UNPKG

reactant-share

Version:

A framework for building shared web applications with Reactant

404 lines (401 loc) 20.4 kB
import { __extends, __spreadArray, __read, __awaiter, __assign, __decorate, __param, __metadata, __generator } from '../node_modules/tslib/tslib.es6.js'; import { nameKey, getRef, watch, actionIdentifier, injectable, inject, optional, applyPatches, PluginModule } from 'reactant'; import { createTransport } from 'data-transport'; import { PortDetector } from './portDetector.js'; import { Storage as ReactantStorage } from './storage.js'; import { pushAllStateName, syncStateName, requestSyncAllStateName, syncStateActionName, proxyWorkerExecuteName, coworkerKey, proxyExecutorKey, syncModuleStateActionName, storageModuleName } from '../constants.js'; var CoworkerOptions = Symbol('CoworkerOptions'); var Coworker = /** @class */ (function (_super) { __extends(Coworker, _super); function Coworker(portDetector, coworkerOptions, storage) { var _a, _b; var _this = _super.call(this) || this; _this.portDetector = portDetector; _this.coworkerOptions = coworkerOptions; _this.storage = storage; _this.proxyModuleKeys = []; _this.ignoreSyncStateKeys = (_b = (_a = _this.coworkerOptions) === null || _a === void 0 ? void 0 : _a.ignoreSyncStateKeys) !== null && _b !== void 0 ? _b : []; _this.afterCreateStore = function (store) { _this.applyProxyExecute(); _this.applyProxyModules(_this.proxyModules); _this.applyProxyState(); if (_this.isCoworker) { if (_this.sequence === -1) { _this.sequence = 0; _this.pushAllState(); } if (_this.storage) { // sync up last state when proxy module state is rehydrated _this.proxyModules.forEach(function (serviceIdentifier) { var modules = _this.ref.container.getAll(serviceIdentifier); modules.forEach(function (module) { if (_this.storage.storageSettingMap.has(module)) { var stopWatching_1 = watch(module, function () { return _this.storage.getRehydrated(module); }, function (rehydrated) { if (rehydrated) { stopWatching_1(); var _a = getRef(module), identifier = _a.identifier, state = _a.state; _this.sequence += 1; // If the coworker runs before the main thread, // then the sequence will ensure that the state is properly synchronized. _this.transport.emit({ name: syncStateName, respond: false }, { _reactant: actionIdentifier, type: "".concat(actionIdentifier, "_").concat(syncModuleStateActionName), params: [], _patches: [ { op: 'replace', path: [identifier], value: state, }, ], }, _this.sequence); } }); } }); }); } } if (_this.isMain && _this.sequence === -1) { _this.requestSyncAllState(); } return store; }; _this.sequence = -1; if (!_this.portDetector.isClient) { _this.transport = _this.createTransport(); } _this.proxyModules = __spreadArray([], __read(_this.coworkerOptions.useModules), false); if (_this.isCoworker && _this.enablePatchesChecker) { // stricter checks to prevent cross-module state updates. _this.middleware = function (store) { return function (next) { return function (_action) { var _patches = _action._patches, type = _action.type, method = _action.method; // skip check for storage module change any state if (type === storageModuleName) return next(_action); var hasCoworkerState; _patches === null || _patches === void 0 ? void 0 : _patches.forEach(function (_a, index) { var path = _a.path; _a.op; _a.value; var _hasCoworkerState = _this.proxyModuleKeys.includes("".concat(path[0])); // ignore first patch if (!index) { hasCoworkerState = _hasCoworkerState; } else if (hasCoworkerState !== _hasCoworkerState) { var methodName = "".concat(type, ".").concat(method); throw new Error("Update state error: Mixed update of coworker proxy state and isolated state is not supported, please check method '".concat(methodName, "'.")); } }); return next(_action); }; }; }; } if (_this.storage && _this.isMain) { // main thread should ignore proxy module storage state _this.storage.beforeCombinePersistReducer = function () { var proxyModules = []; _this.proxyModules.forEach(function (serviceIdentifier) { var modules = _this.ref.container.getAll(serviceIdentifier); proxyModules.push.apply(proxyModules, __spreadArray([], __read(modules), false)); }); _this.storage.storageSettingMap.forEach(function (_, module) { if (proxyModules.includes(module)) { _this.storage.storageSettingMap.delete(module); } }); }; } return _this; } Coworker.prototype.createTransport = function () { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r; if (this.portDetector.isWorkerMode) { if (this.isCoworker) { return ((_b = (_a = this.coworkerOptions.transports) === null || _a === void 0 ? void 0 : _a.coworker) !== null && _b !== void 0 ? _b : createTransport('Broadcast', { prefix: this.prefix, verbose: (_c = this.coworkerOptions) === null || _c === void 0 ? void 0 : _c.enableTransportDebugger, logger: (_d = this.coworkerOptions) === null || _d === void 0 ? void 0 : _d.logger, })); } if (this.portDetector.sharedAppOptions.port === 'server') { return ((_f = (_e = this.coworkerOptions.transports) === null || _e === void 0 ? void 0 : _e.main) !== null && _f !== void 0 ? _f : createTransport('Broadcast', { prefix: this.prefix, verbose: (_g = this.coworkerOptions) === null || _g === void 0 ? void 0 : _g.enableTransportDebugger, logger: (_h = this.coworkerOptions) === null || _h === void 0 ? void 0 : _h.logger, })); } } else if (this.isCoworker) { var isWebWorker = !globalThis.SharedWorkerGlobalScope && globalThis.WorkerGlobalScope; return ((_k = (_j = this.coworkerOptions.transports) === null || _j === void 0 ? void 0 : _j.coworker) !== null && _k !== void 0 ? _k : createTransport(isWebWorker ? 'WebWorkerInternal' : 'SharedWorkerInternal', { prefix: this.prefix, verbose: (_l = this.coworkerOptions) === null || _l === void 0 ? void 0 : _l.enableTransportDebugger, logger: (_m = this.coworkerOptions) === null || _m === void 0 ? void 0 : _m.logger, })); } else if (this.portDetector.sharedAppOptions.port !== 'client') { if ((_o = this.coworkerOptions.transports) === null || _o === void 0 ? void 0 : _o.main) { return this.coworkerOptions.transports.main; } if (!((_p = this.coworkerOptions) === null || _p === void 0 ? void 0 : _p.worker)) { if (process.env.NODE_ENV !== 'production') console.warn('No coworker support in server port.'); return; } if (this.coworkerOptions.worker instanceof Worker) { return createTransport('WebWorkerClient', { worker: this.coworkerOptions.worker, prefix: this.prefix, verbose: this.coworkerOptions.enableTransportDebugger, logger: (_q = this.coworkerOptions) === null || _q === void 0 ? void 0 : _q.logger, }); } return createTransport('SharedWorkerClient', { worker: this.coworkerOptions.worker, prefix: this.prefix, verbose: this.coworkerOptions.enableTransportDebugger, logger: (_r = this.coworkerOptions) === null || _r === void 0 ? void 0 : _r.logger, }); } }; Object.defineProperty(Coworker.prototype, "prefix", { get: function () { return "reactant-share:".concat(this.portDetector.sharedAppOptions.name, ":coworker:").concat(this.name); }, enumerable: false, configurable: true }); Object.defineProperty(Coworker.prototype, "name", { get: function () { return this[nameKey]; }, enumerable: false, configurable: true }); Object.defineProperty(Coworker.prototype, "isCoworker", { /** * Whether the current thread is the coworker thread. */ get: function () { return this.coworkerOptions.isCoworker; }, enumerable: false, configurable: true }); Object.defineProperty(Coworker.prototype, "isMain", { /** * Whether the current thread is the main thread. */ get: function () { return !this.isCoworker && !!this.transport; }, enumerable: false, configurable: true }); // TODO: fix dynamic module with storage state /** * Add proxy modules. */ Coworker.prototype.addProxyModules = function (modules) { var _a; if (process.env.NODE_ENV !== 'production') { if (!Array.isArray(modules)) { throw new TypeError("Expected an array, but received: ".concat(typeof modules)); } } (_a = this.proxyModules).push.apply(_a, __spreadArray([], __read(modules), false)); }; /** * Add ignore sync state keys */ Coworker.prototype.addIgnoreSyncStateKeys = function (keys) { var _a; (_a = this.ignoreSyncStateKeys).push.apply(_a, __spreadArray([], __read(keys), false)); }; Object.defineProperty(Coworker.prototype, "enablePatchesChecker", { get: function () { var _a, _b; return (_b = (_a = this.coworkerOptions) === null || _a === void 0 ? void 0 : _a.enablePatchesChecker) !== null && _b !== void 0 ? _b : process.env.NODE_ENV !== 'production'; }, enumerable: false, configurable: true }); Object.defineProperty(Coworker.prototype, "ref", { get: function () { return getRef(this); }, enumerable: false, configurable: true }); Coworker.prototype.applyProxyState = function () { var _this = this; if (this.isMain) { this.transport.listen(pushAllStateName, function (options) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { this.handleSyncAllState(options); return [2 /*return*/]; }); }); }); this.transport.listen(syncStateName, function (action, coworkerSequence) { return __awaiter(_this, void 0, void 0, function () { var currentState, _sequence, state; return __generator(this, function (_a) { // If the sequence is not continuous, it means that the main thread need sync all state from coworker thread. if (this.sequence + 1 !== coworkerSequence) { this.requestSyncAllState(); return [2 /*return*/]; } currentState = this.ref.store.getState(); _sequence = this.portDetector.lastAction.sequence; state = applyPatches(currentState, action._patches); this.ignoreStates(state, currentState); this.ref.store.dispatch(__assign(__assign({}, action), { state: state, _sequence: _sequence })); this.sequence = coworkerSequence; return [2 /*return*/]; }); }); }); } if (this.isCoworker) { watch(this, function () { return _this.portDetector.lastAction.action; }, function (lastAction) { var _a; var _patches = (_a = lastAction._patches) === null || _a === void 0 ? void 0 : _a.filter(function (_a) { var path = _a.path; var _b = __read(path, 2), module = _b[0], key = _b[1]; return (_this.proxyModuleKeys.includes(module) && !_this.ignoreSyncStateKeys.includes(key)); }); if (!_patches || _patches.length === 0) return; _this.sequence += 1; // If the coworker runs before the main thread, // then the sequence will ensure that the state is properly synchronized. _this.transport.emit({ name: syncStateName, respond: false }, __assign(__assign({}, lastAction), { _patches: _patches }), _this.sequence); }); this.transport.listen(requestSyncAllStateName, function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { this.pushAllState(); return [2 /*return*/]; }); }); }); } }; Coworker.prototype.pushAllState = function () { var _this = this; var currentState = this.ref.store.getState(); var state = {}; this.proxyModuleKeys.forEach(function (key) { state[key] = __assign({}, currentState[key]); _this.ignoreSyncStateKeys.forEach(function (ignoreKey) { delete state[key][ignoreKey]; }); }); this.transport.emit({ name: pushAllStateName, respond: false }, { state: state, sequence: this.sequence, }); }; Coworker.prototype.handleSyncAllState = function (options) { if (options.sequence === this.sequence && this.sequence !== -1) { return; } this.sequence = options.sequence; var currentState = this.ref.store.getState(); var _sequence = this.portDetector.lastAction.sequence; var state = __assign(__assign({}, currentState), options.state); this.ignoreStates(state, currentState); this.ref.store.dispatch({ _reactant: actionIdentifier, type: "".concat(actionIdentifier, "_").concat(syncStateActionName), state: state, _sequence: _sequence, }); }; Coworker.prototype.requestSyncAllState = function () { this.transport.emit({ name: requestSyncAllStateName, respond: false }); }; Coworker.prototype.ignoreStates = function (state, currentState) { var _this = this; this.ignoreSyncStateKeys.forEach(function (ignoreKey) { _this.proxyModuleKeys.forEach(function (key) { state[key][ignoreKey] = currentState[key][ignoreKey]; }); }); }; Coworker.prototype.applyProxyExecute = function () { var _this = this; if (this.isCoworker) { this.transport.listen(proxyWorkerExecuteName, function (_a) { return __awaiter(_this, [_a], void 0, function (_b) { var instance; var _c; var module = _b.module, method = _b.method, args = _b.args; return __generator(this, function (_d) { instance = this.ref.modules[module]; if (process.env.NODE_ENV !== 'production' && typeof instance[method] !== 'function') { console.warn("The method \"".concat(method, "\" does not exist in the module \"").concat(module, "\".")); } return [2 /*return*/, (_c = instance[method]) === null || _c === void 0 ? void 0 : _c.call.apply(_c, __spreadArray([instance], __read(args), false))]; }); }); }); } }; Coworker.prototype.applyProxyModules = function (proxyModules) { var _this = this; proxyModules.forEach(function (serviceIdentifier) { var modules = _this.ref.container.getAll(serviceIdentifier); modules.forEach(function (module) { if (_this.portDetector.isolatedModules.includes(module)) { throw new Error("\n The module \"".concat(serviceIdentifier.toString(), "\" is isolated, and cannot be used as a proxy module in '").concat(_this.name, "' coworker.\n ")); } if (process.env.NODE_ENV !== 'production' && module[coworkerKey]) { console.warn("The proxy module \"".concat(serviceIdentifier.toString(), "\" with \"").concat(_this.name, "\" coworker already exists.")); } module[coworkerKey] = _this; _this.proxyModuleKeys.push(getRef(module).identifier); if (_this.isMain) { if (process.env.NODE_ENV !== 'production' && module[proxyExecutorKey]) { console.warn("The proxy module \"".concat(serviceIdentifier.toString(), "\" with \"").concat(_this.name, "\" already exists.")); } module[proxyExecutorKey] = function (execParams) { return _this.transport.emit(proxyWorkerExecuteName, execParams); }; } }); }); }; Coworker = __decorate([ injectable({ name: 'Coworker', }), __param(1, inject(CoworkerOptions)), __param(2, optional()), __metadata("design:paramtypes", [PortDetector, Object, ReactantStorage]) ], Coworker); return Coworker; }(PluginModule)); var ICoworker = Coworker; var createCoworker = function (name) { var CoworkerOptions = Symbol("".concat(name, "CoworkerOptions")); var Coworker = /** @class */ (function (_super) { __extends(Coworker, _super); function Coworker(portDetector, coworkerOptions, storage) { var _this = _super.call(this, portDetector, coworkerOptions, storage) || this; _this.portDetector = portDetector; _this.coworkerOptions = coworkerOptions; _this.storage = storage; return _this; } Coworker = __decorate([ injectable({ name: "".concat(name, "Coworker"), }), __param(1, inject(CoworkerOptions)), __param(2, optional()), __metadata("design:paramtypes", [PortDetector, Object, ReactantStorage]) ], Coworker); return Coworker; }(ICoworker)); return [Coworker, CoworkerOptions]; }; var getCoworker = function (instance) { return instance === null || instance === void 0 ? void 0 : instance[coworkerKey]; }; export { Coworker, CoworkerOptions, createCoworker, getCoworker };