Merge branch 'stream-types' into swn
This commit is contained in:
@@ -93,7 +93,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']['spec']) {
|
||||
if (data.type === 'note') {
|
||||
const note = await Notes.pack(data.body.id, this.user, { detail: true });
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isMutedUserRelated(note, this.muting)) return;
|
||||
@@ -33,8 +32,6 @@ export default class extends Channel {
|
||||
this.connection.cacheNote(note);
|
||||
|
||||
this.send('note', note);
|
||||
} else {
|
||||
this.send(type, body);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,8 +3,9 @@ import Channel from '../channel';
|
||||
import { Notes, Users } from '@/models/index';
|
||||
import { isMutedUserRelated } from '@/misc/is-muted-user-related';
|
||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||
import { PackedNote } from '@/models/repositories/note';
|
||||
import { User } from '@/models/entities/user';
|
||||
import { StreamMessages } from '../types';
|
||||
import { Packed } from '@/misc/schema';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'channel';
|
||||
@@ -25,7 +26,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onNote(note: PackedNote) {
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
if (note.channelId !== this.channelId) return;
|
||||
|
||||
// リプライなら再pack
|
||||
@@ -52,7 +53,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private onEvent(data: any) {
|
||||
private onEvent(data: StreamMessages['channel']['spec']) {
|
||||
if (data.type === 'typing') {
|
||||
const id = data.body;
|
||||
const begin = this.typers[id] == null;
|
||||
|
@@ -3,9 +3,9 @@ import { isMutedUserRelated } from '@/misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { fetchMeta } from '@/misc/fetch-meta';
|
||||
import { Notes } from '@/models/index';
|
||||
import { PackedNote } from '@/models/repositories/note';
|
||||
import { checkWordMute } from '@/misc/check-word-mute';
|
||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||
import { Packed } from '@/misc/schema';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'globalTimeline';
|
||||
@@ -24,7 +24,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onNote(note: PackedNote) {
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
if (note.visibility !== 'public') return;
|
||||
if (note.channelId != null) return;
|
||||
|
||||
|
@@ -2,9 +2,9 @@ import autobind from 'autobind-decorator';
|
||||
import { isMutedUserRelated } from '@/misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { Notes } from '@/models/index';
|
||||
import { PackedNote } from '@/models/repositories/note';
|
||||
import { normalizeForSearch } from '@/misc/normalize-for-search';
|
||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||
import { Packed } from '@/misc/schema';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'hashtag';
|
||||
@@ -23,7 +23,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onNote(note: PackedNote) {
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
const noteTags = note.tags ? note.tags.map((t: string) => t.toLowerCase()) : [];
|
||||
const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag))));
|
||||
if (!matched) return;
|
||||
|
@@ -2,9 +2,9 @@ import autobind from 'autobind-decorator';
|
||||
import { isMutedUserRelated } from '@/misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { Notes } from '@/models/index';
|
||||
import { PackedNote } from '@/models/repositories/note';
|
||||
import { checkWordMute } from '@/misc/check-word-mute';
|
||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||
import { Packed } from '@/misc/schema';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'homeTimeline';
|
||||
@@ -18,7 +18,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onNote(note: PackedNote) {
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
if (note.channelId) {
|
||||
if (!this.followingChannels.has(note.channelId)) return;
|
||||
} else {
|
||||
|
@@ -3,9 +3,9 @@ import { isMutedUserRelated } from '@/misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { fetchMeta } from '@/misc/fetch-meta';
|
||||
import { Notes } from '@/models/index';
|
||||
import { PackedNote } from '@/models/repositories/note';
|
||||
import { checkWordMute } from '@/misc/check-word-mute';
|
||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||
import { Packed } from '@/misc/schema';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'hybridTimeline';
|
||||
@@ -22,7 +22,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onNote(note: PackedNote) {
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
// チャンネルの投稿ではなく、自分自身の投稿 または
|
||||
// チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
|
||||
// チャンネルの投稿ではなく、全体公開のローカルの投稿 または
|
||||
|
@@ -3,9 +3,9 @@ import { isMutedUserRelated } from '@/misc/is-muted-user-related';
|
||||
import Channel from '../channel';
|
||||
import { fetchMeta } from '@/misc/fetch-meta';
|
||||
import { Notes } from '@/models/index';
|
||||
import { PackedNote } from '@/models/repositories/note';
|
||||
import { checkWordMute } from '@/misc/check-word-mute';
|
||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||
import { Packed } from '@/misc/schema';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'localTimeline';
|
||||
@@ -24,7 +24,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onNote(note: PackedNote) {
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
if (note.user.host !== null) return;
|
||||
if (note.visibility !== 'public') return;
|
||||
if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
|
||||
|
@@ -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']['spec'] | StreamMessages['groupMessaging']['spec']) {
|
||||
if (data.type === 'typing') {
|
||||
const id = data.body;
|
||||
const begin = this.typers[id] == null;
|
||||
|
@@ -3,8 +3,8 @@ import Channel from '../channel';
|
||||
import { Notes, UserListJoinings, UserLists } from '@/models/index';
|
||||
import { isMutedUserRelated } from '@/misc/is-muted-user-related';
|
||||
import { User } from '@/models/entities/user';
|
||||
import { PackedNote } from '@/models/repositories/note';
|
||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||
import { Packed } from '@/misc/schema';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'userList';
|
||||
@@ -47,7 +47,7 @@ export default class extends Channel {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onNote(note: PackedNote) {
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
if (!this.listUsers.includes(note.userId)) return;
|
||||
|
||||
if (['followers', 'specified'].includes(note.visibility)) {
|
||||
|
@@ -14,7 +14,8 @@ 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 { PackedNote } from '@/models/repositories/note';
|
||||
import { StreamEventEmitter, StreamMessages } from './types';
|
||||
import { Packed } from '@/misc/schema';
|
||||
|
||||
/**
|
||||
* Main stream connection
|
||||
@@ -28,10 +29,10 @@ 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: PackedNote[] = [];
|
||||
private cachedNotes: Packed<'Note'>[] = [];
|
||||
|
||||
constructor(
|
||||
wsConnection: websocket.connection,
|
||||
@@ -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']['spec']) { // { 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,13 +144,13 @@ export default class Connection {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private onBroadcastMessage(type: string, body: any) {
|
||||
this.sendMessageToWs(type, body);
|
||||
private onBroadcastMessage(data: StreamMessages['broadcast']['spec']) {
|
||||
this.sendMessageToWs(data.type, data.body);
|
||||
}
|
||||
|
||||
@autobind
|
||||
public cacheNote(note: PackedNote) {
|
||||
const add = (note: PackedNote) => {
|
||||
public cacheNote(note: Packed<'Note'>) {
|
||||
const add = (note: Packed<'Note'>) => {
|
||||
const existIndex = this.cachedNotes.findIndex(n => n.id === note.id);
|
||||
if (existIndex > -1) {
|
||||
this.cachedNotes[existIndex] = note;
|
||||
@@ -249,7 +248,7 @@ export default class Connection {
|
||||
}
|
||||
|
||||
@autobind
|
||||
private async onNoteStreamMessage(data: any) {
|
||||
private async onNoteStreamMessage(data: StreamMessages['note']['spec']) {
|
||||
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';
|
||||
spec: EventUnionFromDictionary<InternalStreamTypes>;
|
||||
};
|
||||
broadcast: {
|
||||
name: 'broadcast';
|
||||
spec: EventUnionFromDictionary<BroadcastTypes>;
|
||||
};
|
||||
user: {
|
||||
name: `user:${User['id']}`;
|
||||
spec: EventUnionFromDictionary<UserStreamTypes>;
|
||||
};
|
||||
main: {
|
||||
name: `mainStream:${User['id']}`;
|
||||
spec: EventUnionFromDictionary<MainStreamTypes>;
|
||||
};
|
||||
drive: {
|
||||
name: `driveStream:${User['id']}`;
|
||||
spec: EventUnionFromDictionary<DriveStreamTypes>;
|
||||
};
|
||||
note: {
|
||||
name: `noteStream:${Note['id']}`;
|
||||
spec: EventUnionFromDictionary<NoteStreamEventTypes>;
|
||||
};
|
||||
channel: {
|
||||
name: `channelStream:${Channel['id']}`;
|
||||
spec: EventUnionFromDictionary<ChannelStreamTypes>;
|
||||
};
|
||||
userList: {
|
||||
name: `userListStream:${UserList['id']}`;
|
||||
spec: EventUnionFromDictionary<UserListStreamTypes>;
|
||||
};
|
||||
antenna: {
|
||||
name: `antennaStream:${Antenna['id']}`;
|
||||
spec: EventUnionFromDictionary<AntennaStreamTypes>;
|
||||
};
|
||||
messaging: {
|
||||
name: `messagingStream:${User['id']}-${User['id']}`;
|
||||
spec: EventUnionFromDictionary<MessagingStreamTypes>;
|
||||
};
|
||||
groupMessaging: {
|
||||
name: `messagingStream:${UserGroup['id']}`;
|
||||
spec: EventUnionFromDictionary<GroupMessagingStreamTypes>;
|
||||
};
|
||||
messagingIndex: {
|
||||
name: `messagingIndexStream:${User['id']}`;
|
||||
spec: EventUnionFromDictionary<MessagingIndexStreamTypes>;
|
||||
};
|
||||
reversi: {
|
||||
name: `reversiStream:${User['id']}`;
|
||||
spec: EventUnionFromDictionary<ReversiStreamTypes>;
|
||||
};
|
||||
reversiGame: {
|
||||
name: `reversiGameStream:${ReversiGame['id']}`;
|
||||
spec: EventUnionFromDictionary<ReversiGameStreamTypes>;
|
||||
};
|
||||
admin: {
|
||||
name: `adminStream:${User['id']}`;
|
||||
spec: EventUnionFromDictionary<AdminStreamTypes>;
|
||||
};
|
||||
notes: {
|
||||
name: 'notesStream';
|
||||
spec: Packed<'Note'>;
|
||||
};
|
||||
};
|
||||
|
||||
// API event definitions
|
||||
// ストリームごとのEmitterの辞書を用意
|
||||
type EventEmitterDictionary = { [x in keyof StreamMessages]: Emitter<EventEmitter, { [y in StreamMessages[x]['name']]: (e: StreamMessages[x]['spec']) => 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'];
|
Reference in New Issue
Block a user