refactor: publishHogeStreamとStreamのEventEmitterに型定義する (#7769)
* wip * wip * wip * ✌️ * add main stream * packedNotificationSchemaを更新 * read:gallery, write:gallery, read:gallery-likes, write:gallery-likesに翻訳を追加 * fix * ok * add header, choice, invitation * add header, choice, invitation * test * fix * fix * yatta * remove no longer needed "as PackedUser/PackedNote" * clean up * add simple-schema * fix lint * fix lint * wip * wip! * wip * fix * wip * wip * ✌️ * 送信側に型エラーがないことを3回確認した * ✌️ * wip * update typescript * define items in full Schema * edit comment * edit comment * edit comment * Update src/prelude/types.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * https://github.com/misskey-dev/misskey/pull/7769#discussion_r703058458 * user packとnote packの型不整合を修正 * revert https://github.com/misskey-dev/misskey/pull/7772#discussion_r706627736 * revert https://github.com/misskey-dev/misskey/pull/7772#discussion_r706627736 * user packとnote packの型不整合を修正 * add prelude/types.ts * emoji * signin * game * matching * clean up * ev => data * refactor * clean up * add type * antenna * channel * fix * add Packed type * add PackedRef * fix lint * add emoji schema * add reversiGame * add reversiMatching * remove signin schema (use Signin entity) * add schemas refs, fix Packed type * wip PackedHoge => Packed<'Hoge'> * add Packed type * note-reaction * user * user-group * user-list * note * app, messaging-message * notification * drive-file * drive-folder * following * muting * blocking * hashtag * page * app (with modifying schema) * import user? * channel * antenna * clip * gallery-post * emoji * Packed * reversi-matching * update stream.ts * https://github.com/misskey-dev/misskey/pull/7769#issuecomment-917542339 * fix lint * clean up? * add changelog * add changelog * add changelog * fix: アンテナが既読にならないのを修正 * revert fix * https://github.com/misskey-dev/misskey/pull/7769#discussion_r711474875 * spec => payload * edit commetn Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
This commit is contained in:
		
							parent
							
								
									5ca6e6b5df
								
							
						
					
					
						commit
						69b56f6658
					
				
					 12 changed files with 383 additions and 60 deletions
				
			
		|  | @ -208,6 +208,7 @@ | ||||||
| 		"seedrandom": "3.0.5", | 		"seedrandom": "3.0.5", | ||||||
| 		"sharp": "0.29.1", | 		"sharp": "0.29.1", | ||||||
| 		"speakeasy": "2.0.0", | 		"speakeasy": "2.0.0", | ||||||
|  | 		"strict-event-emitter-types": "2.0.0", | ||||||
| 		"stringz": "2.1.0", | 		"stringz": "2.1.0", | ||||||
| 		"style-loader": "3.3.0", | 		"style-loader": "3.3.0", | ||||||
| 		"summaly": "2.4.1", | 		"summaly": "2.4.1", | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { Signin } from '@/models/entities/signin'; | ||||||
| @EntityRepository(Signin) | @EntityRepository(Signin) | ||||||
| export class SigninRepository extends Repository<Signin> { | export class SigninRepository extends Repository<Signin> { | ||||||
| 	public async pack( | 	public async pack( | ||||||
| 		src: any, | 		src: Signin, | ||||||
| 	) { | 	) { | ||||||
| 		return src; | 		return src; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -77,7 +77,7 @@ export async function readGroupMessagingMessage( | ||||||
| 		id: In(messageIds) | 		id: In(messageIds) | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	const reads = []; | 	const reads: MessagingMessage['id'][] = []; | ||||||
| 
 | 
 | ||||||
| 	for (const message of messages) { | 	for (const message of messages) { | ||||||
| 		if (message.userId === userId) continue; | 		if (message.userId === userId) continue; | ||||||
|  |  | ||||||
|  | @ -137,7 +137,7 @@ export default define(meta, async (ps, user) => { | ||||||
| 		notify: ps.notify, | 		notify: ps.notify, | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	publishInternalEvent('antennaUpdated', Antennas.findOneOrFail(antenna.id)); | 	publishInternalEvent('antennaUpdated', await Antennas.findOneOrFail(antenna.id)); | ||||||
| 
 | 
 | ||||||
| 	return await Antennas.pack(antenna.id); | 	return await Antennas.pack(antenna.id); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import Channel from '../channel'; | ||||||
| import { Notes } from '@/models/index'; | import { Notes } from '@/models/index'; | ||||||
| import { isMutedUserRelated } from '@/misc/is-muted-user-related'; | import { isMutedUserRelated } from '@/misc/is-muted-user-related'; | ||||||
| import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; | import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; | ||||||
|  | import { StreamMessages } from '../types'; | ||||||
| 
 | 
 | ||||||
| export default class extends Channel { | export default class extends Channel { | ||||||
| 	public readonly chName = 'antenna'; | 	public readonly chName = 'antenna'; | ||||||
|  | @ -19,11 +20,9 @@ export default class extends Channel { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@autobind | 	@autobind | ||||||
| 	private async onEvent(data: any) { | 	private async onEvent(data: StreamMessages['antenna']['payload']) { | ||||||
| 		const { type, body } = data; | 		if (data.type === 'note') { | ||||||
| 
 | 			const note = await Notes.pack(data.body.id, this.user, { detail: true }); | ||||||
| 		if (type === 'note') { |  | ||||||
| 			const note = await Notes.pack(body.id, this.user, { detail: true }); |  | ||||||
| 
 | 
 | ||||||
| 			// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 | 			// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 | ||||||
| 			if (isMutedUserRelated(note, this.muting)) return; | 			if (isMutedUserRelated(note, this.muting)) return; | ||||||
|  | @ -34,7 +33,7 @@ export default class extends Channel { | ||||||
| 
 | 
 | ||||||
| 			this.send('note', note); | 			this.send('note', note); | ||||||
| 		} else { | 		} else { | ||||||
| 			this.send(type, body); | 			this.send(data.type, data.body); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import { Notes, Users } from '@/models/index'; | ||||||
| import { isMutedUserRelated } from '@/misc/is-muted-user-related'; | import { isMutedUserRelated } from '@/misc/is-muted-user-related'; | ||||||
| import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; | import { isBlockerUserRelated } from '@/misc/is-blocker-user-related'; | ||||||
| import { User } from '@/models/entities/user'; | import { User } from '@/models/entities/user'; | ||||||
|  | import { StreamMessages } from '../types'; | ||||||
| import { Packed } from '@/misc/schema'; | import { Packed } from '@/misc/schema'; | ||||||
| 
 | 
 | ||||||
| export default class extends Channel { | export default class extends Channel { | ||||||
|  | @ -52,7 +53,7 @@ export default class extends Channel { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@autobind | 	@autobind | ||||||
| 	private onEvent(data: any) { | 	private onEvent(data: StreamMessages['channel']['payload']) { | ||||||
| 		if (data.type === 'typing') { | 		if (data.type === 'typing') { | ||||||
| 			const id = data.body; | 			const id = data.body; | ||||||
| 			const begin = this.typers[id] == null; | 			const begin = this.typers[id] == null; | ||||||
|  |  | ||||||
|  | @ -11,35 +11,33 @@ export default class extends Channel { | ||||||
| 	public async init(params: any) { | 	public async init(params: any) { | ||||||
| 		// Subscribe main stream channel
 | 		// Subscribe main stream channel
 | ||||||
| 		this.subscriber.on(`mainStream:${this.user!.id}`, async data => { | 		this.subscriber.on(`mainStream:${this.user!.id}`, async data => { | ||||||
| 			const { type } = data; | 			switch (data.type) { | ||||||
| 			let { body } = data; |  | ||||||
| 
 |  | ||||||
| 			switch (type) { |  | ||||||
| 				case 'notification': { | 				case 'notification': { | ||||||
| 					if (this.muting.has(body.userId)) return; | 					if (data.body.userId && this.muting.has(data.body.userId)) return; | ||||||
| 					if (body.note && body.note.isHidden) { | 
 | ||||||
| 						const note = await Notes.pack(body.note.id, this.user, { | 					if (data.body.note && data.body.note.isHidden) { | ||||||
|  | 						const note = await Notes.pack(data.body.note.id, this.user, { | ||||||
| 							detail: true | 							detail: true | ||||||
| 						}); | 						}); | ||||||
| 						this.connection.cacheNote(note); | 						this.connection.cacheNote(note); | ||||||
| 						body.note = note; | 						data.body.note = note; | ||||||
| 					} | 					} | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
| 				case 'mention': { | 				case 'mention': { | ||||||
| 					if (this.muting.has(body.userId)) return; | 					if (this.muting.has(data.body.userId)) return; | ||||||
| 					if (body.isHidden) { | 					if (data.body.isHidden) { | ||||||
| 						const note = await Notes.pack(body.id, this.user, { | 						const note = await Notes.pack(data.body.id, this.user, { | ||||||
| 							detail: true | 							detail: true | ||||||
| 						}); | 						}); | ||||||
| 						this.connection.cacheNote(note); | 						this.connection.cacheNote(note); | ||||||
| 						body = note; | 						data.body = note; | ||||||
| 					} | 					} | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			this.send(type, body); | 			this.send(data.type, data.body); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivit | ||||||
| import Channel from '../channel'; | import Channel from '../channel'; | ||||||
| import { UserGroupJoinings, Users, MessagingMessages } from '@/models/index'; | import { UserGroupJoinings, Users, MessagingMessages } from '@/models/index'; | ||||||
| import { User, ILocalUser, IRemoteUser } from '@/models/entities/user'; | import { User, ILocalUser, IRemoteUser } from '@/models/entities/user'; | ||||||
|  | import { UserGroup } from '@/models/entities/user-group'; | ||||||
|  | import { StreamMessages } from '../types'; | ||||||
| 
 | 
 | ||||||
| export default class extends Channel { | export default class extends Channel { | ||||||
| 	public readonly chName = 'messaging'; | 	public readonly chName = 'messaging'; | ||||||
|  | @ -12,7 +14,7 @@ export default class extends Channel { | ||||||
| 	private otherpartyId: string | null; | 	private otherpartyId: string | null; | ||||||
| 	private otherparty: User | null; | 	private otherparty: User | null; | ||||||
| 	private groupId: string | 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 typers: Record<User['id'], Date> = {}; | ||||||
| 	private emitTypersIntervalId: ReturnType<typeof setInterval>; | 	private emitTypersIntervalId: ReturnType<typeof setInterval>; | ||||||
| 
 | 
 | ||||||
|  | @ -45,7 +47,7 @@ export default class extends Channel { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@autobind | 	@autobind | ||||||
| 	private onEvent(data: any) { | 	private onEvent(data: StreamMessages['messaging']['payload'] | StreamMessages['groupMessaging']['payload']) { | ||||||
| 		if (data.type === 'typing') { | 		if (data.type === 'typing') { | ||||||
| 			const id = data.body; | 			const id = data.body; | ||||||
| 			const begin = this.typers[id] == null; | 			const begin = this.typers[id] == null; | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ import { AccessToken } from '@/models/entities/access-token'; | ||||||
| import { UserProfile } from '@/models/entities/user-profile'; | import { UserProfile } from '@/models/entities/user-profile'; | ||||||
| import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream'; | import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream'; | ||||||
| import { UserGroup } from '@/models/entities/user-group'; | import { UserGroup } from '@/models/entities/user-group'; | ||||||
|  | import { StreamEventEmitter, StreamMessages } from './types'; | ||||||
| import { Packed } from '@/misc/schema'; | import { Packed } from '@/misc/schema'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -28,7 +29,7 @@ export default class Connection { | ||||||
| 	public followingChannels: Set<ChannelModel['id']> = new Set(); | 	public followingChannels: Set<ChannelModel['id']> = new Set(); | ||||||
| 	public token?: AccessToken; | 	public token?: AccessToken; | ||||||
| 	private wsConnection: websocket.connection; | 	private wsConnection: websocket.connection; | ||||||
| 	public subscriber: EventEmitter; | 	public subscriber: StreamEventEmitter; | ||||||
| 	private channels: Channel[] = []; | 	private channels: Channel[] = []; | ||||||
| 	private subscribingNotes: any = {}; | 	private subscribingNotes: any = {}; | ||||||
| 	private cachedNotes: Packed<'Note'>[] = []; | 	private cachedNotes: Packed<'Note'>[] = []; | ||||||
|  | @ -46,8 +47,8 @@ export default class Connection { | ||||||
| 
 | 
 | ||||||
| 		this.wsConnection.on('message', this.onWsConnectionMessage); | 		this.wsConnection.on('message', this.onWsConnectionMessage); | ||||||
| 
 | 
 | ||||||
| 		this.subscriber.on('broadcast', async ({ type, body }) => { | 		this.subscriber.on('broadcast', data => { | ||||||
| 			this.onBroadcastMessage(type, body); | 			this.onBroadcastMessage(data); | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		if (this.user) { | 		if (this.user) { | ||||||
|  | @ -57,43 +58,41 @@ export default class Connection { | ||||||
| 			this.updateFollowingChannels(); | 			this.updateFollowingChannels(); | ||||||
| 			this.updateUserProfile(); | 			this.updateUserProfile(); | ||||||
| 
 | 
 | ||||||
| 			this.subscriber.on(`user:${this.user.id}`, ({ type, body }) => { | 			this.subscriber.on(`user:${this.user.id}`, this.onUserEvent); | ||||||
| 				this.onUserEvent(type, body); |  | ||||||
| 			}); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@autobind | 	@autobind | ||||||
| 	private onUserEvent(type: string, body: any) { | 	private onUserEvent(data: StreamMessages['user']['payload']) { // { type, body }と展開するとそれぞれ型が分離してしまう
 | ||||||
| 		switch (type) { | 		switch (data.type) { | ||||||
| 			case 'follow': | 			case 'follow': | ||||||
| 				this.following.add(body.id); | 				this.following.add(data.body.id); | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			case 'unfollow': | 			case 'unfollow': | ||||||
| 				this.following.delete(body.id); | 				this.following.delete(data.body.id); | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			case 'mute': | 			case 'mute': | ||||||
| 				this.muting.add(body.id); | 				this.muting.add(data.body.id); | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			case 'unmute': | 			case 'unmute': | ||||||
| 				this.muting.delete(body.id); | 				this.muting.delete(data.body.id); | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			// TODO: block events
 | 			// TODO: block events
 | ||||||
| 
 | 
 | ||||||
| 			case 'followChannel': | 			case 'followChannel': | ||||||
| 				this.followingChannels.add(body.id); | 				this.followingChannels.add(data.body.id); | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			case 'unfollowChannel': | 			case 'unfollowChannel': | ||||||
| 				this.followingChannels.delete(body.id); | 				this.followingChannels.delete(data.body.id); | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			case 'updateUserProfile': | 			case 'updateUserProfile': | ||||||
| 				this.userProfile = body; | 				this.userProfile = data.body; | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			case 'terminate': | 			case 'terminate': | ||||||
|  | @ -145,8 +144,8 @@ export default class Connection { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@autobind | 	@autobind | ||||||
| 	private onBroadcastMessage(type: string, body: any) { | 	private onBroadcastMessage(data: StreamMessages['broadcast']['payload']) { | ||||||
| 		this.sendMessageToWs(type, body); | 		this.sendMessageToWs(data.type, data.body); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@autobind | 	@autobind | ||||||
|  | @ -249,7 +248,7 @@ export default class Connection { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@autobind | 	@autobind | ||||||
| 	private async onNoteStreamMessage(data: any) { | 	private async onNoteStreamMessage(data: StreamMessages['note']['payload']) { | ||||||
| 		this.sendMessageToWs('noteUpdated', { | 		this.sendMessageToWs('noteUpdated', { | ||||||
| 			id: data.body.id, | 			id: data.body.id, | ||||||
| 			type: data.type, | 			type: data.type, | ||||||
|  |  | ||||||
							
								
								
									
										299
									
								
								src/server/api/stream/types.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								src/server/api/stream/types.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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']; | ||||||
|  | @ -7,9 +7,28 @@ import { UserGroup } from '@/models/entities/user-group'; | ||||||
| import config from '@/config/index'; | import config from '@/config/index'; | ||||||
| import { Antenna } from '@/models/entities/antenna'; | import { Antenna } from '@/models/entities/antenna'; | ||||||
| import { Channel } from '@/models/entities/channel'; | import { Channel } from '@/models/entities/channel'; | ||||||
|  | import { | ||||||
|  | 	StreamChannels, | ||||||
|  | 	AdminStreamTypes, | ||||||
|  | 	AntennaStreamTypes, | ||||||
|  | 	BroadcastTypes, | ||||||
|  | 	ChannelStreamTypes, | ||||||
|  | 	DriveStreamTypes, | ||||||
|  | 	GroupMessagingStreamTypes, | ||||||
|  | 	InternalStreamTypes, | ||||||
|  | 	MainStreamTypes, | ||||||
|  | 	MessagingIndexStreamTypes, | ||||||
|  | 	MessagingStreamTypes, | ||||||
|  | 	NoteStreamTypes, | ||||||
|  | 	ReversiGameStreamTypes, | ||||||
|  | 	ReversiStreamTypes, | ||||||
|  | 	UserListStreamTypes, | ||||||
|  | 	UserStreamTypes | ||||||
|  | } from '@/server/api/stream/types'; | ||||||
|  | import { Packed } from '@/misc/schema'; | ||||||
| 
 | 
 | ||||||
| class Publisher { | class Publisher { | ||||||
| 	private publish = (channel: string, type: string | null, value?: any): void => { | 	private publish = (channel: StreamChannels, type: string | null, value?: any): void => { | ||||||
| 		const message = type == null ? value : value == null ? | 		const message = type == null ? value : value == null ? | ||||||
| 			{ type: type, body: null } : | 			{ type: type, body: null } : | ||||||
| 			{ type: type, body: value }; | 			{ type: type, body: value }; | ||||||
|  | @ -20,70 +39,70 @@ class Publisher { | ||||||
| 		})); | 		})); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishInternalEvent = (type: string, value?: any): void => { | 	public publishInternalEvent = <K extends keyof InternalStreamTypes>(type: K, value?: InternalStreamTypes[K]): void => { | ||||||
| 		this.publish('internal', type, typeof value === 'undefined' ? null : value); | 		this.publish('internal', type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishUserEvent = (userId: User['id'], type: string, value?: any): void => { | 	public publishUserEvent = <K extends keyof UserStreamTypes>(userId: User['id'], type: K, value?: UserStreamTypes[K]): void => { | ||||||
| 		this.publish(`user:${userId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`user:${userId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishBroadcastStream = (type: string, value?: any): void => { | 	public publishBroadcastStream = <K extends keyof BroadcastTypes>(type: K, value?: BroadcastTypes[K]): void => { | ||||||
| 		this.publish('broadcast', type, typeof value === 'undefined' ? null : value); | 		this.publish('broadcast', type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishMainStream = (userId: User['id'], type: string, value?: any): void => { | 	public publishMainStream = <K extends keyof MainStreamTypes>(userId: User['id'], type: K, value?: MainStreamTypes[K]): void => { | ||||||
| 		this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishDriveStream = (userId: User['id'], type: string, value?: any): void => { | 	public publishDriveStream = <K extends keyof DriveStreamTypes>(userId: User['id'], type: K, value?: DriveStreamTypes[K]): void => { | ||||||
| 		this.publish(`driveStream:${userId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`driveStream:${userId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishNoteStream = (noteId: Note['id'], type: string, value: any): void => { | 	public publishNoteStream = <K extends keyof NoteStreamTypes>(noteId: Note['id'], type: K, value?: NoteStreamTypes[K]): void => { | ||||||
| 		this.publish(`noteStream:${noteId}`, type, { | 		this.publish(`noteStream:${noteId}`, type, { | ||||||
| 			id: noteId, | 			id: noteId, | ||||||
| 			body: value | 			body: value | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishChannelStream = (channelId: Channel['id'], type: string, value?: any): void => { | 	public publishChannelStream = <K extends keyof ChannelStreamTypes>(channelId: Channel['id'], type: K, value?: ChannelStreamTypes[K]): void => { | ||||||
| 		this.publish(`channelStream:${channelId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`channelStream:${channelId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishUserListStream = (listId: UserList['id'], type: string, value?: any): void => { | 	public publishUserListStream = <K extends keyof UserListStreamTypes>(listId: UserList['id'], type: K, value?: UserListStreamTypes[K]): void => { | ||||||
| 		this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishAntennaStream = (antennaId: Antenna['id'], type: string, value?: any): void => { | 	public publishAntennaStream = <K extends keyof AntennaStreamTypes>(antennaId: Antenna['id'], type: K, value?: AntennaStreamTypes[K]): void => { | ||||||
| 		this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishMessagingStream = (userId: User['id'], otherpartyId: User['id'], type: string, value?: any): void => { | 	public publishMessagingStream = <K extends keyof MessagingStreamTypes>(userId: User['id'], otherpartyId: User['id'], type: K, value?: MessagingStreamTypes[K]): void => { | ||||||
| 		this.publish(`messagingStream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`messagingStream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishGroupMessagingStream = (groupId: UserGroup['id'], type: string, value?: any): void => { | 	public publishGroupMessagingStream = <K extends keyof GroupMessagingStreamTypes>(groupId: UserGroup['id'], type: K, value?: GroupMessagingStreamTypes[K]): void => { | ||||||
| 		this.publish(`messagingStream:${groupId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`messagingStream:${groupId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishMessagingIndexStream = (userId: User['id'], type: string, value?: any): void => { | 	public publishMessagingIndexStream = <K extends keyof MessagingIndexStreamTypes>(userId: User['id'], type: K, value?: MessagingIndexStreamTypes[K]): void => { | ||||||
| 		this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishReversiStream = (userId: User['id'], type: string, value?: any): void => { | 	public publishReversiStream = <K extends keyof ReversiStreamTypes>(userId: User['id'], type: K, value?: ReversiStreamTypes[K]): void => { | ||||||
| 		this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishReversiGameStream = (gameId: ReversiGame['id'], type: string, value?: any): void => { | 	public publishReversiGameStream = <K extends keyof ReversiGameStreamTypes>(gameId: ReversiGame['id'], type: K, value?: ReversiGameStreamTypes[K]): void => { | ||||||
| 		this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishNotesStream = (note: any): void => { | 	public publishNotesStream = (note: Packed<'Note'>): void => { | ||||||
| 		this.publish('notesStream', null, note); | 		this.publish('notesStream', null, note); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public publishAdminStream = (userId: User['id'], type: string, value?: any): void => { | 	public publishAdminStream = <K extends keyof AdminStreamTypes>(userId: User['id'], type: K, value?: AdminStreamTypes[K]): void => { | ||||||
| 		this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10288,6 +10288,11 @@ streamsearch@0.1.2: | ||||||
|   resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" |   resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" | ||||||
|   integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= |   integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= | ||||||
| 
 | 
 | ||||||
|  | strict-event-emitter-types@2.0.0: | ||||||
|  |   version "2.0.0" | ||||||
|  |   resolved "https://registry.yarnpkg.com/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz#05e15549cb4da1694478a53543e4e2f4abcf277f" | ||||||
|  |   integrity sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA== | ||||||
|  | 
 | ||||||
| strict-uri-encode@^1.0.0: | strict-uri-encode@^1.0.0: | ||||||
|   version "1.1.0" |   version "1.1.0" | ||||||
|   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" |   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue