sharkey/packages/backend/src/server/api/StreamingApiServerService.ts

145 lines
3.9 KiB
TypeScript
Raw Normal View History

2022-09-18 03:27:08 +09:00
import { EventEmitter } from 'events';
import { Inject, Injectable } from '@nestjs/common';
2023-04-14 13:50:05 +09:00
import * as Redis from 'ioredis';
import * as WebSocket from 'ws';
2022-09-18 03:27:08 +09:00
import { DI } from '@/di-symbols.js';
import type { UsersRepository, AccessToken } from '@/models/index.js';
2022-09-21 05:33:11 +09:00
import type { Config } from '@/config.js';
2022-09-18 03:27:08 +09:00
import { NoteReadService } from '@/core/NoteReadService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
2023-04-05 10:21:10 +09:00
import { CacheService } from '@/core/CacheService.js';
import { LocalUser } from '@/models/entities/User';
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
2022-09-18 03:27:08 +09:00
import MainStreamConnection from './stream/index.js';
import { ChannelsService } from './stream/ChannelsService.js';
import type * as http from 'node:http';
@Injectable()
export class StreamingApiServerService {
#wss: WebSocket.WebSocketServer;
2022-09-18 03:27:08 +09:00
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,
2022-09-18 03:27:08 +09:00
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
2023-04-05 10:21:10 +09:00
private cacheService: CacheService,
2022-09-18 03:27:08 +09:00
private noteReadService: NoteReadService,
private authenticateService: AuthenticateService,
private channelsService: ChannelsService,
private notificationService: NotificationService,
) {
}
@bindThis
public attach(server: http.Server): void {
this.#wss = new WebSocket.WebSocketServer({
noServer: true,
2022-09-18 03:27:08 +09:00
});
server.on('upgrade', async (request, socket, head) => {
if (request.url == null) {
socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
socket.destroy();
2022-09-18 03:27:08 +09:00
return;
}
const q = new URL(request.url, `http://${request.headers.host}`).searchParams;
2022-09-18 03:27:08 +09:00
let user: LocalUser | null = null;
let app: AccessToken | null = null;
try {
[user, app] = await this.authenticateService.authenticate(q.get('i'));
} catch (e) {
if (e instanceof AuthenticationError) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
} else {
socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n');
}
socket.destroy();
return;
2022-09-18 03:27:08 +09:00
}
if (user?.isSuspended) {
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
socket.destroy();
return;
}
2022-09-18 03:27:08 +09:00
const stream = new MainStreamConnection(
2022-09-18 03:27:08 +09:00
this.channelsService,
this.noteReadService,
this.notificationService,
2023-04-05 10:21:10 +09:00
this.cacheService,
user, app,
2022-09-18 03:27:08 +09:00
);
await stream.init();
this.#wss.handleUpgrade(request, socket, head, (ws) => {
this.#wss.emit('connection', ws, request, {
stream, user, app,
});
});
});
this.#wss.on('connection', async (connection: WebSocket.WebSocket, request: http.IncomingMessage, ctx: {
stream: MainStreamConnection,
user: LocalUser | null;
app: AccessToken | null
}) => {
const { stream, user, app } = ctx;
const ev = new EventEmitter();
async function onRedisMessage(_: string, data: string): Promise<void> {
const parsed = JSON.parse(data);
ev.emit(parsed.channel, parsed.message);
}
2023-04-05 10:21:10 +09:00
this.redisForSub.on('message', onRedisMessage);
2023-04-05 10:21:10 +09:00
await stream.listen(ev, connection);
2023-04-05 10:21:10 +09:00
2022-09-18 03:27:08 +09:00
const intervalId = user ? setInterval(() => {
this.usersRepository.update(user.id, {
lastActiveDate: new Date(),
});
}, 1000 * 60 * 5) : null;
if (user) {
this.usersRepository.update(user.id, {
lastActiveDate: new Date(),
});
}
connection.once('close', () => {
ev.removeAllListeners();
stream.dispose();
this.redisForSub.off('message', onRedisMessage);
2022-09-18 03:27:08 +09:00
if (intervalId) clearInterval(intervalId);
});
connection.on('message', async (data) => {
if (data.toString() === 'ping') {
2022-09-18 03:27:08 +09:00
connection.send('pong');
}
});
});
}
@bindThis
public detach(): Promise<void> {
return new Promise((resolve) => {
this.#wss.close(() => resolve());
});
}
2022-09-18 03:27:08 +09:00
}