From dcc790535aa260d0dbe1c5014656ea698d0d767a Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Nov 2024 18:07:39 +0100 Subject: [PATCH] Add async store --- src/worker/core/async-connector.js | 52 ++++++++++ src/worker/store.ts | 5 +- www/common/store-interface.js | 147 +++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/worker/core/async-connector.js create mode 100644 www/common/store-interface.js diff --git a/src/worker/core/async-connector.js b/src/worker/core/async-connector.js new file mode 100644 index 000000000..43559d7b5 --- /dev/null +++ b/src/worker/core/async-connector.js @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* global importScripts */ + +const Interface = require('./interface'); +const Util = require('../../common/common-util'); +let start = (setConfig) => { + let ready = false; + let closed = false; + let onMsg; + const sendMsgEv = Util.mkEvent(); + const closeStore = () => { closed = true; }; + const onMessage = (f) => { + sendMsgEv.reg((data) => { + setTimeout(() => { + f(data); + }); + }); + }; + const postMsg = (data) => { + if (closed) { return; } + sendMsgEv.fire(data); + }; + const query = () => { + if (!onMsg || closed) { return; } + onMsg.fire({data, origin:''}); + }; + + let init = (cfg) => { + if (ready) { return; } + setConfig(cfg); + Interface.init(closeStore); + ready = true; + Interface.initClient({ + postMsg + }, function (_onMsg) { + onMsg = _onMsg; + postMsg('STORE_READY'); + }); + }; + return { + init, + onMessage, + query + }; +}; + +module.exports = { start }; + + diff --git a/src/worker/store.ts b/src/worker/store.ts index ab3f6c22a..2e347fa54 100644 --- a/src/worker/store.ts +++ b/src/worker/store.ts @@ -25,6 +25,7 @@ import * as Store from './async-store.js'; import * as StoreRpc from './core/store-rpc.js'; import * as Interface from './core/interface.js'; import * as SWConnector from './core/sw-connector.js'; +import * as AsyncConnector from './core/async-connector.js'; // Components import * as Migrate from './components/migrate-user-object.js'; @@ -107,6 +108,7 @@ let start = (cfg: StoreConfig):void => { let inWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; let inSharedWorker = typeof SharedWorkerGlobalScope !== 'undefined' && self instanceof SharedWorkerGlobalScope; +let storeObject = {}; if (inSharedWorker) { console.error('SHAREDWORKER'); SWConnector.start(start); @@ -116,10 +118,11 @@ if (inSharedWorker) { console.error('NODEJS'); } else { console.error('BROWSER'); + storeObject = AsyncConnector.start(start); } export { - start + storeObject }; diff --git a/www/common/store-interface.js b/www/common/store-interface.js new file mode 100644 index 000000000..43dd6036e --- /dev/null +++ b/www/common/store-interface.js @@ -0,0 +1,147 @@ +(() => { +const factory = function () { + // XXX TODO + // Support NodeJS + // Support WebWorker + // Return a usable API instead of postMsg/msgEv + // XXX handle AppConfig, ApiConfig, MEssages and Broadcast in this file directly? + // --> pull them using require if browser or... ? + + let create = function (cfg = {}) { + let { noWorker, noSharedWorker, AppConfig, + ApiConfig, Messages, Broadcast } = cfg; + + const mkEvent = function () { + var handlers = []; + return { + reg: function (cb) { + handlers.push(cb); + }, + unreg: function (cb) { + if (handlers.indexOf(cb) === -1) { return; } + handlers.splice(handlers.indexOf(cb), 1); + }, + fire: function () { + var args = Array.prototype.slice.call(arguments); + handlers.forEach(function (h) { + h.apply(null, args); + }); + } + }; + }; + + let called = false; + let msgEv = mkEvent(); + let todo = (resolve, reject) => { + if (called) { return; } + called = true; + + if (!noWorker && !noSharedWorker && typeof(SharedWorker) !== "undefined") { + worker = new SharedWorker('/common/worker.bundle.js?' + urlArgs); + worker.onerror = function (e) { + console.error(e.message); + }; + worker.port.onmessage = function (ev) { + if (ev.data === "SW_READY") { + return; + } + msgEv.fire(ev); + }; + postMsg = function (data) { + worker.port.postMessage(Util.clone(data)); + }; + postMsg({ + type: 'INIT', + cfg: { + AppConfig, + ApiConfig, + Messages, + Broadcast + } + }); + window.addEventListener('unload', function () { + postMsg('CLOSE'); + }); + return void resolve(postMsg, msgEv); + } + + // eslint-disable-next-line no-constant-condition + if (!noWorker && Worker) { + worker = new Worker('/common/worker.bundle.js?' + urlArgs); + worker.onerror = function (e) { + console.error(e.message); + }; + worker.onmessage = function (ev) { + msgEv.fire(ev); + }; + postMsg = function (data) { + worker.postMessage(data); + }; + return void resolve(postMsg, msgEv); + } + + // Use the async store in the main thread if workers + // aren't available + if (typeof(require) === "undefined") { return; } + require(['/common/worker.bundle.js'], function (Store) { + Store.onMessage(function (data) { + msgEv.fire({data: data, origin: ''}); + }); + postMsg = function (d) { + setTimeout(function () { + Store.query(d); + }); + }; + Store.init({ + AppConfig, + ApiConfig, + Messages, + Broadcast + }); + resolve(postMsg, msgEv); + }); + }; + + return new Promise((resolve, reject) => { + if (typeof(SharedWorker) !== "undefined") { + try { + new SharedWorker(''); + } catch (e) { + noSharedWorker = true; + console.log('Disabling SharedWorker because of privacy settings.'); + } + } + if (typeof(Worker) !== "undefined") { + try { + worker = new Worker('/common/outer/testworker.js?' + urlArgs); + worker.onerror = function (errEv) { + errEv.preventDefault(); + errEv.stopPropagation(); + noWorker = true; + worker.terminate(); + todo(resolve, reject); + }; + worker.onmessage = function (ev) { + if (ev.data === "OK") { + worker.terminate(); + todo(resolve, reject); + } + }; + } catch (e) { + noWorker = true; + todo(resolve, reject); + } + } + }); + }; + + return create; +}; + + +if (typeof(module) !== 'undefined' && module.exports) { + module.exports = factory(); +} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { + define([], factory); +} +})();