diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f900509..c80c21d04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ ### Client - Fix: サーバーメトリクスが90度傾いている - Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正 +- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるようになりました + +### Server +- JSON.parse の回数を削減することで、ストリーミングのパフォーマンスを向上しました ## 13.13.2 diff --git a/locales/index.d.ts b/locales/index.d.ts index af6b80327..dea00d783 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -139,8 +139,10 @@ export interface Locale { "suspendConfirm": string; "unsuspendConfirm": string; "selectList": string; + "editList": string; "selectChannel": string; "selectAntenna": string; + "editAntenna": string; "selectWidget": string; "editWidgets": string; "editWidgetsExit": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e7202bfbb..d9d227a0b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -136,8 +136,10 @@ unblockConfirm: "ブロック解除しますか?" suspendConfirm: "凍結しますか?" unsuspendConfirm: "解凍しますか?" selectList: "リストを選択" +editList: "リストを編集" selectChannel: "チャンネルを選択" selectAntenna: "アンテナを選択" +editAntenna: "アンテナを編集" selectWidget: "ウィジェットを選択" editWidgets: "ウィジェットを編集" editWidgetsExit: "編集を終了" diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 5b7359074..48ff00c8c 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; -import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; +import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; import type * as Bull from 'bullmq'; @@ -69,7 +69,7 @@ export class QueueService { if (content == null) return null; if (to == null) return null; - const data = { + const data: DeliverJobData = { user: { id: user.id, }, @@ -88,6 +88,38 @@ export class QueueService { }); } + /** + * ApDeliverManager-DeliverManager.execute()からinboxesを突っ込んでaddBulkしたい + * @param user `{ id: string; }` この関数ではThinUserに変換しないので前もって変換してください + * @param content IActivity | null + * @param inboxes `Map` / key: to (inbox url), value: isSharedInbox (whether it is sharedInbox) + * @returns void + */ + @bindThis + public async deliverMany(user: ThinUser, content: IActivity | null, inboxes: Map) { + const opts = { + attempts: this.config.deliverJobMaxAttempts ?? 12, + backoff: { + type: 'custom', + }, + removeOnComplete: true, + removeOnFail: true, + }; + + await this.deliverQueue.addBulk(Array.from(inboxes.entries()).map(d => ({ + name: d[0], + data: { + user, + content, + to: d[0], + isSharedInbox: d[1], + } as DeliverJobData, + opts, + }))); + + return; + } + @bindThis public inbox(activity: IActivity, signature: httpSignature.IParsedSignature) { const data = { diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index 62a2a33a1..6e910eb53 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -7,6 +7,8 @@ import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; import { QueueService } from '@/core/QueueService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import type { IActivity } from '@/core/activitypub/type.js'; +import { ThinUser } from '@/queue/types.js'; interface IRecipe { type: string; @@ -21,10 +23,10 @@ interface IDirectRecipe extends IRecipe { to: RemoteUser; } -const isFollowers = (recipe: any): recipe is IFollowersRecipe => +const isFollowers = (recipe: IRecipe): recipe is IFollowersRecipe => recipe.type === 'Followers'; -const isDirect = (recipe: any): recipe is IDirectRecipe => +const isDirect = (recipe: IRecipe): recipe is IDirectRecipe => recipe.type === 'Direct'; @Injectable() @@ -46,11 +48,11 @@ export class ApDeliverManagerService { /** * Deliver activity to followers + * @param actor * @param activity Activity - * @param from Followee */ @bindThis - public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: any) { + public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: IActivity) { const manager = new DeliverManager( this.userEntityService, this.followingsRepository, @@ -64,11 +66,12 @@ export class ApDeliverManagerService { /** * Deliver activity to user + * @param actor * @param activity Activity * @param to Target user */ @bindThis - public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: any, to: RemoteUser) { + public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: IActivity, to: RemoteUser) { const manager = new DeliverManager( this.userEntityService, this.followingsRepository, @@ -81,7 +84,7 @@ export class ApDeliverManagerService { } @bindThis - public createDeliverManager(actor: { id: User['id']; host: null; }, activity: any) { + public createDeliverManager(actor: { id: User['id']; host: null; }, activity: IActivity | null) { return new DeliverManager( this.userEntityService, this.followingsRepository, @@ -94,12 +97,15 @@ export class ApDeliverManagerService { } class DeliverManager { - private actor: { id: User['id']; host: null; }; - private activity: any; + private actor: ThinUser; + private activity: IActivity | null; private recipes: IRecipe[] = []; /** * Constructor + * @param userEntityService + * @param followingsRepository + * @param queueService * @param actor Actor * @param activity Activity to deliver */ @@ -109,9 +115,15 @@ class DeliverManager { private queueService: QueueService, actor: { id: User['id']; host: null; }, - activity: any, + activity: IActivity | null, ) { - this.actor = actor; + // 型で弾いてはいるが一応ローカルユーザーかチェック + if (actor.host != null) throw new Error('actor.host must be null'); + + // パフォーマンス向上のためキューに突っ込むのはidのみに絞る + this.actor = { + id: actor.id, + }; this.activity = activity; } @@ -155,9 +167,8 @@ class DeliverManager { */ @bindThis public async execute() { - if (!this.userEntityService.isLocalUser(this.actor)) return; - // The value flags whether it is shared or not. + // key: inbox URL, value: whether it is sharedInbox const inboxes = new Map(); /* @@ -201,9 +212,6 @@ class DeliverManager { .forEach(recipe => inboxes.set(recipe.to.inbox!, false)); // deliver - for (const inbox of inboxes) { - // inbox[0]: inbox, inbox[1]: whether it is sharedInbox - this.queueService.deliver(this.actor, this.activity, inbox[0], inbox[1]); - } + this.queueService.deliverMany(this.actor, this.activity, inboxes); } } diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 4a0342d2b..e4291becf 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -103,6 +103,13 @@ export class StreamingApiServerService { }); }); + const globalEv = new EventEmitter(); + + this.redisForSub.on('message', (_: string, data: string) => { + const parsed = JSON.parse(data); + globalEv.emit('message', parsed); + }); + this.#wss.on('connection', async (connection: WebSocket.WebSocket, request: http.IncomingMessage, ctx: { stream: MainStreamConnection, user: LocalUser | null; @@ -112,12 +119,11 @@ export class StreamingApiServerService { const ev = new EventEmitter(); - async function onRedisMessage(_: string, data: string): Promise { - const parsed = JSON.parse(data); - ev.emit(parsed.channel, parsed.message); + function onRedisMessage(data: any): void { + ev.emit(data.channel, data.message); } - this.redisForSub.on('message', onRedisMessage); + globalEv.on('message', onRedisMessage); await stream.listen(ev, connection); @@ -137,7 +143,7 @@ export class StreamingApiServerService { connection.once('close', () => { ev.removeAllListeners(); stream.dispose(); - this.redisForSub.off('message', onRedisMessage); + globalEv.off('message', onRedisMessage); this.#connections.delete(connection); if (userUpdateIntervalId) clearInterval(userUpdateIntervalId); }); diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 598529bf5..b361d568e 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -21,14 +21,14 @@
- + {{ i18n.ts.loadMore }}
- +
- + {{ i18n.ts.loadMore }} @@ -50,6 +50,7 @@ import { i18n } from '@/i18n'; const SECOND_FETCH_LIMIT = 30; const TOLERANCE = 16; +const APPEAR_MINIMUM_INTERVAL = 600; export type Paging = { endpoint: E; @@ -71,6 +72,16 @@ export type Paging pageEl?: HTMLElement; }; + +type MisskeyEntityMap = Map; + +function arrayToEntries(entities: MisskeyEntity[]): [string, MisskeyEntity][] { + return entities.map(en => [en.id, en]); +} + +function concatMapWithArray(map: MisskeyEntityMap, entities: MisskeyEntity[]): MisskeyEntityMap { + return new Map([...map, ...arrayToEntries(entities)]); +} diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index 3bc5ee972..9cf96d3d0 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -75,7 +75,7 @@ const pagination = { }; function resolved(reportId) { - reports.removeItem(item => item.id === reportId); + reports.removeItem(reportId); } const headerActions = $computed(() => []); diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index 3da6a0d9c..901fbc1ee 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -144,7 +144,7 @@ const edit = (emoji) => { ...result.updated, })); } else if (result.deleted) { - emojisPaginationComponent.value.removeItem((item) => item.id === emoji.id); + emojisPaginationComponent.value.removeItem(emoji.id); } }, }, 'closed'); diff --git a/packages/frontend/src/scripts/clone.ts b/packages/frontend/src/scripts/clone.ts index 16fad2412..cf8fa64ba 100644 --- a/packages/frontend/src/scripts/clone.ts +++ b/packages/frontend/src/scripts/clone.ts @@ -1,5 +1,7 @@ // structredCloneが遅いため // SEE: http://var.blog.jp/archives/86038606.html +// あと、Vue RefをIndexedDBに保存しようとしてstructredCloneを使ったらエラーになった +// https://github.com/misskey-dev/misskey/pull/8098#issuecomment-1114144045 type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[]; diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue index d21a9cc58..a1ca32724 100644 --- a/packages/frontend/src/ui/deck/antenna-column.vue +++ b/packages/frontend/src/ui/deck/antenna-column.vue @@ -44,11 +44,22 @@ async function setAntenna() { }); } -const menu = [{ - icon: 'ti ti-pencil', - text: i18n.ts.selectAntenna, - action: setAntenna, -}]; +function editAntenna() { + os.pageWindow('my/antennas/' + props.column.antennaId); +} + +const menu = [ + { + icon: 'ti ti-pencil', + text: i18n.ts.selectAntenna, + action: setAntenna, + }, + { + icon: 'ti ti-settings', + text: i18n.ts.editAntenna, + action: editAntenna, + }, +]; /* function focus() { diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index f36dc6151..3d6256c4f 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -42,9 +42,20 @@ async function setList() { }); } -const menu = [{ - icon: 'ti ti-pencil', - text: i18n.ts.selectList, - action: setList, -}]; +function editList() { + os.pageWindow('my/lists/' + props.column.listId); +} + +const menu = [ + { + icon: 'ti ti-pencil', + text: i18n.ts.selectList, + action: setList, + }, + { + icon: 'ti ti-settings', + text: i18n.ts.editList, + action: editList, + }, +];