parent
							
								
									d7d02cd2bc
								
							
						
					
					
						commit
						99276028ae
					
				|  | @ -1,5 +1,7 @@ | |||
| export type ID = string; | ||||
| 
 | ||||
| type TODO = Record<string, any>; | ||||
| 
 | ||||
| export type User = { | ||||
| 	id: ID; | ||||
| 	username: string; | ||||
|  | @ -14,6 +16,17 @@ export type User = { | |||
| 	}[]; | ||||
| }; | ||||
| 
 | ||||
| export type MeDetailed = User & { | ||||
| 	avatarId: DriveFile['id']; | ||||
| 	bannerId: DriveFile['id']; | ||||
| 	autoAcceptFollowed: boolean; | ||||
| 	noCrawle: boolean; | ||||
| 	isExplorable: boolean; | ||||
| 	hideOnlineStatus: boolean; | ||||
| 	mutedWords: string[][]; | ||||
| 	[other: string]: any; | ||||
| }; | ||||
| 
 | ||||
| export type DriveFile = { | ||||
| 	id: ID; | ||||
| 	createdAt: string; | ||||
|  | @ -59,6 +72,74 @@ export type Note = { | |||
| 	}[]; | ||||
| }; | ||||
| 
 | ||||
| export type Notification = { | ||||
| 	id: ID; | ||||
| 	createdAt: string; | ||||
| 	isRead: boolean; | ||||
| } & ({ | ||||
| 	type: 'reaction'; | ||||
| 	reaction: string; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| 	note: Note; | ||||
| } | { | ||||
| 	type: 'reply'; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| 	note: Note; | ||||
| } | { | ||||
| 	type: 'renote'; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| 	note: Note; | ||||
| } | { | ||||
| 	type: 'quote'; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| 	note: Note; | ||||
| } | { | ||||
| 	type: 'mention'; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| 	note: Note; | ||||
| } | { | ||||
| 	type: 'pollVote'; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| 	note: Note; | ||||
| } | { | ||||
| 	type: 'follow'; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| } | { | ||||
| 	type: 'followRequestAccepted'; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| } | { | ||||
| 	type: 'receiveFollowRequest'; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| } | { | ||||
| 	type: 'groupInvited'; // TODO
 | ||||
| } | { | ||||
| 	type: 'app'; | ||||
| 	body: string; | ||||
| 	icon: string; | ||||
| }); | ||||
| 
 | ||||
| export type MessagingMessage = { | ||||
| 	id: ID; | ||||
| 	createdAt: string; | ||||
| 	file: DriveFile | null; | ||||
| 	fileId: DriveFile['id'] | null; | ||||
| 	isRead: boolean; | ||||
| 	reads: User['id'][]; | ||||
| 	text: string | null; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| 	groupId: string; // TODO
 | ||||
| }; | ||||
| 
 | ||||
