Merge remote-tracking branch 'misskey-original/develop' into develop

This commit is contained in:
mattyatea 2024-01-29 01:09:09 +09:00
commit ef9ea304f7
8 changed files with 143 additions and 27 deletions

View file

@ -160,14 +160,14 @@ id: 'aidx'
# Job concurrency per worker # Job concurrency per worker
#deliverJobConcurrency: 128 #deliverJobConcurrency: 128
#inboxJobConcurrency: 16 #inboxJobConcurrency: 16
#relashionshipJobConcurrency: 16 #relationshipJobConcurrency: 16
# What's relashionshipJob?: # What's relationshipJob?:
# Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations. # Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
# Job rate limiter # Job rate limiter
#deliverJobPerSec: 128 #deliverJobPerSec: 128
#inboxJobPerSec: 32 #inboxJobPerSec: 32
#relashionshipJobPerSec: 64 #relationshipJobPerSec: 64
# Job attempts # Job attempts
#deliverJobMaxAttempts: 12 #deliverJobMaxAttempts: 12

View file

@ -63,6 +63,7 @@
- Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更 - Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更
- Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正 - Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正
- Fix: properly handle cc followers - Fix: properly handle cc followers
- Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec
### Service Worker ### Service Worker
- Enhance: オフライン表示のデザインを改善・多言語対応 - Enhance: オフライン表示のデザインを改善・多言語対応

30
cypress/e2e/router.cy.js Normal file
View file

@ -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')
});
});
});

View file

@ -74,10 +74,10 @@ type Source = {
deliverJobConcurrency?: number; deliverJobConcurrency?: number;
inboxJobConcurrency?: number; inboxJobConcurrency?: number;
relashionshipJobConcurrency?: number; relationshipJobConcurrency?: number;
deliverJobPerSec?: number; deliverJobPerSec?: number;
inboxJobPerSec?: number; inboxJobPerSec?: number;
relashionshipJobPerSec?: number; relationshipJobPerSec?: number;
deliverJobMaxAttempts?: number; deliverJobMaxAttempts?: number;
inboxJobMaxAttempts?: number; inboxJobMaxAttempts?: number;
@ -135,10 +135,10 @@ export type Config = {
outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined; outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined;
deliverJobConcurrency: number | undefined; deliverJobConcurrency: number | undefined;
inboxJobConcurrency: number | undefined; inboxJobConcurrency: number | undefined;
relashionshipJobConcurrency: number | undefined; relationshipJobConcurrency: number | undefined;
deliverJobPerSec: number | undefined; deliverJobPerSec: number | undefined;
inboxJobPerSec: number | undefined; inboxJobPerSec: number | undefined;
relashionshipJobPerSec: number | undefined; relationshipJobPerSec: number | undefined;
deliverJobMaxAttempts: number | undefined; deliverJobMaxAttempts: number | undefined;
inboxJobMaxAttempts: number | undefined; inboxJobMaxAttempts: number | undefined;
proxyRemoteFiles: boolean | undefined; proxyRemoteFiles: boolean | undefined;
@ -241,10 +241,10 @@ export function loadConfig(): Config {
outgoingAddressFamily: config.outgoingAddressFamily, outgoingAddressFamily: config.outgoingAddressFamily,
deliverJobConcurrency: config.deliverJobConcurrency, deliverJobConcurrency: config.deliverJobConcurrency,
inboxJobConcurrency: config.inboxJobConcurrency, inboxJobConcurrency: config.inboxJobConcurrency,
relashionshipJobConcurrency: config.relashionshipJobConcurrency, relationshipJobConcurrency: config.relationshipJobConcurrency,
deliverJobPerSec: config.deliverJobPerSec, deliverJobPerSec: config.deliverJobPerSec,
inboxJobPerSec: config.inboxJobPerSec, inboxJobPerSec: config.inboxJobPerSec,
relashionshipJobPerSec: config.relashionshipJobPerSec, relationshipJobPerSec: config.relationshipJobPerSec,
deliverJobMaxAttempts: config.deliverJobMaxAttempts, deliverJobMaxAttempts: config.deliverJobMaxAttempts,
inboxJobMaxAttempts: config.inboxJobMaxAttempts, inboxJobMaxAttempts: config.inboxJobMaxAttempts,
proxyRemoteFiles: config.proxyRemoteFiles, proxyRemoteFiles: config.proxyRemoteFiles,

View file

@ -286,9 +286,9 @@ export class QueueProcessorService implements OnApplicationShutdown {
}, { }, {
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP), ...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
autorun: false, autorun: false,
concurrency: this.config.relashionshipJobConcurrency ?? 16, concurrency: this.config.relationshipJobConcurrency ?? 16,
limiter: { limiter: {
max: this.config.relashionshipJobPerSec ?? 64, max: this.config.relationshipJobPerSec ?? 64,
duration: 1000, duration: 1000,
}, },
}); });

