reactant-share
Version:
A framework for building shared web applications with Reactant
360 lines (357 loc) • 17 kB
JavaScript
import { __awaiter, __generator, __assign, __spreadArray, __read, __rest } from './node_modules/tslib/tslib.es6.js';
import { ModuleRef, createApp } from 'reactant';
import { createTransport } from 'data-transport';
import { LastAction, LastActionOptions } from 'reactant-last-action';
import { LOCATION_CHANGE } from 'reactant-router';
import { handleServer } from './server.js';
import { handleClient } from './client.js';
import { createBroadcastTransport } from './createTransport.js';
import { SharedAppOptions, isClientName } from './constants.js';
import { PortDetector } from './modules/portDetector.js';
import { useLock } from './lock.js';
import { IdentifierChecker } from './modules/identifierChecker.js';
import { PatchesChecker } from './modules/patchesChecker.js';
var createBaseApp = function (_a) {
var _b, _c, _d, _e;
var share = _a.share, options = __rest(_a, ["share"]);
(_b = options.modules) !== null && _b !== void 0 ? _b : (options.modules = []);
(_c = options.devOptions) !== null && _c !== void 0 ? _c : (options.devOptions = {});
options.devOptions.enablePatches = true;
options.modules.push(LastAction, {
provide: LastActionOptions,
useFactory: function (moduleRef) {
var portDetector;
return {
stateKey: "lastAction-".concat(share.name),
// ignore router state and isolated state sync for last action
ignoreAction: function (action) {
var _a, _b;
if (!portDetector) {
portDetector = moduleRef.get(PortDetector);
}
var firstPath = (_b = (_a = action._patches) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.path[0];
return (action.type === LOCATION_CHANGE ||
(firstPath && portDetector.hasIsolatedState("".concat(firstPath))));
},
};
},
deps: [ModuleRef],
}, {
provide: SharedAppOptions,
useValue: share,
}, PortDetector);
if (process.env.NODE_ENV !== 'production') {
options.modules.push(IdentifierChecker);
}
if (share.enablePatchesChecker) {
options.modules.push(PatchesChecker);
}
var app;
var disposeServer;
var disposeClient;
var serverTransport = (_d = share.transports) === null || _d === void 0 ? void 0 : _d.server;
var clientTransport = (_e = share.transports) === null || _e === void 0 ? void 0 : _e.client;
var isServer = share.port === 'server';
var transform = share.transform;
share.transform = function (changedPort) {
var _a, _b;
var serverTransport = (_a = share.transports) === null || _a === void 0 ? void 0 : _a.server;
var clientTransport = (_b = share.transports) === null || _b === void 0 ? void 0 : _b.client;
if (changedPort === 'server') {
if (!serverTransport) {
throw new Error("'transports.server' does not exist.");
}
disposeServer = handleServer({
app: app,
transport: serverTransport,
disposeServer: disposeServer,
disposeClient: disposeClient,
enablePatchesChecker: share.enablePatchesChecker,
});
}
else {
if (!clientTransport) {
throw new Error("'transports.client' does not exist.");
}
disposeClient = handleClient({
app: app,
transport: clientTransport,
disposeServer: disposeServer,
disposeClient: disposeClient,
enablePatchesFilter: share.enablePatchesFilter,
});
}
transform === null || transform === void 0 ? void 0 : transform(changedPort);
};
app = createApp(options);
if (share.port) {
if (isServer) {
if (!serverTransport) {
throw new Error("'transports.server' does not exist.");
}
disposeServer = handleServer({
app: app,
transport: serverTransport,
enablePatchesChecker: share.enablePatchesChecker,
});
}
else {
if (!clientTransport) {
throw new Error("'transports.client' does not exist.");
}
disposeClient = handleClient({
app: app,
transport: clientTransport,
enablePatchesFilter: share.enablePatchesFilter,
});
}
}
return app;
};
var createSharedTabApp = function (options) { return __awaiter(void 0, void 0, void 0, function () {
var isSafari, app_1, app_2, app, isServer;
var _a, _b, _c;
var _d, _e, _f;
return __generator(this, function (_g) {
switch (_g.label) {
case 0:
if (process.env.NODE_ENV !== 'production' && options.share.forcedSyncClient === false) {
console.warn("'forcedSyncClient' must be enabled in 'SharedTab' mode");
}
options.share.forcedSyncClient = true;
isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (isSafari && !options.share.forcedShare) {
options.share.transports = {};
options.share.port = undefined;
options.share.type = 'Base';
app_1 = createBaseApp(options);
return [2 /*return*/, app_1];
}
(_a = (_d = options.share).transports) !== null && _a !== void 0 ? _a : (_d.transports = {});
(_b = (_e = options.share.transports).client) !== null && _b !== void 0 ? _b : (_e.client = createBroadcastTransport(options.share.name, options.share.enableTransportDebugger, options.share.transportLogger));
(_c = (_f = options.share.transports).server) !== null && _c !== void 0 ? _c : (_f.server = createBroadcastTransport(options.share.name, options.share.enableTransportDebugger, options.share.transportLogger));
if (options.share.port) {
app_2 = createBaseApp(options);
return [2 /*return*/, app_2];
}
isServer = false;
return [4 /*yield*/, Promise.race([
new Promise(function (resolve) {
// TODO: clear locks for testing in SharedTab mode
useLock("reactant-share-app-lock:".concat(options.share.name), function () { return __awaiter(void 0, void 0, void 0, function () {
var _a, _b;
return __generator(this, function (_c) {
if (!app) {
options.share.port = 'server';
app = createBaseApp(options);
}
else {
(_b = (_a = options.share).transform) === null || _b === void 0 ? void 0 : _b.call(_a, 'server');
}
isServer = true;
resolve(app);
return [2 /*return*/, new Promise(function () {
//
})];
});
}); });
}),
new Promise(function (resolve) { return __awaiter(void 0, void 0, void 0, function () {
var isClient, app_3;
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, ((_b = (_a = options.share.transports) === null || _a === void 0 ? void 0 : _a.client) === null || _b === void 0 ? void 0 : _b.emit(isClientName))];
case 1:
isClient = _c.sent();
if (isClient && !isServer) {
options.share.port = 'client';
app_3 = createBaseApp(options);
resolve(app_3);
}
return [2 /*return*/];
}
});
}); }),
])];
case 1:
app = _g.sent();
return [2 /*return*/, app];
}
});
}); };
/**
* ## Description
*
* You can create an shared app with `createSharedApp()` passing app configuration,
* which will asynchronously return an object including `instance`, `store`,
* and `bootstrap()` method(You can run `bootstrap` to start the app inject into the browser or mobile).
*
* ## Example
*
* ```ts
* import { injectable, state, action } from 'reactant';
* import { createSharedApp, delegate, mockPairTransports } from 'reactant-share';
*
* @injectable({
* name: 'counter',
* })
* class Counter {
* @state
* count = 0;
*
* @action
* increase() {
* this.count += 1;
* }
* }
*
* export default async () => {
* const transports = mockPairTransports();
*
* const server = await createSharedApp({
* modules: [],
* main: Counter,
* render: () => {},
* share: {
* name: 'counter',
* type: 'Base',
* port: 'server',
* transports: {
* server: transports[0],
* },
* },
* });
*
* const client = await createSharedApp({
* modules: [],
* main: Counter,
* render: () => {},
* share: {
* name: 'counter',
* type: 'Base',
* port: 'client',
* transports: {
* client: transports[1],
* },
* },
* });
*
* await delegate(client.instance, 'increase', []);
*
* expect(client.instance.count).toBe(1);
* expect(server.instance.count).toBe(1);
* };
* ```
*/
var createSharedApp = function (options) { return __awaiter(void 0, void 0, void 0, function () {
var app, transports, _a, e_1, _b, name_1, shareOptions, bootstrap;
var _c, _d, _e, _f, _g, _h;
var _j, _k;
return __generator(this, function (_l) {
switch (_l.label) {
case 0:
if (typeof options.share === 'undefined') {
throw new Error("'createSharedApp(options)' should be set 'share' option.");
}
// Check to minimized patch.
(_c = (_j = options.share).enablePatchesChecker) !== null && _c !== void 0 ? _c : (_j.enablePatchesChecker = process.env.NODE_ENV !== 'production');
// force Sync for all client
(_d = (_k = options.share).forcedSyncClient) !== null && _d !== void 0 ? _d : (_k.forcedSyncClient = true);
_a = options.share.type;
switch (_a) {
case 'SharedWorker': return [3 /*break*/, 1];
case 'SharedTab': return [3 /*break*/, 5];
case 'Base': return [3 /*break*/, 7];
}
return [3 /*break*/, 8];
case 1:
_l.trys.push([1, 2, , 4]);
transports = {
server: (_e = options.share.transports) === null || _e === void 0 ? void 0 : _e.server,
client: (_f = options.share.transports) === null || _f === void 0 ? void 0 : _f.client,
};
if (options.share.port === 'client' && options.share.worker) {
if (process.env.NODE_ENV !== 'production' && !transports.client) {
if (!(options.share.worker instanceof SharedWorker)) {
throw new Error("'options.share.worker' is not a SharedWorker instance.");
}
}
(_g = transports.client) !== null && _g !== void 0 ? _g : (transports.client = createTransport('SharedWorkerClient', {
worker: options.share.worker,
prefix: "reactant-share:".concat(options.share.name),
verbose: options.share.enableTransportDebugger,
logger: options.share.transportLogger,
}));
}
if (options.share.port === 'server') {
(_h = transports.server) !== null && _h !== void 0 ? _h : (transports.server = createTransport('SharedWorkerInternal', {
prefix: "reactant-share:".concat(options.share.name),
verbose: options.share.enableTransportDebugger,
logger: options.share.transportLogger,
}));
}
else if (options.share.port === 'client' && !transports.client) {
if (typeof options.share.workerURL !== 'string') {
throw new Error("The value of 'options.share.workerURL' should be a string.");
}
transports.client = createTransport('SharedWorkerClient', {
worker: new SharedWorker(options.share.workerURL),
prefix: "reactant-share:".concat(options.share.name),
verbose: options.share.enableTransportDebugger,
logger: options.share.transportLogger,
});
}
options.share.transports = transports;
app = createBaseApp(options);
return [3 /*break*/, 4];
case 2:
e_1 = _l.sent();
console.warn(e_1);
_b = options.share, _b.port, _b.workerURL, name_1 = _b.name, shareOptions = __rest(_b, ["port", "workerURL", "name"]);
return [4 /*yield*/, createSharedTabApp(__assign(__assign({}, options), { share: __assign(__assign({}, shareOptions), { type: 'SharedTab', name: name_1, forcedSyncClient: true }) }))];
case 3:
app = _l.sent();
return [3 /*break*/, 4];
case 4: return [3 /*break*/, 9];
case 5: return [4 /*yield*/, createSharedTabApp(options)];
case 6:
app = _l.sent();
return [3 /*break*/, 9];
case 7:
app = createBaseApp(options);
return [3 /*break*/, 9];
case 8: throw new Error("The value of 'options.share.type' be 'SharedTab', 'SharedWorker' or 'Base'.");
case 9:
bootstrap = app.bootstrap;
return [2 /*return*/, __assign(__assign({}, app), { destroy: function () {
var _a, _b, _c, _d;
app.destroy();
(_b = (_a = options.share.transports) === null || _a === void 0 ? void 0 : _a.client) === null || _b === void 0 ? void 0 : _b.dispose();
(_d = (_c = options.share.transports) === null || _c === void 0 ? void 0 : _c.server) === null || _d === void 0 ? void 0 : _d.dispose();
}, bootstrap: function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return __awaiter(void 0, void 0, void 0, function () {
var result, portDetector;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
result = bootstrap.apply(void 0, __spreadArray([], __read(args), false));
portDetector = app.container.get(PortDetector);
if (!portDetector.isClient) return [3 /*break*/, 2];
return [4 /*yield*/, portDetector.syncFullStatePromise];
case 1:
_a.sent();
_a.label = 2;
case 2: return [2 /*return*/, result];
}
});
});
} })];
}
});
}); };
export { createSharedApp };