import { Location } from 'history'; import { from, Observable, of, Subject } from 'rxjs'; import { concatMap, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { ResolvedPath } from './PageRouter'; import { verifyResponse } from './applyPreload'; import { PageModule, PageResponse, Store } from './index'; /** * This is the input parameters that every page-level middleware will receive */ export interface MwInput { location: Location; store: Store; mod: PageModule; resolved: ResolvedPath; } /** * Middlewares can optionally choose to return an object where * a certain sub-set of keys have special means. For now we * only support 'response' which allows easy redirects and errors */ export interface MwOutput { response?: PageResponse; } /** * The signature for every page-level middleware */ export type MwFn = (input: MwInput) => Observable | Promise; /** * Apply page-level middlewares. * * Note: middlewares for now can only do the following two things * * 1. They can read & write to a global store that is persisted across browser page views * 2. They can return a PageResponse to enable redirects and errors etc * * @param input * @param mw */ export function applyMiddleware(input: MwInput, mw: MwFn[]): Observable { /** * Create a multi-cast stream of the results of each middleware */ const responses = new Subject(); return from(mw).pipe( concatMap((fn) => fn(input)), takeUntil(responses), mergeMap((mwOutput) => { if (mwOutput && mwOutput.response) { return verifyResponse(mwOutput); } return of(mwOutput); }), tap((x) => x && x.response && responses.next(x.response)), ); } export const OK_RESP: { response: PageResponse } = { response: { kind: 'success' } }; export function errorResponse(message: string, statusCode = 500): { response: PageResponse } { return { response: { kind: 'error', statusCode, message } }; }