Skip to content

Commit

Permalink
feat(frontend/nirax): リダイレクトを設定できるように (misskey-dev#13030)
Browse files Browse the repository at this point in the history
* feat(frontend/nirax): リダイレクトを設定できるように

* revert demonstrative changes

* fix

* revert unrelated changes

* リダイレクトの際にパスが変わらない問題を修正

* リダイレクトが必要なrouteを設定

* fix lint

* router向けe2eテストの追加

* fix

---------

Co-authored-by: syuilo <[email protected]>
Co-authored-by: samunohito <[email protected]>
  • Loading branch information
3 people authored and AyumuNekozuki committed Feb 16, 2024
1 parent 73f0ef9 commit 8b1dabc
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 16 deletions.
30 changes: 30 additions & 0 deletions cypress/e2e/router.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
describe('Router transition', () => {
describe('Redirect', () => {
// サーバの初期化。ルートのテストに関しては各describeごとに1度だけ実行で十分だと思う(使いまわした方が早い)
before(() => {
cy.resetState();

// インスタンス初期セットアップ
cy.registerUser('admin', 'pass', true);

// ユーザー作成
cy.registerUser('alice', 'alice1234');

cy.login('alice', 'alice1234');

// アカウント初期設定ウィザード
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 12000 }).click();
cy.wait(500);
cy.get('[data-cy-modal-dialog-ok]').click();
});

it('redirect to user profile', () => {
// テストのためだけに用意されたリダイレクト用ルートに飛ぶ
cy.visit('/redirect-test');

// プロフィールページのURLであることを確認する
cy.url().should('include', '/@alice')
});
});
});
7 changes: 7 additions & 0 deletions packages/frontend/src/components/MkPageWindow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ windowRouter.addListener('push', ctx => {
history.value.push({ path: ctx.path, key: ctx.key });
});

windowRouter.addListener('replace', ctx => {
history.value.pop();
history.value.push({ path: ctx.path, key: ctx.key });
});

windowRouter.init();

provide('router', windowRouter);
provideMetadataReceiver((info) => {
pageMetadata.value = info;
Expand Down
19 changes: 14 additions & 5 deletions packages/frontend/src/global/router/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { App, AsyncComponentLoader, defineAsyncComponent, provide } from 'vue';
import type { RouteDef } from '@/nirax.js';
import { IRouter, Router } from '@/nirax.js';
import { $i, iAmModerator } from '@/account.js';
import MkLoading from '@/pages/_loading_.vue';
Expand All @@ -16,7 +17,7 @@ const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
errorComponent: MkError,
});

const routes = [{
const routes: RouteDef[] = [{
path: '/@:initUser/pages/:initPageName/view-source',
component: page(() => import('@/pages/page-editor/page-editor.vue')),
}, {
Expand Down Expand Up @@ -333,8 +334,7 @@ const routes = [{
component: page(() => import('@/pages/registry.vue')),
}, {
path: '/install-extentions',
// Note: This path is kept for compatibility. It may be deleted.
component: page(() => import('@/pages/install-extensions.vue')),
redirect: '/install-extensions',
loginRequired: true,
}, {
path: '/install-extensions',
Expand Down Expand Up @@ -557,6 +557,11 @@ const routes = [{
path: '/',
component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')),
globalCacheKey: 'index',
}, {
// テスト用リダイレクト設定。ログイン中ユーザのプロフィールにリダイレクトする
path: '/redirect-test',
redirect: $i ? `@${$i.username}` : '/',
loginRequired: true,
}, {
path: '/:(*)',
component: page(() => import('@/pages/not-found.vue')),
Expand All @@ -575,8 +580,6 @@ export function setupRouter(app: App) {

const mainRouter = createRouterImpl(location.pathname + location.search + location.hash);

window.history.replaceState({ key: mainRouter.getCurrentKey() }, '', location.href);

window.addEventListener('popstate', (event) => {
mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
});
Expand All @@ -585,5 +588,11 @@ export function setupRouter(app: App) {
window.history.pushState({ key: ctx.key }, '', ctx.path);
});

mainRouter.addListener('replace', ctx => {
window.history.replaceState({ key: ctx.key }, '', ctx.path);
});

mainRouter.init();

setMainRouter(mainRouter);
}
91 changes: 80 additions & 11 deletions packages/frontend/src/nirax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,25 @@ import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
import { EventEmitter } from 'eventemitter3';
import { safeURIDecode } from '@/scripts/safe-uri-decode.js';

export type RouteDef = {
interface RouteDefBase {
path: string;
component: Component;
query?: Record<string, string>;
loginRequired?: boolean;
name?: string;
hash?: string;
globalCacheKey?: string;
children?: RouteDef[];
};
}

interface RouteDefWithComponent extends RouteDefBase {
component: Component,
}

interface RouteDefWithRedirect extends RouteDefBase {
redirect: string | ((props: Map<string, string | boolean>) => string);
}

export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;

type ParsedPath = (string | {
name: string;
Expand Down Expand Up @@ -48,7 +57,19 @@ export type RouterEvent = {
same: () => void;
}

export type Resolved = { route: RouteDef; props: Map<string, string | boolean>; child?: Resolved; };
export type Resolved = {
route: RouteDef;
props: Map<string, string | boolean>;
child?: Resolved;
redirected?: boolean;

/** @internal */
_parsedRoute: {
fullPath: string;
queryString: string | null;
hash: string | null;
};
};

function parsePath(path: string): ParsedPath {
const res = [] as ParsedPath;
Expand Down Expand Up @@ -81,6 +102,11 @@ export interface IRouter extends EventEmitter<RouterEvent> {
currentRoute: ShallowRef<RouteDef>;
navHook: ((path: string, flag?: any) => boolean) | null;

/**
* ルートの初期化(eventListenerの定義後に必ず呼び出すこと)
*/
init(): void;

resolve(path: string): Resolved | null;

getCurrentPath(): any;
Expand Down Expand Up @@ -156,26 +182,38 @@ export interface IRouter extends EventEmitter<RouterEvent> {
export class Router extends EventEmitter<RouterEvent> implements IRouter {
private routes: RouteDef[];
public current: Resolved;
public currentRef: ShallowRef<Resolved> = shallowRef();
public currentRoute: ShallowRef<RouteDef> = shallowRef();
public currentRef: ShallowRef<Resolved>;
public currentRoute: ShallowRef<RouteDef>;
private currentPath: string;
private isLoggedIn: boolean;
private notFoundPageComponent: Component;
private currentKey = Date.now().toString();
private redirectCount = 0;

public navHook: ((path: string, flag?: any) => boolean) | null = null;

constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
super();

this.routes = routes;
this.current = this.resolve(currentPath)!;
this.currentRef = shallowRef(this.current);
this.currentRoute = shallowRef(this.current.route);
this.currentPath = currentPath;
this.isLoggedIn = isLoggedIn;
this.notFoundPageComponent = notFoundPageComponent;
this.navigate(currentPath, null, false);
}

public init() {
const res = this.navigate(this.currentPath, null, false);
this.emit('replace', {
path: res._parsedRoute.fullPath,
key: this.currentKey,
});
}

public resolve(path: string): Resolved | null {
const fullPath = path;
let queryString: string | null = null;
let hash: string | null = null;
if (path[0] === '/') path = path.substring(1);
Expand All @@ -188,6 +226,12 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
path = path.substring(0, path.indexOf('?'));
}

const _parsedRoute = {
fullPath,
queryString,
hash,
};

if (_DEV_) console.log('Routing: ', path, queryString);

function check(routes: RouteDef[], _parts: string[]): Resolved | null {
Expand Down Expand Up @@ -238,6 +282,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
route,
props,
child,
_parsedRoute,
};
} else {
continue forEachRouteLoop;
Expand All @@ -263,6 +308,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
return {
route,
props,
_parsedRoute,
};
} else {
if (route.children) {
Expand All @@ -272,6 +318,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
route,
props,
child,
_parsedRoute,
};
} else {
continue forEachRouteLoop;
Expand All @@ -290,7 +337,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
return check(this.routes, _parts);
}

private navigate(path: string, key: string | null | undefined, emitChange = true) {
private navigate(path: string, key: string | null | undefined, emitChange = true, _redirected = false): Resolved {
const beforePath = this.currentPath;
this.currentPath = path;

Expand All @@ -300,6 +347,20 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
throw new Error('no route found for: ' + path);
}

if ('redirect' in res.route) {
let redirectPath: string;
if (typeof res.route.redirect === 'function') {
redirectPath = res.route.redirect(res.props);
} else {
redirectPath = res.route.redirect + (res._parsedRoute.queryString ? '?' + res._parsedRoute.queryString : '') + (res._parsedRoute.hash ? '#' + res._parsedRoute.hash : '');
}
if (_DEV_) console.log('Redirecting to: ', redirectPath);
if (_redirected && this.redirectCount++ > 10) {
throw new Error('redirect loop detected');
}
return this.navigate(redirectPath, null, emitChange, true);
}

if (res.route.loginRequired && !this.isLoggedIn) {
res.route.component = this.notFoundPageComponent;
res.props.set('showLoginPopup', true);
Expand All @@ -321,7 +382,11 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
});
}

return res;
this.redirectCount = 0;
return {
...res,
redirected: _redirected,
};
}

public getCurrentPath() {
Expand All @@ -345,15 +410,19 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
const res = this.navigate(path, null);
this.emit('push', {
beforePath,
path,
path: res._parsedRoute.fullPath,
route: res.route,
props: res.props,
key: this.currentKey,
});
}

public replace(path: string, key?: string | null) {
this.navigate(path, key);
const res = this.navigate(path, key);
this.emit('replace', {
path: res._parsedRoute.fullPath,
key: this.currentKey,
});
}
}

Expand Down

0 comments on commit 8b1dabc

Please sign in to comment.