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", | ||||
| 		"sharp": "0.29.1", | ||||
| 		"speakeasy": "2.0.0", | ||||
| 		"strict-event-emitter-types": "2.0.0", | ||||
| 		"stringz": "2.1.0", | ||||
| 		"style-loader": "3.3.0", | ||||
| 		"summaly": "2.4.1", | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { Signin } from '@/models/entities/signin'; | |||
| @EntityRepository(Signin) | ||||
| export class SigninRepository extends Repository<Signin> { | ||||
| 	public async pack( | ||||
| 		src: any, | ||||
| 		src: Signin, | ||||
| 	) { | ||||
| 		return src; | ||||
| 	} | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
| }); | ||||
|  |  | |||
|  | @ -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); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
							
								
								
									
										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 { Antenna } from '@/models/entities/antenna'; | ||||
| 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 { | ||||
| 	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 ? | ||||
| 			{ type: type, body: null } : | ||||
| 			{ 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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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, { | ||||
| 			id: noteId, | ||||
| 			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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	public publishNotesStream = (note: any): void => { | ||||
| 	public publishNotesStream = (note: Packed<'Note'>): void => { | ||||
| 		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); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -10288,6 +10288,11 @@ streamsearch@0.1.2: | |||
|   resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" | ||||
|   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: | ||||
|   version "1.1.0" | ||||
|   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