parent
							
								
									d7d02cd2bc
								
							
						
					
					
						commit
						99276028ae
					
				
					 3 changed files with 174 additions and 10 deletions
				
			
		| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
export type ID = string;
 | 
					export type ID = string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TODO = Record<string, any>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type User = {
 | 
					export type User = {
 | 
				
			||||||
	id: ID;
 | 
						id: ID;
 | 
				
			||||||
	username: string;
 | 
						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 = {
 | 
					export type DriveFile = {
 | 
				
			||||||
	id: ID;
 | 
						id: ID;
 | 
				
			||||||
	createdAt: string;
 | 
						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 = {
 | 
					export type InstanceMetadata = {
 | 
				
			||||||
	emojis: {
 | 
						emojis: {
 | 
				
			||||||
		category: string;
 | 
							category: string;
 | 
				
			||||||
| 
						 | 
					@ -119,5 +200,13 @@ export type Page = {
 | 
				
			||||||
	isLiked?: boolean;
 | 
						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 UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt';
 | 
				
			||||||
export type OriginType = 'combined' | 'local' | 'remote';
 | 
					export type OriginType = 'combined' | 'local' | 'remote';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import { EventEmitter } from 'eventemitter3';
 | 
				
			||||||
import ReconnectingWebsocket from 'reconnecting-websocket';
 | 
					import ReconnectingWebsocket from 'reconnecting-websocket';
 | 
				
			||||||
import { stringify } from 'querystring';
 | 
					import { stringify } from 'querystring';
 | 
				
			||||||
import { markRaw } from '@vue/reactivity';
 | 
					import { markRaw } from '@vue/reactivity';
 | 
				
			||||||
 | 
					import { MeDetailed, MessagingMessage, Note, Notification, PageEvent, User } from './entities';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function urlQuery(obj: {}): string {
 | 
					function urlQuery(obj: {}): string {
 | 
				
			||||||
	return stringify(Object.entries(obj)
 | 
						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>));
 | 
							.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
 | 
					 * Misskey stream connection
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export default class Stream extends EventEmitter {
 | 
					export default class Stream extends EventEmitter<StreamEvents> {
 | 
				
			||||||
	private stream: ReconnectingWebsocket;
 | 
						private stream: ReconnectingWebsocket;
 | 
				
			||||||
	public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing';
 | 
						public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing';
 | 
				
			||||||
	private sharedConnectionPools: Pool[] = [];
 | 
						private sharedConnectionPools: Pool[] = [];
 | 
				
			||||||
| 
						 | 
					@ -38,7 +113,7 @@ export default class Stream extends EventEmitter {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@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);
 | 
							let pool = this.sharedConnectionPools.find(p => p.channel === channel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (pool == null) {
 | 
							if (pool == null) {
 | 
				
			||||||
| 
						 | 
					@ -62,7 +137,7 @@ export default class Stream extends EventEmitter {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@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));
 | 
							const connection = markRaw(new NonSharedConnection(this, channel, params));
 | 
				
			||||||
		this.nonSharedConnections.push(connection);
 | 
							this.nonSharedConnections.push(connection);
 | 
				
			||||||
		return 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;
 | 
						public channel: string;
 | 
				
			||||||
	protected stream: Stream;
 | 
						protected stream: Stream;
 | 
				
			||||||
	public abstract id: string;
 | 
						public abstract id: string;
 | 
				
			||||||
| 
						 | 
					@ -261,7 +336,7 @@ abstract class Connection extends EventEmitter {
 | 
				
			||||||
	public abstract dispose(): void;
 | 
						public abstract dispose(): void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SharedConnection extends Connection {
 | 
					class SharedConnection<Events = any> extends Connection<Events> {
 | 
				
			||||||
	private pool: Pool;
 | 
						private pool: Pool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public get id(): string {
 | 
						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;
 | 
						public id: string;
 | 
				
			||||||
	protected params: any;
 | 
						protected params: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ describe('Streaming', () => {
 | 
				
			||||||
		const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
 | 
							const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
 | 
				
			||||||
		const mainChannelReceived: any[] = [];
 | 
							const mainChannelReceived: any[] = [];
 | 
				
			||||||
		const main = stream.useSharedConnection('main');
 | 
							const main = stream.useSharedConnection('main');
 | 
				
			||||||
		main.on('foo', payload => {
 | 
							main.on('meUpdated', payload => {
 | 
				
			||||||
			mainChannelReceived.push(payload);
 | 
								mainChannelReceived.push(payload);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
		await server.connected;
 | 
							await server.connected;
 | 
				
			||||||
| 
						 | 
					@ -21,15 +21,15 @@ describe('Streaming', () => {
 | 
				
			||||||
			type: 'channel',
 | 
								type: 'channel',
 | 
				
			||||||
			body: {
 | 
								body: {
 | 
				
			||||||
				id: mainChannelId,
 | 
									id: mainChannelId,
 | 
				
			||||||
				type: 'foo',
 | 
									type: 'meUpdated',
 | 
				
			||||||
				body: {
 | 
									body: {
 | 
				
			||||||
					bar: 'buzz'
 | 
										id: 'foo'
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}));
 | 
							}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		expect(mainChannelReceived[0]).toEqual({
 | 
							expect(mainChannelReceived[0]).toEqual({
 | 
				
			||||||
			bar: 'buzz'
 | 
								id: 'foo'
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue