From 99276028ae5ded5cc1d51cff928db3fcd290846e Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 17 May 2021 19:50:31 +0900 Subject: [PATCH] Typed stream (#13) * Update streaming.ts * Update streaming.ts * wip --- src/entities.ts | 89 +++++++++++++++++++++++++++++++++++++++++++++++ src/streaming.ts | 87 +++++++++++++++++++++++++++++++++++++++++---- test/streaming.ts | 8 ++--- 3 files changed, 174 insertions(+), 10 deletions(-) diff --git a/src/entities.ts b/src/entities.ts index 64124f45b..3e7d58394 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -1,5 +1,7 @@ export type ID = string; +type TODO = Record; + 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'; diff --git a/src/streaming.ts b/src/streaming.ts index a30ef5497..effa1f11b 100644 --- a/src/streaming.ts +++ b/src/streaming.ts @@ -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)); } +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 { 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(channel: C, name?: string): SharedConnection { 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(channel: C, params?: any): NonSharedConnection { 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 = any> extends EventEmitter { 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 extends Connection { private pool: Pool; public get id(): string { @@ -288,7 +363,7 @@ class SharedConnection extends Connection { } } -class NonSharedConnection extends Connection { +class NonSharedConnection extends Connection { public id: string; protected params: any; diff --git a/test/streaming.ts b/test/streaming.ts index 65ab449b6..bfa872ce9 100644 --- a/test/streaming.ts +++ b/test/streaming.ts @@ -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' }); }); });