Skip to content

Commit

Permalink
feat(projects): add before guard
Browse files Browse the repository at this point in the history
  • Loading branch information
Ohh-889 committed Sep 12, 2024
1 parent 58d1feb commit 13b0cab
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 23 deletions.
25 changes: 14 additions & 11 deletions packages/simple-router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,18 @@ export function createRouter({ beforeEach, initRoutes, mode, opt, getReactRoutes

const flattenRoutes = routes.flat();

const reactRoutes = flattenRoutes.map(route => {
const match = matcher.getRecordMatcher(route.name);
if (match) return null;
// Add route
addRoute(route);
// Transform to react-router route
const reactRoute = getReactRoutes(route);

return reactRoute;
});
const reactRoutes = flattenRoutes
.map(route => {
const match = matcher.getRecordMatcher(route.name);
if (match) return null;
// Add route
addRoute(route);
// Transform to react-router route
const reactRoute = getReactRoutes(route);

return reactRoute;
})
.filter(Boolean);
if (reactRoutes.length < 1) return;
// Add to react-router's routes
reactRouter.patchRoutes(parent, reactRoutes as RouteObject[]);
Expand Down Expand Up @@ -117,7 +119,8 @@ export function createRouter({ beforeEach, initRoutes, mode, opt, getReactRoutes

if (typeof param === 'object') {
const finalRedirectPath = resolve(param);
reactRouter.navigate(finalRedirectPath.fullPath);

reactRouter.navigate(finalRedirectPath.fullPath || finalRedirectPath.path);
}

return true;
Expand Down
11 changes: 6 additions & 5 deletions packages/simple-router/src/types/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,17 @@ export interface Router {
removeRoute: (name: string) => void;
}

export type NavigationGuardNext = (
param?: boolean | undefined | null | string | Location | RouteLocationNamedRaw
) => boolean;

export type BeforeEach = (
to: RouteLocationNormalizedLoaded,
from: RouteLocationNormalizedLoaded,
next: (param?: boolean | undefined | null | string | Location | RouteLocationNamedRaw) => boolean
blockerOrJump: NavigationGuardNext
) => boolean;

export type Init = (
current: RouteLocationNormalizedLoaded,
next: (param?: boolean | undefined | null | string | Location | RouteLocationNamedRaw) => boolean
) => Promise<boolean>;
export type Init = (current: RouteLocationNormalizedLoaded, blockerOrJump: NavigationGuardNext) => Promise<boolean>;

export type AfterEach = (to: RouteLocationNormalizedLoaded, from: RouteLocationNormalizedLoaded) => void;

Expand Down
155 changes: 150 additions & 5 deletions src/router/guard/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,136 @@
import type { AfterEach, BeforeEach } from '@sa/simple-router';
import type {
AfterEach,
BeforeEach,
Init,
LocationQuery,
NavigationGuardNext,
RouteLocationNamedRaw,
RouteLocationNormalizedLoaded
} from '@sa/simple-router';
import type { RouteKey, RoutePath } from '@elegant-router/types';
import { $t } from '@/locales';
import { localStg } from '@/utils/storage';
import { store } from '@/store';
import { getRouteHome, initAuthRoute, initConstantRoute } from '@/store/slice/route';
import { getRouteName, getRoutePath } from '@/router/elegant/transform';
import { isStaticSuper, selectUserInfo } from '@/store/slice/auth';

export const init: Init = async (current, blockerOrJump) => {
await store.dispatch(initConstantRoute());
const isLogin = Boolean(localStg.get('token'));
if (!isLogin) {
const loginRoute: RouteKey = 'login';
const routeHome = getRouteHome(store.getState());
const query = getRouteQueryOfLoginRoute(current, routeHome as RouteKey);

const location: RouteLocationNamedRaw = {
name: loginRoute,
query
};

return blockerOrJump(location);
}
await store.dispatch(initAuthRoute());
return blockerOrJump();
};

/**
* create route guard
*
* @param router router instance
*/
export const createRouteGuard: BeforeEach = (to, _, next) => {
export const createRouteGuard: BeforeEach = (to, _, blockerOrJump) => {
window.NProgress?.start?.();

const notFoundRoute: RouteKey = 'not-found';

const isNotFoundRoute = to.name === notFoundRoute;

// it is captured by the "not-found" route, then check whether the route exists
if (isNotFoundRoute) {
if (!to.name) {
return blockerOrJump();
}

const exist = getIsAuthRouteExist(to.name as RouteKey);
const noPermissionRoute: RouteKey = '403';

if (exist) {
const location: RouteLocationNamedRaw = {
name: noPermissionRoute
};

return blockerOrJump(location);
}

return blockerOrJump();
}

const rootRoute: RouteKey = 'root';
const loginRoute: RouteKey = 'login';
const noAuthorizationRoute: RouteKey = '403';

const isLogin = Boolean(localStg.get('token'));
const needLogin = !to.meta.constant;
const routeRoles = to.meta.roles || [];

const hasRole = selectUserInfo(store.getState()).roles.some(role => routeRoles.includes(role));

const hasAuth = store.dispatch(isStaticSuper()) || !routeRoles.length || hasRole;

const routeSwitches: CommonType.StrategicPattern[] = [
// if it is login route when logged in, then switch to the root page
{
condition: isLogin && to.path.includes('login'),
callback: () => {
return blockerOrJump({ name: rootRoute });
}
},
// if it is constant route, then it is allowed to access directly
{
condition: !needLogin,
callback: () => {
return handleRouteSwitch(to, blockerOrJump);
}
},
// if the route need login but the user is not logged in, then switch to the login page
{
condition: !isLogin && needLogin,
callback: () => {
return blockerOrJump({ name: loginRoute, query: { redirect: to.fullPath } });
}
},
// if the user is logged in and has authorization, then it is allowed to access
{
condition: isLogin && needLogin && hasAuth,
callback: () => {
return handleRouteSwitch(to, blockerOrJump);
}
},
// if the user is logged in but does not have authorization, then switch to the 403 page
{
condition: isLogin && needLogin && !hasAuth,
callback: () => {
return blockerOrJump({ name: noAuthorizationRoute });
}
}
];

const res = routeSwitches.find(({ condition }) => condition)?.callback || blockerOrJump;

return res();
};

function handleRouteSwitch(to: RouteLocationNormalizedLoaded, NavigationGuardNext: NavigationGuardNext) {
// route with href
if (to.meta.href) {
window.open(to.meta.href, '_blank');

return next(true);
return NavigationGuardNext(true);
}

return next();
};
return NavigationGuardNext();
}

export const afterEach: AfterEach = to => {
const { i18nKey, title } = to.meta;
Expand All @@ -24,3 +139,33 @@ export const afterEach: AfterEach = to => {
document.title = documentTitle ?? 'React-Soybean';
window.NProgress?.done?.();
};

function getRouteQueryOfLoginRoute(to: RouteLocationNormalizedLoaded, routeHome: RouteKey) {
const loginRoute: RouteKey = 'login';
const redirect = to.fullPath;
const [redirectPath, redirectQuery] = redirect.split('?');
const redirectName = getRouteName(redirectPath as RoutePath);

const isRedirectHome = routeHome === redirectName;

const query: LocationQuery = to.name !== loginRoute && !isRedirectHome ? { redirect } : {};

if (isRedirectHome && redirectQuery) {
query.redirect = `/?${redirectQuery}`;
}

return query;
}

/**
* Get is auth route exist
*
* @param routePath Route path
*/
function getIsAuthRouteExist(key: RouteKey) {
const routeName = getRoutePath(key);
if (!routeName) {
return false;
}
return true;
}
32 changes: 31 additions & 1 deletion src/store/slice/route/shared.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ElegantConstRoute } from '@elegant-router/types';
import type { ElegantConstRoute, RouteKey } from '@elegant-router/types';

/**
* Filter auth routes by roles
Expand Down Expand Up @@ -58,3 +58,33 @@ function sortRouteByOrder(route: ElegantConstRoute) {
export function sortRoutesByOrder(routes: ElegantConstRoute[]) {
return routes.map(sortRouteByOrder).sort((a, b) => (Number(a.meta?.order) || 0) - (Number(b.meta?.order) || 0));
}

/**
* Is route exist by route name
*
* @param routeName
* @param routes
*/
export function isRouteExistByRouteName(routeName: RouteKey, routes: ElegantConstRoute[]) {
return routes.some(route => recursiveGetIsRouteExistByRouteName(route, routeName));
}

/**
* Recursive get is route exist by route name
*
* @param route
* @param routeName
*/
function recursiveGetIsRouteExistByRouteName(route: ElegantConstRoute, routeName: RouteKey) {
let isExist = route.name === routeName;

if (isExist) {
return true;
}

if (route.children && route.children.length) {
isExist = route.children.some(item => recursiveGetIsRouteExistByRouteName(item, routeName));
}

return isExist;
}
2 changes: 1 addition & 1 deletion src/types/common.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ declare namespace CommonType {
/** The condition */
condition: boolean;
/** If the condition is true, then call the action function */
callback: () => void;
callback: () => boolean;
}

/**
Expand Down

0 comments on commit 13b0cab

Please sign in to comment.