View file

@ -93,6 +93,13 @@ windowRouter.addListener('push', ctx => {
history.value.push({ path: ctx.path, key: ctx.key }); 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); provide('router', windowRouter);
provideMetadataReceiver((info) => { provideMetadataReceiver((info) => {
pageMetadata.value = info; pageMetadata.value = info;

View file

@ -4,6 +4,7 @@
*/ */
import { App, AsyncComponentLoader, defineAsyncComponent, provide } from 'vue'; import { App, AsyncComponentLoader, defineAsyncComponent, provide } from 'vue';
import type { RouteDef } from '@/nirax.js';
import { IRouter, Router } from '@/nirax.js'; import { IRouter, Router } from '@/nirax.js';
import { $i, iAmModerator } from '@/account.js'; import { $i, iAmModerator } from '@/account.js';
import MkLoading from '@/pages/_loading_.vue'; import MkLoading from '@/pages/_loading_.vue';
@ -16,7 +17,7 @@ const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
errorComponent: MkError, errorComponent: MkError,
}); });
const routes = [{ const routes: RouteDef[] = [{
path: '/@:initUser/pages/:initPageName/view-source', path: '/@:initUser/pages/:initPageName/view-source',
component: page(() => import('@/pages/page-editor/page-editor.vue')), component: page(() => import('@/pages/page-editor/page-editor.vue')),
}, { }, {
@ -333,8 +334,7 @@ const routes = [{
component: page(() => import('@/pages/registry.vue')), component: page(() => import('@/pages/registry.vue')),
}, { }, {
path: '/install-extentions', path: '/install-extentions',
// Note: This path is kept for compatibility. It may be deleted. redirect: '/install-extensions',
component: page(() => import('@/pages/install-extensions.vue')),
loginRequired: true, loginRequired: true,
}, { }, {
path: '/install-extensions', path: '/install-extensions',
@ -557,6 +557,11 @@ const routes = [{
path: '/', path: '/',
component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')), component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')),
globalCacheKey: 'index', globalCacheKey: 'index',
}, {
// テスト用リダイレクト設定。ログイン中ユーザのプロフィールにリダイレクトする
path: '/redirect-test',
redirect: $i ? `@${$i.username}` : '/',
loginRequired: true,
}, { }, {
path: '/:(*)', path: '/:(*)',
component: page(() => import('@/pages/not-found.vue')), component: page(() => import('@/pages/not-found.vue')),
@ -575,8 +580,6 @@ export function setupRouter(app: App) {
const mainRouter = createRouterImpl(location.pathname + location.search + location.hash); const mainRouter = createRouterImpl(location.pathname + location.search + location.hash);
window.history.replaceState({ key: mainRouter.getCurrentKey() }, '', location.href);
window.addEventListener('popstate', (event) => { window.addEventListener('popstate', (event) => {
mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key); mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
}); });
@ -585,5 +588,11 @@ export function setupRouter(app: App) {
window.history.pushState({ key: ctx.key }, '', ctx.path); window.history.pushState({ key: ctx.key }, '', ctx.path);
}); });
mainRouter.addListener('replace', ctx => {
window.history.replaceState({ key: ctx.key }, '', ctx.path);
});
mainRouter.init();
setMainRouter(mainRouter); setMainRouter(mainRouter);
} }

View file

@ -9,16 +9,25 @@ import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import { safeURIDecode } from '@/scripts/safe-uri-decode.js'; import { safeURIDecode } from '@/scripts/safe-uri-decode.js';
export type RouteDef = { interface RouteDefBase {
path: string; path: string;
component: Component;
query?: Record<string, string>; query?: Record<string, string>;
loginRequired?: boolean; loginRequired?: boolean;
name?: string; name?: string;
hash?: string; hash?: string;
globalCacheKey?: string; globalCacheKey?: string;
children?: RouteDef[]; 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 | { type ParsedPath = (string | {
name: string; name: string;
@ -48,7 +57,19 @@ export type RouterEvent = {
same: () => void; 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 { function parsePath(path: string): ParsedPath {
const res = [] as ParsedPath; const res = [] as ParsedPath;
@ -81,6 +102,11 @@ export interface IRouter extends EventEmitter<RouterEvent> {
currentRoute: ShallowRef<RouteDef>; currentRoute: ShallowRef<RouteDef>;
navHook: ((path: string, flag?: any) => boolean) | null; navHook: ((path: string, flag?: any) => boolean) | null;
/**
* eventListenerの定義後に必ず呼び出すこと
*/
init(): void;
resolve(path: string): Resolved | null; resolve(path: string): Resolved | null;
getCurrentPath(): any; getCurrentPath(): any;
@ -156,12 +182,13 @@ export interface IRouter extends EventEmitter<RouterEvent> {
export class Router extends EventEmitter<RouterEvent> implements IRouter { export class Router extends EventEmitter<RouterEvent> implements IRouter {
private routes: RouteDef[]; private routes: RouteDef[];
public current: Resolved; public current: Resolved;
public currentRef: ShallowRef<Resolved> = shallowRef(); public currentRef: ShallowRef<Resolved>;
public currentRoute: ShallowRef<RouteDef> = shallowRef(); public currentRoute: ShallowRef<RouteDef>;
private currentPath: string; private currentPath: string;
private isLoggedIn: boolean; private isLoggedIn: boolean;
private notFoundPageComponent: Component; private notFoundPageComponent: Component;
private currentKey = Date.now().toString(); private currentKey = Date.now().toString();
private redirectCount = 0;
public navHook: ((path: string, flag?: any) => boolean) | null = null; public navHook: ((path: string, flag?: any) => boolean) | null = null;
@ -169,13 +196,24 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
super(); super();
this.routes = routes; this.routes = routes;
this.current = this.resolve(currentPath)!;
this.currentRef = shallowRef(this.current);
this.currentRoute = shallowRef(this.current.route);
this.currentPath = currentPath; this.currentPath = currentPath;
this.isLoggedIn = isLoggedIn; this.isLoggedIn = isLoggedIn;
this.notFoundPageComponent = notFoundPageComponent; 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 { public resolve(path: string): Resolved | null {
const fullPath = path;
let queryString: string | null = null; let queryString: string | null = null;
let hash: string | null = null; let hash: string | null = null;
if (path[0] === '/') path = path.substring(1); if (path[0] === '/') path = path.substring(1);
@ -188,6 +226,12 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
path = path.substring(0, path.indexOf('?')); path = path.substring(0, path.indexOf('?'));
} }
const _parsedRoute = {
fullPath,
queryString,
hash,
};
if (_DEV_) console.log('Routing: ', path, queryString); if (_DEV_) console.log('Routing: ', path, queryString);
function check(routes: RouteDef[], _parts: string[]): Resolved | null { function check(routes: RouteDef[], _parts: string[]): Resolved | null {
@ -238,6 +282,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
route, route,
props, props,
child, child,
_parsedRoute,
}; };
} else { } else {
continue forEachRouteLoop; continue forEachRouteLoop;
@ -263,6 +308,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
return { return {
route, route,
props, props,
_parsedRoute,
}; };
} else { } else {
if (route.children) { if (route.children) {
@ -272,6 +318,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
route, route,
props, props,
child, child,
_parsedRoute,
}; };
} else { } else {
continue forEachRouteLoop; continue forEachRouteLoop;
@ -290,7 +337,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
return check(this.routes, _parts); 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; const beforePath = this.currentPath;
this.currentPath = path; this.currentPath = path;
@ -300,6 +347,20 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
throw new Error('no route found for: ' + path); 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) { if (res.route.loginRequired && !this.isLoggedIn) {
res.route.component = this.notFoundPageComponent; res.route.component = this.notFoundPageComponent;
res.props.set('showLoginPopup', true); res.props.set('showLoginPopup', true);
@ -321,7 +382,11 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
}); });
} }
return res; this.redirectCount = 0;
return {
...res,
redirected: _redirected,
};
} }
public getCurrentPath() { public getCurrentPath() {
@ -345,7 +410,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
const res = this.navigate(path, null); const res = this.navigate(path, null);
this.emit('push', { this.emit('push', {
beforePath, beforePath,
path, path: res._parsedRoute.fullPath,
route: res.route, route: res.route,
props: res.props, props: res.props,
key: this.currentKey, key: this.currentKey,
@ -353,7 +418,11 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
} }
public replace(path: string, key?: string | null) { 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,
});
} }
} }