kill any from streaming API Implementation (#14251)
* chore: add JsonValue type * refactor: kill any from Connection.ts * refactor: fix StreamEventEmitter contains undefined instead of null * refactor: kill any from channels * docs(changelog): Fix: Steaming APIが不正なデータを受けた場合の動作が不安定である問題 * fix license header * fix lints
This commit is contained in:
		| @@ -14,6 +14,7 @@ import { CacheService } from '@/core/CacheService.js'; | ||||
| import { MiFollowing, MiUserProfile } from '@/models/_.js'; | ||||
| import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js'; | ||||
| import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import type { ChannelsService } from './ChannelsService.js'; | ||||
| import type { EventEmitter } from 'events'; | ||||
| import type Channel from './channel.js'; | ||||
| @@ -28,7 +29,7 @@ export default class Connection { | ||||
| 	private wsConnection: WebSocket.WebSocket; | ||||
| 	public subscriber: StreamEventEmitter; | ||||
| 	private channels: Channel[] = []; | ||||
| 	private subscribingNotes: any = {}; | ||||
| 	private subscribingNotes: Partial<Record<string, number>> = {}; | ||||
| 	private cachedNotes: Packed<'Note'>[] = []; | ||||
| 	public userProfile: MiUserProfile | null = null; | ||||
| 	public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {}; | ||||
| @@ -101,7 +102,7 @@ export default class Connection { | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	private async onWsConnectionMessage(data: WebSocket.RawData) { | ||||
| 		let obj: Record<string, any>; | ||||
| 		let obj: JsonObject; | ||||
|  | ||||
| 		try { | ||||
| 			obj = JSON.parse(data.toString()); | ||||
| @@ -111,6 +112,8 @@ export default class Connection { | ||||
|  | ||||
| 		const { type, body } = obj; | ||||
|  | ||||
| 		if (typeof body !== 'object' || body === null || Array.isArray(body)) return; | ||||
|  | ||||
| 		switch (type) { | ||||
| 			case 'readNotification': this.onReadNotification(body); break; | ||||
| 			case 'subNote': this.onSubscribeNote(body); break; | ||||
| @@ -151,7 +154,7 @@ export default class Connection { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	private readNote(body: any) { | ||||
| 	private readNote(body: JsonObject) { | ||||
| 		const id = body.id; | ||||
|  | ||||
| 		const note = this.cachedNotes.find(n => n.id === id); | ||||
| @@ -163,7 +166,7 @@ export default class Connection { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	private onReadNotification(payload: any) { | ||||
| 	private onReadNotification(payload: JsonObject) { | ||||
| 		this.notificationService.readAllNotification(this.user!.id); | ||||
| 	} | ||||
|  | ||||
| @@ -171,16 +174,14 @@ export default class Connection { | ||||
| 	 * 投稿購読要求時 | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	private onSubscribeNote(payload: any) { | ||||
| 		if (!payload.id) return; | ||||
| 	private onSubscribeNote(payload: JsonObject) { | ||||
| 		if (!payload.id || typeof payload.id !== 'string') return; | ||||
|  | ||||
| 		if (this.subscribingNotes[payload.id] == null) { | ||||
| 			this.subscribingNotes[payload.id] = 0; | ||||
| 		} | ||||
| 		const current = this.subscribingNotes[payload.id] ?? 0; | ||||
| 		const updated = current + 1; | ||||
| 		this.subscribingNotes[payload.id] = updated; | ||||
|  | ||||
| 		this.subscribingNotes[payload.id]++; | ||||
|  | ||||
| 		if (this.subscribingNotes[payload.id] === 1) { | ||||
| 		if (updated === 1) { | ||||
| 			this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage); | ||||
| 		} | ||||
| 	} | ||||
| @@ -189,11 +190,14 @@ export default class Connection { | ||||
| 	 * 投稿購読解除要求時 | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	private onUnsubscribeNote(payload: any) { | ||||
| 		if (!payload.id) return; | ||||
| 	private onUnsubscribeNote(payload: JsonObject) { | ||||
| 		if (!payload.id || typeof payload.id !== 'string') return; | ||||
|  | ||||
| 		this.subscribingNotes[payload.id]--; | ||||
| 		if (this.subscribingNotes[payload.id] <= 0) { | ||||
| 		const current = this.subscribingNotes[payload.id]; | ||||
| 		if (current == null) return; | ||||
| 		const updated = current - 1; | ||||
| 		this.subscribingNotes[payload.id] = updated; | ||||
| 		if (updated <= 0) { | ||||
| 			delete this.subscribingNotes[payload.id]; | ||||
| 			this.subscriber.off(`noteStream:${payload.id}`, this.onNoteStreamMessage); | ||||
| 		} | ||||
| @@ -212,17 +216,22 @@ export default class Connection { | ||||
| 	 * チャンネル接続要求時 | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	private onChannelConnectRequested(payload: any) { | ||||
| 	private onChannelConnectRequested(payload: JsonObject) { | ||||
| 		const { channel, id, params, pong } = payload; | ||||
| 		this.connectChannel(id, params, channel, pong); | ||||
| 		if (typeof id !== 'string') return; | ||||
| 		if (typeof channel !== 'string') return; | ||||
| 		if (typeof pong !== 'boolean' && typeof pong !== 'undefined' && pong !== null) return; | ||||
| 		if (typeof params !== 'undefined' && (typeof params !== 'object' || params === null || Array.isArray(params))) return; | ||||
| 		this.connectChannel(id, params, channel, pong ?? undefined); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * チャンネル切断要求時 | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	private onChannelDisconnectRequested(payload: any) { | ||||
| 	private onChannelDisconnectRequested(payload: JsonObject) { | ||||
| 		const { id } = payload; | ||||
| 		if (typeof id !== 'string') return; | ||||
| 		this.disconnectChannel(id); | ||||
| 	} | ||||
|  | ||||
| @@ -230,7 +239,7 @@ export default class Connection { | ||||
| 	 * クライアントにメッセージ送信 | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	public sendMessageToWs(type: string, payload: any) { | ||||
| 	public sendMessageToWs(type: string, payload: JsonObject) { | ||||
| 		this.wsConnection.send(JSON.stringify({ | ||||
| 			type: type, | ||||
| 			body: payload, | ||||
| @@ -241,7 +250,7 @@ export default class Connection { | ||||
| 	 * チャンネルに接続 | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	public connectChannel(id: string, params: any, channel: string, pong = false) { | ||||
| 	public connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) { | ||||
| 		const channelService = this.channelsService.getChannelService(channel); | ||||
|  | ||||
| 		if (channelService.requireCredential && this.user == null) { | ||||
| @@ -288,7 +297,11 @@ export default class Connection { | ||||
| 	 * @param data メッセージ | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	private onChannelMessageRequested(data: any) { | ||||
| 	private onChannelMessageRequested(data: JsonObject) { | ||||
| 		if (typeof data.id !== 'string') return; | ||||
| 		if (typeof data.type !== 'string') return; | ||||
| 		if (typeof data.body === 'undefined') return; | ||||
|  | ||||
| 		const channel = this.channels.find(c => c.id === data.id); | ||||
| 		if (channel != null && channel.onMessage != null) { | ||||
| 			channel.onMessage(data.type, data.body); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { isInstanceMuted } from '@/misc/is-instance-muted.js'; | ||||
| import { isUserRelated } from '@/misc/is-user-related.js'; | ||||
| import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; | ||||
| import type { Packed } from '@/misc/json-schema.js'; | ||||
| import type { JsonObject, JsonValue } from '@/misc/json-value.js'; | ||||
| import type Connection from './Connection.js'; | ||||
|  | ||||
| /** | ||||
| @@ -81,10 +82,12 @@ export default abstract class Channel { | ||||
| 		this.connection = connection; | ||||
| 	} | ||||
|  | ||||
| 	public send(payload: { type: string, body: JsonValue }): void | ||||
| 	public send(type: string, payload: JsonValue): void | ||||
| 	@bindThis | ||||
| 	public send(typeOrPayload: any, payload?: any) { | ||||
| 		const type = payload === undefined ? typeOrPayload.type : typeOrPayload; | ||||
| 		const body = payload === undefined ? typeOrPayload.body : payload; | ||||
| 	public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) { | ||||
| 		const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string); | ||||
| 		const body = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).body : payload; | ||||
|  | ||||
| 		this.connection.sendMessageToWs('channel', { | ||||
| 			id: this.id, | ||||
| @@ -93,11 +96,11 @@ export default abstract class Channel { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	public abstract init(params: any): void; | ||||
| 	public abstract init(params: JsonObject): void; | ||||
|  | ||||
| 	public dispose?(): void; | ||||
|  | ||||
| 	public onMessage?(type: string, body: any): void; | ||||
| 	public onMessage?(type: string, body: JsonValue): void; | ||||
| } | ||||
|  | ||||
| export type MiChannelService<T extends boolean> = { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class AdminChannel extends Channel { | ||||
| @@ -14,7 +15,7 @@ class AdminChannel extends Channel { | ||||
| 	public static kind = 'read:admin:stream'; | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 	public async init(params: JsonObject) { | ||||
| 		// Subscribe admin stream | ||||
| 		this.subscriber.on(`adminStream:${this.user!.id}`, data => { | ||||
| 			this.send(data); | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common'; | ||||
| import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { GlobalEvents } from '@/core/GlobalEventService.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class AntennaChannel extends Channel { | ||||
| @@ -27,8 +28,9 @@ class AntennaChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 		this.antennaId = params.antennaId as string; | ||||
| 	public async init(params: JsonObject) { | ||||
| 		if (typeof params.antennaId !== 'string') return; | ||||
| 		this.antennaId = params.antennaId; | ||||
|  | ||||
| 		// Subscribe stream | ||||
| 		this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import type { Packed } from '@/misc/json-schema.js'; | ||||
| import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class ChannelChannel extends Channel { | ||||
| @@ -27,8 +28,9 @@ class ChannelChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 		this.channelId = params.channelId as string; | ||||
| 	public async init(params: JsonObject) { | ||||
| 		if (typeof params.channelId !== 'string') return; | ||||
| 		this.channelId = params.channelId; | ||||
|  | ||||
| 		// Subscribe stream | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class DriveChannel extends Channel { | ||||
| @@ -14,7 +15,7 @@ class DriveChannel extends Channel { | ||||
| 	public static kind = 'read:account'; | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 	public async init(params: JsonObject) { | ||||
| 		// Subscribe drive stream | ||||
| 		this.subscriber.on(`driveStream:${this.user!.id}`, data => { | ||||
| 			this.send(data); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { RoleService } from '@/core/RoleService.js'; | ||||
| import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class GlobalTimelineChannel extends Channel { | ||||
| @@ -32,12 +33,12 @@ class GlobalTimelineChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 	public async init(params: JsonObject) { | ||||
| 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); | ||||
| 		if (!policies.gtlAvailable) return; | ||||
|  | ||||
| 		this.withRenotes = params.withRenotes ?? true; | ||||
| 		this.withFiles = params.withFiles ?? false; | ||||
| 		this.withRenotes = !!(params.withRenotes ?? true); | ||||
| 		this.withFiles = !!(params.withFiles ?? false); | ||||
|  | ||||
| 		// Subscribe events | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import type { Packed } from '@/misc/json-schema.js'; | ||||
| import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class HashtagChannel extends Channel { | ||||
| @@ -28,11 +29,11 @@ class HashtagChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 	public async init(params: JsonObject) { | ||||
| 		if (!Array.isArray(params.q)) return; | ||||
| 		if (!params.q.every(x => Array.isArray(x) && x.every(y => typeof y === 'string'))) return; | ||||
| 		this.q = params.q; | ||||
|  | ||||
| 		if (this.q == null) return; | ||||
|  | ||||
| 		// Subscribe stream | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
| 	} | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import type { Packed } from '@/misc/json-schema.js'; | ||||
| import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class HomeTimelineChannel extends Channel { | ||||
| @@ -29,9 +30,9 @@ class HomeTimelineChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 		this.withRenotes = params.withRenotes ?? true; | ||||
| 		this.withFiles = params.withFiles ?? false; | ||||
| 	public async init(params: JsonObject) { | ||||
| 		this.withRenotes = !!(params.withRenotes ?? true); | ||||
| 		this.withFiles = !!(params.withFiles ?? false); | ||||
|  | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
| 	} | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { RoleService } from '@/core/RoleService.js'; | ||||
| import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class HybridTimelineChannel extends Channel { | ||||
| @@ -34,13 +35,13 @@ class HybridTimelineChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any): Promise<void> { | ||||
| 	public async init(params: JsonObject): Promise<void> { | ||||
| 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); | ||||
| 		if (!policies.ltlAvailable) return; | ||||
|  | ||||
| 		this.withRenotes = params.withRenotes ?? true; | ||||
| 		this.withReplies = params.withReplies ?? false; | ||||
| 		this.withFiles = params.withFiles ?? false; | ||||
| 		this.withRenotes = !!(params.withRenotes ?? true); | ||||
| 		this.withReplies = !!(params.withReplies ?? false); | ||||
| 		this.withFiles = !!(params.withFiles ?? false); | ||||
|  | ||||
| 		// Subscribe events | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { RoleService } from '@/core/RoleService.js'; | ||||
| import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class LocalTimelineChannel extends Channel { | ||||
| @@ -33,13 +34,13 @@ class LocalTimelineChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 	public async init(params: JsonObject) { | ||||
| 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); | ||||
| 		if (!policies.ltlAvailable) return; | ||||
|  | ||||
| 		this.withRenotes = params.withRenotes ?? true; | ||||
| 		this.withReplies = params.withReplies ?? false; | ||||
| 		this.withFiles = params.withFiles ?? false; | ||||
| 		this.withRenotes = !!(params.withRenotes ?? true); | ||||
| 		this.withReplies = !!(params.withReplies ?? false); | ||||
| 		this.withFiles = !!(params.withFiles ?? false); | ||||
|  | ||||
| 		// Subscribe events | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common'; | ||||
| import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js'; | ||||
| import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class MainChannel extends Channel { | ||||
| @@ -25,7 +26,7 @@ class MainChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 	public async init(params: JsonObject) { | ||||
| 		// Subscribe main stream channel | ||||
| 		this.subscriber.on(`mainStream:${this.user!.id}`, async data => { | ||||
| 			switch (data.type) { | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| import Xev from 'xev'; | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { JsonObject, JsonValue } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| const ev = new Xev(); | ||||
| @@ -22,19 +23,22 @@ class QueueStatsChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 	public async init(params: JsonObject) { | ||||
| 		ev.addListener('queueStats', this.onStats); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	private onStats(stats: any) { | ||||
| 	private onStats(stats: JsonObject) { | ||||
| 		this.send('stats', stats); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public onMessage(type: string, body: any) { | ||||
| 	public onMessage(type: string, body: JsonValue) { | ||||
| 		switch (type) { | ||||
| 			case 'requestLog': | ||||
| 				if (typeof body !== 'object' || body === null || Array.isArray(body)) return; | ||||
| 				if (typeof body.id !== 'string') return; | ||||
| 				if (typeof body.length !== 'number') return; | ||||
| 				ev.once(`queueStatsLog:${body.id}`, statsLog => { | ||||
| 					this.send('statsLog', statsLog); | ||||
| 				}); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { ReversiService } from '@/core/ReversiService.js'; | ||||
| import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js'; | ||||
| import type { JsonObject, JsonValue } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class ReversiGameChannel extends Channel { | ||||
| @@ -28,25 +29,41 @@ class ReversiGameChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 		this.gameId = params.gameId as string; | ||||
| 	public async init(params: JsonObject) { | ||||
| 		if (typeof params.gameId !== 'string') return; | ||||
| 		this.gameId = params.gameId; | ||||
|  | ||||
| 		this.subscriber.on(`reversiGameStream:${this.gameId}`, this.send); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public onMessage(type: string, body: any) { | ||||
| 	public onMessage(type: string, body: JsonValue) { | ||||
| 		switch (type) { | ||||
| 			case 'ready': this.ready(body); break; | ||||
| 			case 'updateSettings': this.updateSettings(body.key, body.value); break; | ||||
| 			case 'cancel': this.cancelGame(); break; | ||||
| 			case 'putStone': this.putStone(body.pos, body.id); break; | ||||
| 			case 'ready': | ||||
| 				if (typeof body !== 'boolean') return; | ||||
| 				this.ready(body); | ||||
| 				break; | ||||
| 			case 'updateSettings': | ||||
| 				if (typeof body !== 'object' || body === null || Array.isArray(body)) return; | ||||
| 				if (typeof body.key !== 'string') return; | ||||
| 				if (typeof body.value !== 'object' || body.value === null || Array.isArray(body.value)) return; | ||||
| 				this.updateSettings(body.key, body.value); | ||||
| 				break; | ||||
| 			case 'cancel': | ||||
| 				this.cancelGame(); | ||||
| 				break; | ||||
| 			case 'putStone': | ||||
| 				if (typeof body !== 'object' || body === null || Array.isArray(body)) return; | ||||
| 				if (typeof body.pos !== 'number') return; | ||||
| 				if (typeof body.id !== 'string') return; | ||||
| 				this.putStone(body.pos, body.id); | ||||
| 				break; | ||||
| 			case 'claimTimeIsUp': this.claimTimeIsUp(); break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	private async updateSettings(key: string, value: any) { | ||||
| 	private async updateSettings(key: string, value: JsonObject) { | ||||
| 		if (this.user == null) return; | ||||
|  | ||||
| 		this.reversiService.updateSettings(this.gameId!, this.user, key, value); | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class ReversiChannel extends Channel { | ||||
| @@ -21,7 +22,7 @@ class ReversiChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 	public async init(params: JsonObject) { | ||||
| 		this.subscriber.on(`reversiStream:${this.user!.id}`, this.send); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { RoleService } from '@/core/RoleService.js'; | ||||
| import type { GlobalEvents } from '@/core/GlobalEventService.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class RoleTimelineChannel extends Channel { | ||||
| @@ -28,8 +29,9 @@ class RoleTimelineChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 		this.roleId = params.roleId as string; | ||||
| 	public async init(params: JsonObject) { | ||||
| 		if (typeof params.roleId !== 'string') return; | ||||
| 		this.roleId = params.roleId; | ||||
|  | ||||
| 		this.subscriber.on(`roleTimelineStream:${this.roleId}`, this.onEvent); | ||||
| 	} | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| import Xev from 'xev'; | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { JsonObject, JsonValue } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| const ev = new Xev(); | ||||
| @@ -22,19 +23,20 @@ class ServerStatsChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 	public async init(params: JsonObject) { | ||||
| 		ev.addListener('serverStats', this.onStats); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	private onStats(stats: any) { | ||||
| 	private onStats(stats: JsonObject) { | ||||
| 		this.send('stats', stats); | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public onMessage(type: string, body: any) { | ||||
| 	public onMessage(type: string, body: JsonValue) { | ||||
| 		switch (type) { | ||||
| 			case 'requestLog': | ||||
| 				if (typeof body !== 'object' || body === null || Array.isArray(body)) return; | ||||
| 				ev.once(`serverStatsLog:${body.id}`, statsLog => { | ||||
| 					this.send('statsLog', statsLog); | ||||
| 				}); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; | ||||
| import type { JsonObject } from '@/misc/json-value.js'; | ||||
| import Channel, { type MiChannelService } from '../channel.js'; | ||||
|  | ||||
| class UserListChannel extends Channel { | ||||
| @@ -36,10 +37,11 @@ class UserListChannel extends Channel { | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 		this.listId = params.listId as string; | ||||
| 		this.withFiles = params.withFiles ?? false; | ||||
| 		this.withRenotes = params.withRenotes ?? true; | ||||
| 	public async init(params: JsonObject) { | ||||
| 		if (typeof params.listId !== 'string') return; | ||||
| 		this.listId = params.listId; | ||||
| 		this.withFiles = !!(params.withFiles ?? false); | ||||
| 		this.withRenotes = !!(params.withRenotes ?? true); | ||||
|  | ||||
| 		// Check existence and owner | ||||
| 		const listExist = await this.userListsRepository.exists({ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 anatawa12
					anatawa12