reactant-share
Version:
A framework for building shared web applications with Reactant
404 lines (401 loc) • 20.4 kB
JavaScript
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 };