wip
This commit is contained in:
		
							parent
							
								
									b53a6bfe0c
								
							
						
					
					
						commit
						cb0e275db9
					
				|  | @ -7,6 +7,12 @@ | |||
|     "": { | ||||
|       "name": "misskey-js", | ||||
|       "version": "0.0.0", | ||||
|       "dependencies": { | ||||
|         "@vue/reactivity": "^3.0.11", | ||||
|         "autobind-decorator": "^2.4.0", | ||||
|         "eventemitter3": "^4.0.7", | ||||
|         "reconnecting-websocket": "^4.4.0" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
|         "@types/mocha": "8.2.x", | ||||
|         "@types/node": "14.14.x", | ||||
|  | @ -199,6 +205,19 @@ | |||
|       "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/@vue/reactivity": { | ||||
|       "version": "3.0.11", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.0.11.tgz", | ||||
|       "integrity": "sha512-SKM3YKxtXHBPMf7yufXeBhCZ4XZDKP9/iXeQSC8bBO3ivBuzAi4aZi0bNoeE2IF2iGfP/AHEt1OU4ARj4ao/Xw==", | ||||
|       "dependencies": { | ||||
|         "@vue/shared": "3.0.11" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@vue/shared": { | ||||
|       "version": "3.0.11", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.11.tgz", | ||||
|       "integrity": "sha512-b+zB8A2so8eCE0JsxjL24J7vdGl8rzPQ09hZNhystm+KqSbKcAej1A+Hbva1rCMmTTqA+hFnUSDc5kouEo0JzA==" | ||||
|     }, | ||||
|     "node_modules/ansi-align": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", | ||||
|  | @ -349,6 +368,15 @@ | |||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/autobind-decorator": { | ||||
|       "version": "2.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.4.0.tgz", | ||||
|       "integrity": "sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==", | ||||
|       "engines": { | ||||
|         "node": ">=8.10", | ||||
|         "npm": ">=6.4.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/balanced-match": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", | ||||
|  | @ -866,6 +894,11 @@ | |||
|       "integrity": "sha512-Wnn0ETzE2v2UT0OdRCcdMNPkQtbzyZr3pPPXnkreP0l6ZJaKqnl88dL1DqZ6nCCZZwDGBAnN0Y+nCvGxxLPQLQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/eventemitter3": { | ||||
|       "version": "4.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", | ||||
|       "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" | ||||
|     }, | ||||
|     "node_modules/fast-glob": { | ||||
|       "version": "3.2.5", | ||||
|       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", | ||||
|  | @ -2082,6 +2115,11 @@ | |||
|         "node": ">=8.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/reconnecting-websocket": { | ||||
|       "version": "4.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz", | ||||
|       "integrity": "sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==" | ||||
|     }, | ||||
|     "node_modules/redent": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", | ||||
|  | @ -3006,6 +3044,19 @@ | |||
|       "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "@vue/reactivity": { | ||||
|       "version": "3.0.11", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.0.11.tgz", | ||||
|       "integrity": "sha512-SKM3YKxtXHBPMf7yufXeBhCZ4XZDKP9/iXeQSC8bBO3ivBuzAi4aZi0bNoeE2IF2iGfP/AHEt1OU4ARj4ao/Xw==", | ||||
|       "requires": { | ||||
|         "@vue/shared": "3.0.11" | ||||
|       } | ||||
|     }, | ||||
|     "@vue/shared": { | ||||
|       "version": "3.0.11", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.11.tgz", | ||||
|       "integrity": "sha512-b+zB8A2so8eCE0JsxjL24J7vdGl8rzPQ09hZNhystm+KqSbKcAej1A+Hbva1rCMmTTqA+hFnUSDc5kouEo0JzA==" | ||||
|     }, | ||||
|     "ansi-align": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", | ||||
|  | @ -3119,6 +3170,11 @@ | |||
|       "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "autobind-decorator": { | ||||
|       "version": "2.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.4.0.tgz", | ||||
|       "integrity": "sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==" | ||||
|     }, | ||||
|     "balanced-match": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", | ||||
|  | @ -3525,6 +3581,11 @@ | |||
|       "integrity": "sha512-Wnn0ETzE2v2UT0OdRCcdMNPkQtbzyZr3pPPXnkreP0l6ZJaKqnl88dL1DqZ6nCCZZwDGBAnN0Y+nCvGxxLPQLQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "eventemitter3": { | ||||
|       "version": "4.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", | ||||
|       "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" | ||||
|     }, | ||||
|     "fast-glob": { | ||||
|       "version": "3.2.5", | ||||
|       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", | ||||
|  | @ -4424,6 +4485,11 @@ | |||
|         "picomatch": "^2.2.1" | ||||
|       } | ||||
|     }, | ||||
|     "reconnecting-websocket": { | ||||
|       "version": "4.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz", | ||||
|       "integrity": "sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==" | ||||
|     }, | ||||
|     "redent": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", | ||||
|  |  | |||
|  | @ -24,5 +24,11 @@ | |||
|   }, | ||||
|   "files": [ | ||||
|     "built" | ||||
|   ] | ||||
|   ], | ||||
|   "dependencies": { | ||||
|     "@vue/reactivity": "^3.0.11", | ||||
|     "autobind-decorator": "^2.4.0", | ||||
|     "eventemitter3": "^4.0.7", | ||||
|     "reconnecting-websocket": "^4.4.0" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,42 @@ | |||
| import { Endpoints } from './endpoints'; | ||||
| 
 | ||||
| export class MisskeyClient { | ||||
| 	public i: { token: string; } | null = null; | ||||
| 	private apiUrl: string; | ||||
| 
 | ||||
| 	constructor(opts: { | ||||
| 		apiUrl: MisskeyClient['apiUrl']; | ||||
| 	}) { | ||||
| 		this.apiUrl = opts.apiUrl; | ||||
| 	} | ||||
| 
 | ||||
| 	public api<E extends keyof Endpoints>( | ||||
| 		endpoint: E, data: Endpoints[E]['req'] = {}, token?: string | null | undefined | ||||
| 	): Promise<Endpoints[E]['res']> { | ||||
| 		const promise = new Promise<Endpoints[E]['res']>((resolve, reject) => { | ||||
| 			// Append a credential
 | ||||
| 			if (this.i) (data as Record<string, any>).i = this.i.token; | ||||
| 			if (token !== undefined) (data as Record<string, any>).i = token; | ||||
| 	 | ||||
| 			// Send request
 | ||||
| 			fetch(endpoint.indexOf('://') > -1 ? endpoint : `${this.apiUrl}/${endpoint}`, { | ||||
| 				method: 'POST', | ||||
| 				body: JSON.stringify(data), | ||||
| 				credentials: 'omit', | ||||
| 				cache: 'no-cache' | ||||
| 			}).then(async (res) => { | ||||
| 				const body = res.status === 204 ? null : await res.json(); | ||||
| 	 | ||||
| 				if (res.status === 200) { | ||||
| 					resolve(body); | ||||
| 				} else if (res.status === 204) { | ||||
| 					resolve(null); | ||||
| 				} else { | ||||
| 					reject(body.error); | ||||
| 				} | ||||
| 			}).catch(reject); | ||||
| 		}); | ||||
| 	 | ||||
| 		return promise; | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,8 @@ | |||
| import { Instance, User } from './types'; | ||||
| 
 | ||||
| type TODO = Record<string, any>; | ||||
| 
 | ||||
| export type Endpoints = { | ||||
| 	'i': { req: TODO; res: User; }; | ||||
| 	'meta': { req: { detail?: boolean; }; res: Instance; }; | ||||
| }; | ||||
|  | @ -0,0 +1,322 @@ | |||
| import autobind from 'autobind-decorator'; | ||||
| import { EventEmitter } from 'eventemitter3'; | ||||
| import ReconnectingWebsocket from 'reconnecting-websocket'; | ||||
| import { stringify } from 'querystring'; | ||||
| import { markRaw } from '@vue/reactivity'; | ||||
| 
 | ||||
| function urlQuery(obj: {}): string { | ||||
| 	return stringify(Object.entries(obj) | ||||
| 		.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined) | ||||
| 		.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>)); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Misskey stream connection | ||||
|  */ | ||||
| export default class Stream extends EventEmitter { | ||||
| 	private stream: ReconnectingWebsocket; | ||||
| 	public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing'; | ||||
| 	private sharedConnectionPools: Pool[] = []; | ||||
| 	private sharedConnections: SharedConnection[] = []; | ||||
| 	private nonSharedConnections: NonSharedConnection[] = []; | ||||
| 
 | ||||
| 	constructor(wsUrl: string, user: { token: string; } | null, options?: { | ||||
| 	}) { | ||||
| 		super(); | ||||
| 
 | ||||
| 		const query = urlQuery({ | ||||
| 			i: user?.token, | ||||
| 			_t: Date.now(), | ||||
| 		}); | ||||
| 
 | ||||
| 		this.stream = new ReconnectingWebsocket(`${wsUrl}?${query}`, '', { minReconnectionDelay: 1 }); // https://github.com/pladaria/reconnecting-websocket/issues/91
 | ||||
| 		this.stream.addEventListener('open', this.onOpen); | ||||
| 		this.stream.addEventListener('close', this.onClose); | ||||
| 		this.stream.addEventListener('message', this.onMessage); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public useSharedConnection(channel: string, name?: string): SharedConnection { | ||||
| 		let pool = this.sharedConnectionPools.find(p => p.channel === channel); | ||||
| 
 | ||||
| 		if (pool == null) { | ||||
| 			pool = new Pool(this, channel); | ||||
| 			this.sharedConnectionPools.push(pool); | ||||
| 		} | ||||
| 
 | ||||
| 		const connection = markRaw(new SharedConnection(this, channel, pool, name)); | ||||
| 		this.sharedConnections.push(connection); | ||||
| 		return connection; | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public removeSharedConnection(connection: SharedConnection) { | ||||
| 		this.sharedConnections = this.sharedConnections.filter(c => c !== connection); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public removeSharedConnectionPool(pool: Pool) { | ||||
| 		this.sharedConnectionPools = this.sharedConnectionPools.filter(p => p !== pool); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public connectToChannel(channel: string, params?: any): NonSharedConnection { | ||||
| 		const connection = markRaw(new NonSharedConnection(this, channel, params)); | ||||
| 		this.nonSharedConnections.push(connection); | ||||
| 		return connection; | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public disconnectToChannel(connection: NonSharedConnection) { | ||||
| 		this.nonSharedConnections = this.nonSharedConnections.filter(c => c !== connection); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Callback of when open connection | ||||
| 	 */ | ||||
| 	@autobind | ||||
| 	private onOpen() { | ||||
| 		const isReconnect = this.state === 'reconnecting'; | ||||
| 
 | ||||
| 		this.state = 'connected'; | ||||
| 		this.emit('_connected_'); | ||||
| 
 | ||||
| 		// チャンネル再接続
 | ||||
| 		if (isReconnect) { | ||||
| 			for (const p of this.sharedConnectionPools) | ||||
| 				p.connect(); | ||||
| 			for (const c of this.nonSharedConnections) | ||||
| 				c.connect(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Callback of when close connection | ||||
| 	 */ | ||||
| 	@autobind | ||||
| 	private onClose() { | ||||
| 		if (this.state === 'connected') { | ||||
| 			this.state = 'reconnecting'; | ||||
| 			this.emit('_disconnected_'); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Callback of when received a message from connection | ||||
| 	 */ | ||||
| 	@autobind | ||||
| 	private onMessage(message: { data: string; }) { | ||||
| 		const { type, body } = JSON.parse(message.data); | ||||
| 
 | ||||
| 		if (type === 'channel') { | ||||
| 			const id = body.id; | ||||
| 
 | ||||
| 			let connections: Connection[]; | ||||
| 
 | ||||
| 			connections = this.sharedConnections.filter(c => c.id === id); | ||||
| 
 | ||||
| 			if (connections.length === 0) { | ||||
| 				const found = this.nonSharedConnections.find(c => c.id === id); | ||||
| 				if (found) { | ||||
| 					connections = [found]; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			for (const c of connections.filter(c => c != null)) { | ||||
| 				c.emit(body.type, Object.freeze(body.body)); | ||||
| 				c.inCount++; | ||||
| 			} | ||||
| 		} else { | ||||
| 			this.emit(type, Object.freeze(body)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Send a message to connection | ||||
| 	 */ | ||||
| 	@autobind | ||||
| 	public send(typeOrPayload: any, payload?: any) { | ||||
| 		const data = payload === undefined ? typeOrPayload : { | ||||
| 			type: typeOrPayload, | ||||
| 			body: payload | ||||
| 		}; | ||||
| 
 | ||||
| 		this.stream.send(JSON.stringify(data)); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Close this connection | ||||
| 	 */ | ||||
| 	@autobind | ||||
| 	public close() { | ||||
| 		this.stream.removeEventListener('open', this.onOpen); | ||||
| 		this.stream.removeEventListener('message', this.onMessage); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| let idCounter = 0; | ||||
| 
 | ||||
| class Pool { | ||||
| 	public channel: string; | ||||
| 	public id: string; | ||||
| 	protected stream: Stream; | ||||
| 	public users = 0; | ||||
| 	private disposeTimerId: any; | ||||
| 	private isConnected = false; | ||||
| 
 | ||||
| 	constructor(stream: Stream, channel: string) { | ||||
| 		this.channel = channel; | ||||
| 		this.stream = stream; | ||||
| 
 | ||||
| 		this.id = (++idCounter).toString(); | ||||
| 
 | ||||
| 		this.stream.on('_disconnected_', this.onStreamDisconnected); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	private onStreamDisconnected() { | ||||
| 		this.isConnected = false; | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public inc() { | ||||
| 		if (this.users === 0 && !this.isConnected) { | ||||
| 			this.connect(); | ||||
| 		} | ||||
| 
 | ||||
| 		this.users++; | ||||
| 
 | ||||
| 		// タイマー解除
 | ||||
| 		if (this.disposeTimerId) { | ||||
| 			clearTimeout(this.disposeTimerId); | ||||
| 			this.disposeTimerId = null; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public dec() { | ||||
| 		this.users--; | ||||
| 
 | ||||
| 		// そのコネクションの利用者が誰もいなくなったら
 | ||||
| 		if (this.users === 0) { | ||||
| 			// また直ぐに再利用される可能性があるので、一定時間待ち、
 | ||||
| 			// 新たな利用者が現れなければコネクションを切断する
 | ||||
| 			this.disposeTimerId = setTimeout(() => { | ||||
| 				this.disconnect(); | ||||
| 			}, 3000); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public connect() { | ||||
| 		if (this.isConnected) return; | ||||
| 		this.isConnected = true; | ||||
| 		this.stream.send('connect', { | ||||
| 			channel: this.channel, | ||||
| 			id: this.id | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	private disconnect() { | ||||
| 		this.stream.off('_disconnected_', this.onStreamDisconnected); | ||||
| 		this.stream.send('disconnect', { id: this.id }); | ||||
| 		this.stream.removeSharedConnectionPool(this); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| abstract class Connection extends EventEmitter { | ||||
| 	public channel: string; | ||||
| 	protected stream: Stream; | ||||
| 	public abstract id: string; | ||||
| 
 | ||||
| 	public name?: string; // for debug
 | ||||
| 	public inCount: number = 0; // for debug
 | ||||
| 	public outCount: number = 0; // for debug
 | ||||
| 
 | ||||
| 	constructor(stream: Stream, channel: string, name?: string) { | ||||
| 		super(); | ||||
| 
 | ||||
| 		this.stream = stream; | ||||
| 		this.channel = channel; | ||||
| 		this.name = name; | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public send(id: string, typeOrPayload: any, payload?: any) { | ||||
| 		const type = payload === undefined ? typeOrPayload.type : typeOrPayload; | ||||
| 		const body = payload === undefined ? typeOrPayload.body : payload; | ||||
| 
 | ||||
| 		this.stream.send('ch', { | ||||
| 			id: id, | ||||
| 			type: type, | ||||
| 			body: body | ||||
| 		}); | ||||
| 
 | ||||
| 		this.outCount++; | ||||
| 	} | ||||
| 
 | ||||
| 	public abstract dispose(): void; | ||||
| } | ||||
| 
 | ||||
| class SharedConnection extends Connection { | ||||
| 	private pool: Pool; | ||||
| 
 | ||||
| 	public get id(): string { | ||||
| 		return this.pool.id; | ||||
| 	} | ||||
| 
 | ||||
| 	constructor(stream: Stream, channel: string, pool: Pool, name?: string) { | ||||
| 		super(stream, channel, name); | ||||
| 
 | ||||
| 		this.pool = pool; | ||||
| 		this.pool.inc(); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public send(typeOrPayload: any, payload?: any) { | ||||
| 		super.send(this.pool.id, typeOrPayload, payload); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public dispose() { | ||||
| 		this.pool.dec(); | ||||
| 		this.removeAllListeners(); | ||||
| 		this.stream.removeSharedConnection(this); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| class NonSharedConnection extends Connection { | ||||
| 	public id: string; | ||||
| 	protected params: any; | ||||
| 
 | ||||
| 	constructor(stream: Stream, channel: string, params?: any) { | ||||
| 		super(stream, channel); | ||||
| 
 | ||||
| 		this.params = params; | ||||
| 		this.id = (++idCounter).toString(); | ||||
| 
 | ||||
| 		this.connect(); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public connect() { | ||||
| 		this.stream.send('connect', { | ||||
| 			channel: this.channel, | ||||
| 			id: this.id, | ||||
| 			params: this.params | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public send(typeOrPayload: any, payload?: any) { | ||||
| 		super.send(this.id, typeOrPayload, payload); | ||||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public dispose() { | ||||
| 		this.removeAllListeners(); | ||||
| 		this.stream.send('disconnect', { id: this.id }); | ||||
| 		this.stream.disconnectToChannel(this); | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,73 @@ | |||
| type ID = string; | ||||
| 
 | ||||
| export type User = { | ||||
| 	id: ID; | ||||
| 	username: string; | ||||
| 	host: string | null; | ||||
| 	name: string; | ||||
| 	onlineStatus: 'online' | 'active' | 'offline' | 'unknown'; | ||||
| 	avatarUrl: string; | ||||
| 	avatarBlurhash: string; | ||||
| 	emojis: { | ||||
| 		name: string; | ||||
| 		url: string; | ||||
| 	}[]; | ||||
| }; | ||||
| 
 | ||||
| export type DriveFile = { | ||||
| 	id: ID; | ||||
| 	createdAt: string; | ||||
| 	isSensitive: boolean; | ||||
| 	name: string; | ||||
| 	thumbnailUrl: string; | ||||
| 	url: string; | ||||
| 	type: string; | ||||
| 	size: number; | ||||
| 	md5: string; | ||||
| 	blurhash: string; | ||||
| 	properties: Record<string, any>; | ||||
| }; | ||||
| 
 | ||||
| export type Note = { | ||||
| 	id: ID; | ||||
| 	createdAt: string; | ||||
| 	text: string | null; | ||||
| 	cw: string | null; | ||||
| 	user: User; | ||||
| 	userId: User['id']; | ||||
| 	reply?: Note; | ||||
| 	replyId: Note['id']; | ||||
| 	renote?: Note; | ||||
| 	renoteId: Note['id']; | ||||
| 	files: DriveFile[]; | ||||
| 	fileIds: DriveFile['id'][]; | ||||
| 	visibility: 'public' | 'home' | 'followers' | 'specified'; | ||||
| 	myReaction?: string; | ||||
| 	reactions: Record<string, number>; | ||||
| 	poll?: { | ||||
| 		expiresAt: string | null; | ||||
| 		multiple: boolean; | ||||
| 		choices: { | ||||
| 			isVoted: boolean; | ||||
| 			text: string; | ||||
| 			votes: number; | ||||
| 		}[]; | ||||
| 	}; | ||||
| 	emojis: { | ||||
| 		name: string; | ||||
| 		url: string; | ||||
| 	}[]; | ||||
| }; | ||||
| 
 | ||||
| export type Instance = { | ||||
| 	emojis: { | ||||
| 		category: string; | ||||
| 	}[]; | ||||
| 	ads: { | ||||
| 		id: ID; | ||||
| 		ratio: number; | ||||
| 		place: string; | ||||
| 		url: string; | ||||
| 		imageUrl: string; | ||||
| 	}[]; | ||||
| }; | ||||
|  | @ -8,6 +8,8 @@ | |||
|     "removeComments": true, | ||||
|     "strict": true, | ||||
|     "strictFunctionTypes": true, | ||||
|     "strictNullChecks": true, | ||||
|     "experimentalDecorators": true, | ||||
|     "noImplicitReturns": true, | ||||
|     "esModuleInterop": true, | ||||
|   }, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue