UNPKG

reactant-share

Version:

A framework for building shared web applications with Reactant

360 lines (357 loc) 17 kB
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 };