diff options
| author | かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> | 2025-05-10 07:58:26 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-10 07:58:26 +0900 |
| commit | e1cd7c94fb13f8e49667b17554d22ce8de627a2a (patch) | |
| tree | a064c4b0937160cf1e26697dcfe18de8f2eb0144 /packages/frontend/src/use/use-note-capture.ts | |
| parent | Bump version to 2025.5.1-alpha.0 (diff) | |
| download | misskey-e1cd7c94fb13f8e49667b17554d22ce8de627a2a.tar.gz misskey-e1cd7c94fb13f8e49667b17554d22ce8de627a2a.tar.bz2 misskey-e1cd7c94fb13f8e49667b17554d22ce8de627a2a.zip | |
refactor(frontend): use* 関数の格納場所のフォルダ名を composables に変更 (#16004)
* refactor(frontend): use* 関数の格納場所を正式名称(composables)に変更
* migrate
* move useLoading
Diffstat (limited to 'packages/frontend/src/use/use-note-capture.ts')
| -rw-r--r-- | packages/frontend/src/use/use-note-capture.ts | 283 |
1 files changed, 0 insertions, 283 deletions
diff --git a/packages/frontend/src/use/use-note-capture.ts b/packages/frontend/src/use/use-note-capture.ts deleted file mode 100644 index 2f33c25a0a..0000000000 --- a/packages/frontend/src/use/use-note-capture.ts +++ /dev/null @@ -1,283 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { onUnmounted } from 'vue'; -import * as Misskey from 'misskey-js'; -import { EventEmitter } from 'eventemitter3'; -import type { Reactive, Ref } from 'vue'; -import { useStream } from '@/stream.js'; -import { $i } from '@/i.js'; -import { store } from '@/store.js'; -import { misskeyApi } from '@/utility/misskey-api.js'; -import { prefer } from '@/preferences.js'; -import { globalEvents } from '@/events.js'; - -export const noteEvents = new EventEmitter<{ - [ev: `reacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void; - [ev: `unreacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void; - [ev: `pollVoted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; choice: string; }) => void; -}>(); - -const fetchEvent = new EventEmitter<{ - [id: string]: Pick<Misskey.entities.Note, 'reactions' | 'reactionEmojis'>; -}>(); - -const pollingQueue = new Map<string, { - referenceCount: number; - lastAddedAt: number; -}>(); - -function pollingEnqueue(note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>) { - if (pollingQueue.has(note.id)) { - const data = pollingQueue.get(note.id)!; - pollingQueue.set(note.id, { - ...data, - referenceCount: data.referenceCount + 1, - lastAddedAt: Date.now(), - }); - } else { - pollingQueue.set(note.id, { - referenceCount: 1, - lastAddedAt: Date.now(), - }); - } -} - -function pollingDequeue(note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>) { - const data = pollingQueue.get(note.id); - if (data == null) return; - - if (data.referenceCount === 1) { - pollingQueue.delete(note.id); - } else { - pollingQueue.set(note.id, { - ...data, - referenceCount: data.referenceCount - 1, - }); - } -} - -const CAPTURE_MAX = 30; -const MIN_POLLING_INTERVAL = 1000 * 10; -const POLLING_INTERVAL = - prefer.s.pollingInterval === 1 ? MIN_POLLING_INTERVAL * 1.5 * 1.5 : - prefer.s.pollingInterval === 2 ? MIN_POLLING_INTERVAL * 1.5 : - prefer.s.pollingInterval === 3 ? MIN_POLLING_INTERVAL : - MIN_POLLING_INTERVAL; - -window.setInterval(() => { - const ids = [...pollingQueue.entries()] - .filter(([k, v]) => Date.now() - v.lastAddedAt < 1000 * 60 * 5) // 追加されてから一定時間経過したものは省く - .map(([k, v]) => k) - .sort((a, b) => (a > b ? -1 : 1)) // 新しいものを優先するためにIDで降順ソート - .slice(0, CAPTURE_MAX); - - if (ids.length === 0) return; - if (window.document.hidden) return; - - // まとめてリクエストするのではなく、個別にHTTPリクエスト投げてCDNにキャッシュさせた方がサーバーの負荷低減には良いかもしれない? - misskeyApi('notes/show-partial-bulk', { - noteIds: ids, - }).then((items) => { - for (const item of items) { - fetchEvent.emit(item.id, { - reactions: item.reactions, - reactionEmojis: item.reactionEmojis, - }); - } - }); -}, POLLING_INTERVAL); - -function pollingSubscribe(props: { - note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>; - $note: ReactiveNoteData; -}) { - const { note, $note } = props; - - function onFetched(data: Pick<Misskey.entities.Note, 'reactions' | 'reactionEmojis'>): void { - $note.reactions = data.reactions; - $note.reactionCount = Object.values(data.reactions).reduce((a, b) => a + b, 0); - $note.reactionEmojis = data.reactionEmojis; - } - - pollingEnqueue(note); - fetchEvent.on(note.id, onFetched); - - onUnmounted(() => { - pollingDequeue(note); - fetchEvent.off(note.id, onFetched); - }); -} - -function realtimeSubscribe(props: { - note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>; -}): void { - const note = props.note; - const connection = useStream(); - - function onStreamNoteUpdated(noteData): void { - const { type, id, body } = noteData; - - if (id !== note.id) return; - - switch (type) { - case 'reacted': { - noteEvents.emit(`reacted:${id}`, { - userId: body.userId, - reaction: body.reaction, - emoji: body.emoji, - }); - break; - } - - case 'unreacted': { - noteEvents.emit(`unreacted:${id}`, { - userId: body.userId, - reaction: body.reaction, - emoji: body.emoji, - }); - break; - } - - case 'pollVoted': { - noteEvents.emit(`pollVoted:${id}`, { - userId: body.userId, - choice: body.choice, - }); - break; - } - - case 'deleted': { - globalEvents.emit('noteDeleted', id); - break; - } - } - } - - function capture(withHandler = false): void { - connection.send('sr', { id: note.id }); - if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated); - } - - function decapture(withHandler = false): void { - connection.send('un', { id: note.id }); - if (withHandler) connection.off('noteUpdated', onStreamNoteUpdated); - } - - function onStreamConnected() { - capture(false); - } - - capture(true); - connection.on('_connected_', onStreamConnected); - - onUnmounted(() => { - decapture(true); - connection.off('_connected_', onStreamConnected); - }); -} - -type ReactiveNoteData = Reactive<{ - reactions: Misskey.entities.Note['reactions']; - reactionCount: Misskey.entities.Note['reactionCount']; - reactionEmojis: Misskey.entities.Note['reactionEmojis']; - myReaction: Misskey.entities.Note['myReaction']; - pollChoices: NonNullable<Misskey.entities.Note['poll']>['choices']; -}>; - -export function useNoteCapture(props: { - note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>; - parentNote: Misskey.entities.Note | null; - $note: ReactiveNoteData; -}) { - const { note, parentNote, $note } = props; - - noteEvents.on(`reacted:${note.id}`, onReacted); - noteEvents.on(`unreacted:${note.id}`, onUnreacted); - noteEvents.on(`pollVoted:${note.id}`, onPollVoted); - - let latestReactedKey: string | null = null; - let latestUnreactedKey: string | null = null; - let latestPollVotedKey: string | null = null; - - function onReacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }): void { - const newReactedKey = `${ctx.userId}:${ctx.reaction}`; - if (newReactedKey === latestReactedKey) return; - latestReactedKey = newReactedKey; - - if (ctx.emoji && !(ctx.emoji.name in $note.reactionEmojis)) { - $note.reactionEmojis[ctx.emoji.name] = ctx.emoji.url; - } - - const currentCount = $note.reactions[ctx.reaction] || 0; - - $note.reactions[ctx.reaction] = currentCount + 1; - $note.reactionCount += 1; - - if ($i && (ctx.userId === $i.id)) { - $note.myReaction = ctx.reaction; - } - } - - function onUnreacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }): void { - const newUnreactedKey = `${ctx.userId}:${ctx.reaction}`; - if (newUnreactedKey === latestUnreactedKey) return; - latestUnreactedKey = newUnreactedKey; - - const currentCount = $note.reactions[ctx.reaction] || 0; - - $note.reactions[ctx.reaction] = Math.max(0, currentCount - 1); - $note.reactionCount = Math.max(0, $note.reactionCount - 1); - if ($note.reactions[ctx.reaction] === 0) delete $note.reactions[ctx.reaction]; - - if ($i && (ctx.userId === $i.id)) { - $note.myReaction = null; - } - } - - function onPollVoted(ctx: { userId: Misskey.entities.User['id']; choice: string; }): void { - const newPollVotedKey = `${ctx.userId}:${ctx.choice}`; - if (newPollVotedKey === latestPollVotedKey) return; - latestPollVotedKey = newPollVotedKey; - - const choices = [...$note.pollChoices]; - choices[ctx.choice] = { - ...choices[ctx.choice], - votes: choices[ctx.choice].votes + 1, - ...($i && (ctx.userId === $i.id) ? { - isVoted: true, - } : {}), - }; - - $note.pollChoices = choices; - } - - onUnmounted(() => { - noteEvents.off(`reacted:${note.id}`, onReacted); - noteEvents.off(`unreacted:${note.id}`, onUnreacted); - noteEvents.off(`pollVoted:${note.id}`, onPollVoted); - }); - - // 投稿からある程度経過している(=タイムラインを遡って表示した)ノートは、イベントが発生する可能性が低いためそもそも購読しない - // ただし「リノートされたばかりの過去のノート」(= parentNoteが存在し、かつparentNoteの投稿日時が最近)はイベント発生が考えられるため購読する - // TODO: デバイスとサーバーの時計がズレていると不具合の元になるため、ズレを検知して警告を表示するなどのケアが必要かもしれない - if (parentNote == null) { - if ((Date.now() - new Date(note.createdAt).getTime()) > 1000 * 60 * 5) { // 5min - // リノートで表示されているノートでもないし、投稿からある程度経過しているので購読しない - return; - } - } else { - if ((Date.now() - new Date(parentNote.createdAt).getTime()) > 1000 * 60 * 5) { // 5min - // リノートで表示されているノートだが、リノートされてからある程度経過しているので購読しない - return; - } - } - - if ($i && store.s.realtimeMode) { - realtimeSubscribe(props); - } else { - pollingSubscribe(props); - } -} |