| export type InstanceMetadata = { | ||||
| 	emojis: { | ||||
| 		category: string; | ||||
|  | @ -119,5 +200,13 @@ export type Page = { | |||
| 	isLiked?: boolean; | ||||
| }; | ||||
| 
 | ||||
| export type PageEvent = { | ||||
| 	pageId: Page['id']; | ||||
| 	event: string; | ||||
| 	var: any; | ||||
| 	userId: User['id']; | ||||
| 	user: User; | ||||
| }; | ||||
| 
 | ||||
| export type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; | ||||
| export type OriginType = 'combined' | 'local' | 'remote'; | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import { EventEmitter } from 'eventemitter3'; | |||
| import ReconnectingWebsocket from 'reconnecting-websocket'; | ||||
| import { stringify } from 'querystring'; | ||||
| import { markRaw } from '@vue/reactivity'; | ||||
| import { MeDetailed, MessagingMessage, Note, Notification, PageEvent, User } from './entities'; | ||||
| 
 | ||||
| function urlQuery(obj: {}): string { | ||||
| 	return stringify(Object.entries(obj) | ||||
|  | @ -10,10 +11,84 @@ function urlQuery(obj: {}): string { | |||
| 		.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>)); | ||||
| } | ||||
| 
 | ||||
| type FIXME = any; | ||||
| 
 | ||||
| type ChannelDef = { | ||||
| 	main: { | ||||
| 		events: { | ||||
| 			notification: (payload: Notification) => void; | ||||
| 			mention: (payload: Note) => void; | ||||
| 			reply: (payload: Note) => void; | ||||
| 			renote: (payload: Note) => void; | ||||
| 			follow: (payload: User) => void; // 自分が他人をフォローしたとき
 | ||||
| 			followed: (payload: User) => void; // 他人が自分をフォローしたとき
 | ||||
| 			unfollow: (payload: User) => void; // 自分が他人をフォロー解除したとき
 | ||||
| 			meUpdated: (payload: MeDetailed) => void; | ||||
| 			pageEvent: (payload: PageEvent) => void; | ||||
| 		}; | ||||
| 	}; | ||||
| 	homeTimeline: { | ||||
| 		events: { | ||||
| 			note: (payload: Note) => void; | ||||
| 		}; | ||||
| 	}; | ||||
| 	localTimeline: { | ||||
| 		events: { | ||||
| 			note: (payload: Note) => void; | ||||
| 		}; | ||||
| 	}; | ||||
| 	hybridTimeline: { | ||||
| 		events: { | ||||
| 			note: (payload: Note) => void; | ||||
| 		}; | ||||
| 	}; | ||||
| 	globalTimeline: { | ||||
| 		events: { | ||||
| 			note: (payload: Note) => void; | ||||
| 		}; | ||||
| 	}; | ||||
| 	messaging: { | ||||
| 		events: { | ||||
| 			message: (payload: MessagingMessage) => void; | ||||
| 			deleted: (payload: MessagingMessage['id']) => void; | ||||
| 			read: (payload: MessagingMessage['id'][]) => void; | ||||
| 			typing: (payload: User['id']) => void; | ||||
| 		}; | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| type NoteUpdatedEvent = { | ||||
| 	id: Note['id']; | ||||
| 	type: 'reacted'; | ||||
| 	body: { | ||||
| 		reaction: string; | ||||
| 		userId: User['id']; | ||||
| 	}; | ||||
| } | { | ||||
| 	id: Note['id']; | ||||
| 	type: 'deleted'; | ||||
| 	body: { | ||||
| 		deletedAt: string; | ||||
| 	}; | ||||
| } | { | ||||
| 	id: Note['id']; | ||||
| 	type: 'pollVoted'; | ||||
| 	body: { | ||||
| 		choice: number; | ||||
| 		userId: User['id']; | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| type StreamEvents = { | ||||
| 	_connected_: void; | ||||
| 	_disconnected_: void; | ||||
| 	noteUpdated: (payload: NoteUpdatedEvent) => void; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Misskey stream connection | ||||
|  */ | ||||
| export default class Stream extends EventEmitter { | ||||
| export default class Stream extends EventEmitter<StreamEvents> { | ||||
| 	private stream: ReconnectingWebsocket; | ||||
| 	public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing'; | ||||
| 	private sharedConnectionPools: Pool[] = []; | ||||
|  | @ -38,7 +113,7 @@ export default class Stream extends EventEmitter { | |||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public useSharedConnection(channel: string, name?: string): SharedConnection { | ||||
| 	public useSharedConnection<C extends keyof ChannelDef>(channel: C, name?: string): SharedConnection<ChannelDef[C]['events']> { | ||||
| 		let pool = this.sharedConnectionPools.find(p => p.channel === channel); | ||||
| 
 | ||||
| 		if (pool == null) { | ||||
|  | @ -62,7 +137,7 @@ export default class Stream extends EventEmitter { | |||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public connectToChannel(channel: string, params?: any): NonSharedConnection { | ||||
| 	public connectToChannel<C extends keyof ChannelDef>(channel: C, params?: any): NonSharedConnection<ChannelDef[C]['events']> { | ||||
| 		const connection = markRaw(new NonSharedConnection(this, channel, params)); | ||||
| 		this.nonSharedConnections.push(connection); | ||||
| 		return connection; | ||||
|  | @ -227,7 +302,7 @@ class Pool { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| abstract class Connection extends EventEmitter { | ||||
| abstract class Connection<Events extends Record<string, any> = any> extends EventEmitter<Events> { | ||||
| 	public channel: string; | ||||
| 	protected stream: Stream; | ||||
| 	public abstract id: string; | ||||
|  | @ -261,7 +336,7 @@ abstract class Connection extends EventEmitter { | |||
| 	public abstract dispose(): void; | ||||
| } | ||||
| 
 | ||||
| class SharedConnection extends Connection { | ||||
| class SharedConnection<Events = any> extends Connection<Events> { | ||||
| 	private pool: Pool; | ||||
| 
 | ||||
| 	public get id(): string { | ||||
|  | @ -288,7 +363,7 @@ class SharedConnection extends Connection { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| class NonSharedConnection extends Connection { | ||||
| class NonSharedConnection<Events = any> extends Connection<Events> { | ||||
| 	public id: string; | ||||
| 	protected params: any; | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ describe('Streaming', () => { | |||
| 		const stream = new Stream('https://misskey.test', { token: 'TOKEN' }); | ||||
| 		const mainChannelReceived: any[] = []; | ||||
| 		const main = stream.useSharedConnection('main'); | ||||
| 		main.on('foo', payload => { | ||||
| 		main.on('meUpdated', payload => { | ||||
| 			mainChannelReceived.push(payload); | ||||
| 		}); | ||||
| 		await server.connected; | ||||
|  | @ -21,15 +21,15 @@ describe('Streaming', () => { | |||
| 			type: 'channel', | ||||
| 			body: { | ||||
| 				id: mainChannelId, | ||||
| 				type: 'foo', | ||||
| 				type: 'meUpdated', | ||||
| 				body: { | ||||
| 					bar: 'buzz' | ||||
| 					id: 'foo' | ||||
| 				} | ||||
| 			} | ||||
| 		})); | ||||
| 
 | ||||
| 		expect(mainChannelReceived[0]).toEqual({ | ||||
| 			bar: 'buzz' | ||||
| 			id: 'foo' | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue