diff options
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/api/common/read-messaging-message.ts | 2 | ||||
| -rw-r--r-- | src/server/api/endpoints/antennas/update.ts | 2 | ||||
| -rw-r--r-- | src/server/api/stream/channels/antenna.ts | 11 | ||||
| -rw-r--r-- | src/server/api/stream/channels/channel.ts | 3 | ||||
| -rw-r--r-- | src/server/api/stream/channels/main.ts | 24 | ||||
| -rw-r--r-- | src/server/api/stream/channels/messaging.ts | 6 | ||||
| -rw-r--r-- | src/server/api/stream/index.ts | 35 | ||||
| -rw-r--r-- | src/server/api/stream/types.ts | 299 |
8 files changed, 340 insertions, 42 deletions
diff --git a/src/server/api/common/read-messaging-message.ts b/src/server/api/common/read-messaging-message.ts index 1dce76d2a9..33f41b2770 100644 --- a/src/server/api/common/read-messaging-message.ts +++ b/src/server/api/common/read-messaging-message.ts @@ -77,7 +77,7 @@ export async function readGroupMessagingMessage( id: In(messageIds) }); - const reads = []; + const reads: MessagingMessage['id'][] = []; for (const message of messages) { if (message.userId === userId) continue; diff --git a/src/server/api/endpoints/antennas/update.ts b/src/server/api/endpoints/antennas/update.ts index ff13e89bcc..d69b4feee6 100644 --- a/src/server/api/endpoints/antennas/update.ts +++ b/src/server/api/endpoints/antennas/update.ts @@ -137,7 +137,7 @@ export default define(meta, async (ps, user) => { notify: ps.notify, }); - publishInternalEvent('antennaUpdated', Antennas.findOneOrFail(antenna.id)); + publishInternalEvent('antennaUpdated', await Antennas.findOneOrFail(antenna.id)); return await Antennas.pack(antenna.id); }); diff --git a/src/server/api/stream/channels/antenna.ts b/src/server/api/stream/channels/antenna.ts index bf9c53c453..3cbdfebb43 100644 --- a/src/server/api/stream/channels/antenna.ts +++ b/src/server/api/stream/channels/antenna.ts @@ -3,6 +3,7 @@ import Channel from '../channel'; import { Notes } from '@/models/index'; import { isMutedUserRelated } from '@/misc/is-muted-user-related'; import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; +import { StreamMessages } from '../types'; export default class extends Channel { public readonly chName = 'antenna'; @@ -19,11 +20,9 @@ export default class extends Channel { } @autobind - private async onEvent(data: any) { - const { type, body } = data; - - if (type === 'note') { - const note = await Notes.pack(body.id, this.user, { detail: true }); + private async onEvent(data: StreamMessages['antenna']['payload']) { + if (data.type === 'note') { + const note = await Notes.pack(data.body.id, this.user, { detail: true }); // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isMutedUserRelated(note, this.muting)) return; @@ -34,7 +33,7 @@ export default class extends Channel { this.send('note', note); } else { - this.send(type, body); + this.send(data.type, data.body); } } diff --git a/src/server/api/stream/channels/channel.ts b/src/server/api/stream/channels/channel.ts index 72ddbf93b4..bf7942f522 100644 --- a/src/server/api/stream/channels/channel.ts +++ b/src/server/api/stream/channels/channel.ts @@ -4,6 +4,7 @@ import { Notes, Users } from '@/models/index'; import { isMutedUserRelated } from '@/misc/is-muted-user-related'; import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; import { User } from '@/models/entities/user'; +import { StreamMessages } from '../types'; import { Packed } from '@/misc/schema'; export default class extends Channel { @@ -52,7 +53,7 @@ export default class extends Channel { } @autobind - private onEvent(data: any) { + private onEvent(data: StreamMessages['channel']['payload']) { if (data.type === 'typing') { const id = data.body; const begin = this.typers[id] == null; diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts index b99cb931da..131ac30472 100644 --- a/src/server/api/stream/channels/main.ts +++ b/src/server/api/stream/channels/main.ts @@ -11,35 +11,33 @@ export default class extends Channel { public async init(params: any) { // Subscribe main stream channel this.subscriber.on(`mainStream:${this.user!.id}`, async data => { - const { type } = data; - let { body } = data; - - switch (type) { + switch (data.type) { case 'notification': { - if (this.muting.has(body.userId)) return; - if (body.note && body.note.isHidden) { - const note = await Notes.pack(body.note.id, this.user, { + if (data.body.userId && this.muting.has(data.body.userId)) return; + + if (data.body.note && data.body.note.isHidden) { + const note = await Notes.pack(data.body.note.id, this.user, { detail: true }); this.connection.cacheNote(note); - body.note = note; + data.body.note = note; } break; } case 'mention': { - if (this.muting.has(body.userId)) return; - if (body.isHidden) { - const note = await Notes.pack(body.id, this.user, { + if (this.muting.has(data.body.userId)) return; + if (data.body.isHidden) { + const note = await Notes.pack(data.body.id, this.user, { detail: true }); this.connection.cacheNote(note); - body = note; + data.body = note; } break; } } - this.send(type, body); + this.send(data.type, data.body); }); } } diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts index 015b0a7650..c049e880b9 100644 --- a/src/server/api/stream/channels/messaging.ts +++ b/src/server/api/stream/channels/messaging.ts @@ -3,6 +3,8 @@ import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivit import Channel from '../channel'; import { UserGroupJoinings, Users, MessagingMessages } from '@/models/index'; import { User, ILocalUser, IRemoteUser } from '@/models/entities/user'; +import { UserGroup } from '@/models/entities/user-group'; +import { StreamMessages } from '../types'; export default class extends Channel { public readonly chName = 'messaging'; @@ -12,7 +14,7 @@ export default class extends Channel { private otherpartyId: string | null; private otherparty: User | null; private groupId: string | null; - private subCh: string; + private subCh: `messagingStream:${User['id']}-${User['id']}` | `messagingStream:${UserGroup['id']}`; private typers: Record<User['id'], Date> = {}; private emitTypersIntervalId: ReturnType<typeof setInterval>; @@ -45,7 +47,7 @@ export default class extends Channel { } @autobind - private onEvent(data: any) { + private onEvent(data: StreamMessages['messaging']['payload'] | StreamMessages['groupMessaging']['payload']) { if (data.type === 'typing') { const id = data.body; const begin = this.typers[id] == null; diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index ccd555e149..da4ea5ec99 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -14,6 +14,7 @@ import { AccessToken } from '@/models/entities/access-token'; import { UserProfile } from '@/models/entities/user-profile'; import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream'; import { UserGroup } from '@/models/entities/user-group'; +import { StreamEventEmitter, StreamMessages } from './types'; import { Packed } from '@/misc/schema'; /** @@ -28,7 +29,7 @@ export default class Connection { public followingChannels: Set<ChannelModel['id']> = new Set(); public token?: AccessToken; private wsConnection: websocket.connection; - public subscriber: EventEmitter; + public subscriber: StreamEventEmitter; private channels: Channel[] = []; private subscribingNotes: any = {}; private cachedNotes: Packed<'Note'>[] = []; @@ -46,8 +47,8 @@ export default class Connection { this.wsConnection.on('message', this.onWsConnectionMessage); - this.subscriber.on('broadcast', async ({ type, body }) => { - this.onBroadcastMessage(type, body); + this.subscriber.on('broadcast', data => { + this.onBroadcastMessage(data); }); if (this.user) { @@ -57,43 +58,41 @@ export default class Connection { this.updateFollowingChannels(); this.updateUserProfile(); - this.subscriber.on(`user:${this.user.id}`, ({ type, body }) => { - this.onUserEvent(type, body); - }); + this.subscriber.on(`user:${this.user.id}`, this.onUserEvent); } } @autobind - private onUserEvent(type: string, body: any) { - switch (type) { + private onUserEvent(data: StreamMessages['user']['payload']) { // { type, body }と展開するとそれぞれ型が分離してしまう + switch (data.type) { case 'follow': - this.following.add(body.id); + this.following.add(data.body.id); break; case 'unfollow': - this.following.delete(body.id); + this.following.delete(data.body.id); break; case 'mute': - this.muting.add(body.id); + this.muting.add(data.body.id); break; case 'unmute': - this.muting.delete(body.id); + this.muting.delete(data.body.id); break; // TODO: block events case 'followChannel': - this.followingChannels.add(body.id); + this.followingChannels.add(data.body.id); break; case 'unfollowChannel': - this.followingChannels.delete(body.id); + this.followingChannels.delete(data.body.id); break; case 'updateUserProfile': - this.userProfile = body; + this.userProfile = data.body; break; case 'terminate': @@ -145,8 +144,8 @@ export default class Connection { } @autobind - private onBroadcastMessage(type: string, body: any) { - this.sendMessageToWs(type, body); + private onBroadcastMessage(data: StreamMessages['broadcast']['payload']) { + this.sendMessageToWs(data.type, data.body); } @autobind @@ -249,7 +248,7 @@ export default class Connection { } @autobind - private async onNoteStreamMessage(data: any) { + private async onNoteStreamMessage(data: StreamMessages['note']['payload']) { this.sendMessageToWs('noteUpdated', { id: data.body.id, type: data.type, diff --git a/src/server/api/stream/types.ts b/src/server/api/stream/types.ts new file mode 100644 index 0000000000..70eb5c5ce5 --- /dev/null +++ b/src/server/api/stream/types.ts @@ -0,0 +1,299 @@ +import { EventEmitter } from 'events'; +import Emitter from 'strict-event-emitter-types'; +import { Channel } from '@/models/entities/channel'; +import { User } from '@/models/entities/user'; +import { UserProfile } from '@/models/entities/user-profile'; +import { Note } from '@/models/entities/note'; +import { Antenna } from '@/models/entities/antenna'; +import { DriveFile } from '@/models/entities/drive-file'; +import { DriveFolder } from '@/models/entities/drive-folder'; +import { Emoji } from '@/models/entities/emoji'; +import { UserList } from '@/models/entities/user-list'; +import { MessagingMessage } from '@/models/entities/messaging-message'; +import { UserGroup } from '@/models/entities/user-group'; +import { ReversiGame } from '@/models/entities/games/reversi/game'; +import { AbuseUserReport } from '@/models/entities/abuse-user-report'; +import { Signin } from '@/models/entities/signin'; +import { Page } from '@/models/entities/page'; +import { Packed } from '@/misc/schema'; + +//#region Stream type-body definitions +export interface InternalStreamTypes { + antennaCreated: Antenna; + antennaDeleted: Antenna; + antennaUpdated: Antenna; +} + +export interface BroadcastTypes { + emojiAdded: { + emoji: Packed<'Emoji'>; + }; +} + +export interface UserStreamTypes { + terminate: {}; + followChannel: Channel; + unfollowChannel: Channel; + updateUserProfile: UserProfile; + mute: User; + unmute: User; + follow: Packed<'User'>; + unfollow: Packed<'User'>; + userAdded: Packed<'User'>; +} + +export interface MainStreamTypes { + notification: Packed<'Notification'>; + mention: Packed<'Note'>; + reply: Packed<'Note'>; + renote: Packed<'Note'>; + follow: Packed<'User'>; + followed: Packed<'User'>; + unfollow: Packed<'User'>; + meUpdated: Packed<'User'>; + pageEvent: { + pageId: Page['id']; + event: string; + var: any; + userId: User['id']; + user: Packed<'User'>; + }; + urlUploadFinished: { + marker?: string | null; + file: Packed<'DriveFile'>; + }; + readAllNotifications: undefined; + unreadNotification: Packed<'Notification'>; + unreadMention: Note['id']; + readAllUnreadMentions: undefined; + unreadSpecifiedNote: Note['id']; + readAllUnreadSpecifiedNotes: undefined; + readAllMessagingMessages: undefined; + messagingMessage: Packed<'MessagingMessage'>; + unreadMessagingMessage: Packed<'MessagingMessage'>; + readAllAntennas: undefined; + unreadAntenna: Antenna; + readAllAnnouncements: undefined; + readAllChannels: undefined; + unreadChannel: Note['id']; + myTokenRegenerated: undefined; + reversiNoInvites: undefined; + reversiInvited: Packed<'ReversiMatching'>; + signin: Signin; + registryUpdated: { + scope?: string[]; + key: string; + value: any | null; + }; + driveFileCreated: Packed<'DriveFile'>; + readAntenna: Antenna; +} + +export interface DriveStreamTypes { + fileCreated: Packed<'DriveFile'>; + fileDeleted: DriveFile['id']; + fileUpdated: Packed<'DriveFile'>; + folderCreated: Packed<'DriveFolder'>; + folderDeleted: DriveFolder['id']; + folderUpdated: Packed<'DriveFolder'>; +} + +export interface NoteStreamTypes { + pollVoted: { + choice: number; + userId: User['id']; + }; + deleted: { + deletedAt: Date; + }; + reacted: { + reaction: string; + emoji?: Emoji; + userId: User['id']; + }; + unreacted: { + reaction: string; + userId: User['id']; + }; +} +type NoteStreamEventTypes = { + [key in keyof NoteStreamTypes]: { + id: Note['id']; + body: NoteStreamTypes[key]; + }; +}; + +export interface ChannelStreamTypes { + typing: User['id']; +} + +export interface UserListStreamTypes { + userAdded: Packed<'User'>; + userRemoved: Packed<'User'>; +} + +export interface AntennaStreamTypes { + note: Note; +} + +export interface MessagingStreamTypes { + read: MessagingMessage['id'][]; + typing: User['id']; + message: Packed<'MessagingMessage'>; + deleted: MessagingMessage['id']; +} + +export interface GroupMessagingStreamTypes { + read: { + ids: MessagingMessage['id'][]; + userId: User['id']; + }; + typing: User['id']; + message: Packed<'MessagingMessage'>; + deleted: MessagingMessage['id']; +} + +export interface MessagingIndexStreamTypes { + read: MessagingMessage['id'][]; + message: Packed<'MessagingMessage'>; +} + +export interface ReversiStreamTypes { + matched: Packed<'ReversiGame'>; + invited: Packed<'ReversiMatching'>; +} + +export interface ReversiGameStreamTypes { + started: Packed<'ReversiGame'>; + ended: { + winnerId?: User['id'] | null, + game: Packed<'ReversiGame'>; + }; + updateSettings: { + key: string; + value: FIXME; + }; + initForm: { + userId: User['id']; + form: FIXME; + }; + updateForm: { + userId: User['id']; + id: string; + value: FIXME; + }; + message: { + userId: User['id']; + message: FIXME; + }; + changeAccepts: { + user1: boolean; + user2: boolean; + }; + set: { + at: Date; + color: boolean; + pos: number; + next: boolean; + }; + watching: User['id']; +} + +export interface AdminStreamTypes { + newAbuseUserReport: { + id: AbuseUserReport['id']; + targetUserId: User['id'], + reporterId: User['id'], + comment: string; + }; +} +//#endregion + +// 辞書(interface or type)から{ type, body }ユニオンを定義 +// https://stackoverflow.com/questions/49311989/can-i-infer-the-type-of-a-value-using-extends-keyof-type +// VS Codeの展開を防止するためにEvents型を定義 +type Events<T extends object> = { [K in keyof T]: { type: K; body: T[K]; } }; +type EventUnionFromDictionary< + T extends object, + U = Events<T> +> = U[keyof U]; + +// name/messages(spec) pairs dictionary +export type StreamMessages = { + internal: { + name: 'internal'; + payload: EventUnionFromDictionary<InternalStreamTypes>; + }; + broadcast: { + name: 'broadcast'; + payload: EventUnionFromDictionary<BroadcastTypes>; + }; + user: { + name: `user:${User['id']}`; + payload: EventUnionFromDictionary<UserStreamTypes>; + }; + main: { + name: `mainStream:${User['id']}`; + payload: EventUnionFromDictionary<MainStreamTypes>; + }; + drive: { + name: `driveStream:${User['id']}`; + payload: EventUnionFromDictionary<DriveStreamTypes>; + }; + note: { + name: `noteStream:${Note['id']}`; + payload: EventUnionFromDictionary<NoteStreamEventTypes>; + }; + channel: { + name: `channelStream:${Channel['id']}`; + payload: EventUnionFromDictionary<ChannelStreamTypes>; + }; + userList: { + name: `userListStream:${UserList['id']}`; + payload: EventUnionFromDictionary<UserListStreamTypes>; + }; + antenna: { + name: `antennaStream:${Antenna['id']}`; + payload: EventUnionFromDictionary<AntennaStreamTypes>; + }; + messaging: { + name: `messagingStream:${User['id']}-${User['id']}`; + payload: EventUnionFromDictionary<MessagingStreamTypes>; + }; + groupMessaging: { + name: `messagingStream:${UserGroup['id']}`; + payload: EventUnionFromDictionary<GroupMessagingStreamTypes>; + }; + messagingIndex: { + name: `messagingIndexStream:${User['id']}`; + payload: EventUnionFromDictionary<MessagingIndexStreamTypes>; + }; + reversi: { + name: `reversiStream:${User['id']}`; + payload: EventUnionFromDictionary<ReversiStreamTypes>; + }; + reversiGame: { + name: `reversiGameStream:${ReversiGame['id']}`; + payload: EventUnionFromDictionary<ReversiGameStreamTypes>; + }; + admin: { + name: `adminStream:${User['id']}`; + payload: EventUnionFromDictionary<AdminStreamTypes>; + }; + notes: { + name: 'notesStream'; + payload: Packed<'Note'>; + }; +}; + +// API event definitions +// ストリームごとのEmitterの辞書を用意 +type EventEmitterDictionary = { [x in keyof StreamMessages]: Emitter<EventEmitter, { [y in StreamMessages[x]['name']]: (e: StreamMessages[x]['payload']) => void }> }; +// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection +type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; +// Emitter辞書から共用体型を作り、UnionToIntersectionで交差型にする +export type StreamEventEmitter = UnionToIntersection<EventEmitterDictionary[keyof StreamMessages]>; +// { [y in name]: (e: spec) => void }をまとめてその交差型をEmitterにかけるとts(2590)にひっかかる + +// provide stream channels union +export type StreamChannels = StreamMessages[keyof StreamMessages]['name']; |