reactant-share
Version:
A framework for building shared web applications with Reactant
109 lines (107 loc) • 3.5 kB
text/typescript
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable consistent-return */
import { containerKey, actionIdentifier } from 'reactant';
import type { Container } from 'reactant';
import { LastAction } from 'reactant-last-action';
import { HandleServerOptions } from './interfaces';
import {
isClientName,
lastActionName,
loadFullStateActionName,
proxyClientActionName,
} from './constants';
import { PortDetector } from './modules/portDetector';
import { PatchesChecker } from './modules/patchesChecker';
import { applyMethod } from './applyMethod';
import { isSharedWorker } from './utils';
export const handleServer = ({
app,
transport,
disposeServer,
disposeClient,
enablePatchesChecker,
}: HandleServerOptions) => {
if (!transport) {
throw new Error(`The server transport does not exist.`);
}
disposeServer?.();
disposeClient?.();
const container: Container = app.instance[containerKey];
const lastAction = container.get(LastAction);
const portDetector = container.get(PortDetector);
const patchesChecker: PatchesChecker | null = enablePatchesChecker
? container.get(PatchesChecker)
: null;
if (isSharedWorker) {
let executed = false;
// before any other event, it should be connected with the first client
globalThis.addEventListener('connect', () => {
if (executed) return;
executed = true;
portDetector.setPort({ server: app }, transport);
});
} else {
portDetector.setPort({ server: app }, transport);
}
const disposeListeners: ((() => void) | undefined)[] = [];
disposeListeners.push(transport.listen(isClientName, async () => true));
disposeListeners.push(
transport.listen(loadFullStateActionName, async (sequence) =>
lastAction.sequence > sequence ? app.store?.getState() : null
)
);
disposeListeners.push(
transport.listen(proxyClientActionName, async (options) => {
if (options.hook) {
const hook = portDetector.serverHooks[options.hook];
if (typeof hook === 'function') {
const result = await hook(options);
const { sequence } = portDetector.lastAction;
return [sequence, result];
}
}
const result = await applyMethod(app, options);
const { sequence } = portDetector.lastAction;
return [sequence, result];
})
);
disposeListeners.push(() => transport.dispose());
let oldStateTree: Record<string, any>;
if (__DEV__) {
oldStateTree = app.store!.getState();
}
disposeListeners.push(
app.store?.subscribe(() => {
try {
if (lastAction.action) {
const { action } = lastAction;
if (!portDetector.lastAction.options?.ignoreAction?.(action)) {
if (
__DEV__ &&
enablePatchesChecker &&
patchesChecker &&
action?._reactant === actionIdentifier &&
action._patches
) {
patchesChecker.checkPatches(oldStateTree, action);
}
transport.emit({ name: lastActionName, respond: false }, action);
}
}
} finally {
if (__DEV__) {
oldStateTree = app.store!.getState();
}
}
})
);
// app synchronizes state to all clients immediately after switching server port
if (portDetector.previousPort === 'client') {
portDetector.syncToClients();
}
return () => {
for (const dispose of disposeListeners) {
dispose?.();
}
};
};