mirror of
https://github.com/sergeyyarkov/educt-server.git
synced 2026-10-11 10:19:20 +03:00
feat: added ws online counter
This commit is contained in:
parent
aa74a7527f
commit
7c678797d7
24
app/Controllers/Ws/OnlineController.ts
Normal file
24
app/Controllers/Ws/OnlineController.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import Ws, { ClientToServerEvents, InterServerEvents, ServerToClientEvents, SocketData } from 'App/Services/WsService';
|
||||
import { Socket } from 'socket.io';
|
||||
import InMemorySessionStore from 'App/Store/SessionStore';
|
||||
import Logger from '@ioc:Adonis/Core/Logger';
|
||||
|
||||
export default class OnlineController {
|
||||
public socket: Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
||||
|
||||
constructor(socket: Socket) {
|
||||
this.socket = socket;
|
||||
this.onConnected();
|
||||
this.onDisconnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* On socket connected
|
||||
*/
|
||||
public async onConnected() {}
|
||||
|
||||
/**
|
||||
* On socket disconnected
|
||||
*/
|
||||
public async onDisconnected() {}
|
||||
}
|
||||
14
app/Helpers/CookieHelper.ts
Normal file
14
app/Helpers/CookieHelper.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export default class CookieHelper {
|
||||
public static parseCookieString(string: string): { [key: string]: string } {
|
||||
const values = string
|
||||
.split(';')
|
||||
.map(v => v.split('='))
|
||||
.reduce((acc, value) => {
|
||||
const [key, ...rest] = value;
|
||||
acc[decodeURIComponent(key.trim())] = decodeURIComponent(rest.join('=').trim());
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
30
app/Helpers/OpaqueTokenHelper.ts
Normal file
30
app/Helpers/OpaqueTokenHelper.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
export default class OpaqueTokenHelper {
|
||||
/**
|
||||
* Parse token
|
||||
*
|
||||
* @param token token
|
||||
* @returns Hashed token with id
|
||||
*/
|
||||
public static parseToken(token: string) {
|
||||
const parts = token.split('.');
|
||||
|
||||
if (parts.length !== 2) {
|
||||
throw new Error('E_INVALID_API_TOKEN');
|
||||
}
|
||||
|
||||
const id = Buffer.from(parts[0], 'base64').toString('utf-8');
|
||||
|
||||
if (!id) {
|
||||
throw new Error('E_INVALID_API_TOKEN');
|
||||
}
|
||||
|
||||
const hashed = crypto.createHash('sha256').update(parts[1]).digest('hex');
|
||||
|
||||
return {
|
||||
hashed,
|
||||
id,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -31,8 +31,7 @@ export default class AuthMiddleware {
|
||||
* driver
|
||||
*/
|
||||
let guardLastAttempted: string | undefined;
|
||||
const authPromises: Promise<boolean>[] = guards.map(guard => auth.use(guard).check());
|
||||
const authResults: boolean[] = await Promise.all(authPromises);
|
||||
const authResults: boolean[] = await Promise.all(guards.map(guard => auth.use(guard).check()));
|
||||
|
||||
for (let i = 0; i < authResults.length; i += 1) {
|
||||
/**
|
||||
@ -54,7 +53,7 @@ export default class AuthMiddleware {
|
||||
|
||||
public async handle(ctx: HttpContextContract, next: () => Promise<void>, customGuards: (keyof GuardsList)[]) {
|
||||
/**
|
||||
* Verify token from cookie and set the authorization header
|
||||
* Set the authorization header for token
|
||||
*/
|
||||
const token = ctx.request.cookie('token');
|
||||
if (token) {
|
||||
|
||||
81
app/Middleware/Ws/Auth.ts
Normal file
81
app/Middleware/Ws/Auth.ts
Normal file
@ -0,0 +1,81 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { nanoid } from 'nanoid';
|
||||
import Redis from '@ioc:Adonis/Addons/Redis';
|
||||
import CookieHelper from 'App/Helpers/CookieHelper';
|
||||
import OpaqueTokenHelper from 'App/Helpers/OpaqueTokenHelper';
|
||||
import Ws, { ClientToServerEvents, InterServerEvents, ServerToClientEvents, SocketData } from 'App/Services/WsService';
|
||||
import { Socket } from 'socket.io';
|
||||
import { ExtendedError } from 'socket.io/dist/namespace';
|
||||
import Logger from '@ioc:Adonis/Core/Logger';
|
||||
|
||||
export default class AuthMiddleware {
|
||||
/**
|
||||
* Checks the cookie token in headers and
|
||||
* validates the opaque token
|
||||
*
|
||||
* @param socket
|
||||
* @param next
|
||||
* @returns void
|
||||
*/
|
||||
public static async authenticate(
|
||||
socket: Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>,
|
||||
next: (err?: ExtendedError | undefined) => void
|
||||
) {
|
||||
/**
|
||||
* Parse and validate token from cookie
|
||||
*/
|
||||
const cookie = socket.handshake.headers?.cookie;
|
||||
|
||||
if (!cookie) {
|
||||
return next(new Error('SocketErrors.MissingParameter'));
|
||||
}
|
||||
|
||||
const token = CookieHelper.parseCookieString(cookie)?.token;
|
||||
|
||||
if (!token) {
|
||||
return next(new Error('SocketErrors.MissingParameter'));
|
||||
}
|
||||
|
||||
const tokenData = JSON.parse(Buffer.from(token.split('.')[0].split(':')[1], 'base64').toString('utf-8'));
|
||||
const parsed = OpaqueTokenHelper.parseToken(tokenData.message);
|
||||
const data = await Redis.get(`api:${parsed.id}`);
|
||||
|
||||
if (!data) {
|
||||
return next(new Error('SocketErrors.BadCredentials'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign existing session to socket data
|
||||
*/
|
||||
const sessionId = socket.handshake.auth.sessionId as string | undefined;
|
||||
|
||||
if (sessionId) {
|
||||
const session = await Ws.sessionStore.findSession(sessionId);
|
||||
|
||||
if (session) {
|
||||
Logger.info(`[Ws]: Attacing session from redis session store.`);
|
||||
|
||||
socket.data.sessionId = sessionId;
|
||||
socket.data.userId = session.userId;
|
||||
socket.data.userName = session.userName;
|
||||
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse data from redis string and create new session
|
||||
*/
|
||||
Logger.info(`[Ws]: Attaching token data from cookie.`);
|
||||
const parsedData = JSON.parse(data);
|
||||
|
||||
/**
|
||||
* Set custom data to socket
|
||||
*/
|
||||
socket.data.sessionId = nanoid();
|
||||
socket.data.userId = parsedData.user_id as string;
|
||||
socket.data.userName = parsedData.userName as string;
|
||||
|
||||
return next();
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||
import Hash from '@ioc:Adonis/Core/Hash';
|
||||
import Logger from '@ioc:Adonis/Core/Logger';
|
||||
|
||||
/**
|
||||
* Datatypes
|
||||
@ -68,17 +67,19 @@ export default class AuthService {
|
||||
const token = await ctx.auth.use(this.authGuard).generate(user, {
|
||||
expiresIn: '1d',
|
||||
userRoles: user.roles.map(role => role.slug),
|
||||
userName: user.fullname,
|
||||
});
|
||||
|
||||
ctx.response.cookie('token', token.token);
|
||||
|
||||
Logger.info(`A token for user with id: "${token.user.id}" was successfully generated.`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: HttpStatusEnum.OK,
|
||||
message: 'Login success.',
|
||||
data: token.toJSON(),
|
||||
data: {
|
||||
userId: user.id,
|
||||
...token.toJSON(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
||||
import IResponse from 'App/Datatypes/Interfaces/IResponse';
|
||||
import HttpStatusEnum from 'App/Datatypes/Enums/HttpStatusEnum';
|
||||
import Database from '@ioc:Adonis/Lucid/Database';
|
||||
import Ws from './WsService';
|
||||
|
||||
@inject()
|
||||
export default class StatService {
|
||||
@ -19,12 +20,15 @@ export default class StatService {
|
||||
`
|
||||
);
|
||||
|
||||
const online = await Ws.sessionStore.getOnlineSessionsCount();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: HttpStatusEnum.OK,
|
||||
message: 'Fetched stat.',
|
||||
data: {
|
||||
...count.rows[0],
|
||||
online,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
124
app/Services/WsService.ts
Normal file
124
app/Services/WsService.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import Redis from '@ioc:Adonis/Addons/Redis';
|
||||
import AdonisServer from '@ioc:Adonis/Core/Server';
|
||||
import { Server } from 'socket.io';
|
||||
|
||||
/**
|
||||
* Controllers
|
||||
*/
|
||||
import OnlineController from 'App/Controllers/Ws/OnlineController';
|
||||
|
||||
/**
|
||||
* Middlewares
|
||||
*/
|
||||
import AuthMiddleware from 'App/Middleware/Ws/Auth';
|
||||
import RedisSessionStore from 'App/Store/SessionStore';
|
||||
|
||||
export interface ServerToClientEvents {
|
||||
/**
|
||||
* User
|
||||
*/
|
||||
'user:session': (data: { sessionId: string | undefined; userId: string | undefined }) => void;
|
||||
'user:connected': (data: { userId: string; socketId: string }) => void;
|
||||
'user:online': (data: number) => void;
|
||||
}
|
||||
|
||||
export interface ClientToServerEvents {
|
||||
/**
|
||||
* User
|
||||
*/
|
||||
'user:logout': (data: { sessionId: string }) => void;
|
||||
}
|
||||
|
||||
export interface InterServerEvents {}
|
||||
|
||||
export interface SocketData {
|
||||
sessionId?: string | undefined;
|
||||
userId?: string | undefined;
|
||||
userName?: string | undefined;
|
||||
}
|
||||
|
||||
class WsService {
|
||||
public io: Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
|
||||
|
||||
public sessionStore: RedisSessionStore;
|
||||
|
||||
public booted = false;
|
||||
|
||||
public boot() {
|
||||
if (this.booted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.booted = true;
|
||||
this.io = new Server(AdonisServer.instance!, {
|
||||
cors: {
|
||||
origin: 'http://localhost:3000',
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
this.sessionStore = new RedisSessionStore(Redis.connection('session'));
|
||||
|
||||
this.setupMiddlewares();
|
||||
this.listen();
|
||||
}
|
||||
|
||||
private listen() {
|
||||
this.io.on('connection', socket => {
|
||||
const onlineController = new OnlineController(socket);
|
||||
|
||||
/**
|
||||
* Set user session and send online count
|
||||
*/
|
||||
this.sessionStore
|
||||
.saveSession(socket.data.sessionId, {
|
||||
sessionId: socket.data.sessionId,
|
||||
userId: socket.data.userId,
|
||||
userName: socket.data.userName,
|
||||
connected: true,
|
||||
})
|
||||
.then(async () => {
|
||||
const online = await this.sessionStore.getOnlineSessionsCount();
|
||||
socket.emit('user:online', online);
|
||||
socket.broadcast.emit('user:online', online);
|
||||
});
|
||||
|
||||
/**
|
||||
* Send session to client
|
||||
*/
|
||||
socket.emit('user:session', { sessionId: socket.data.sessionId, userId: socket.data.userId });
|
||||
|
||||
/**
|
||||
* Broadcast to all clients about new connection
|
||||
*/
|
||||
socket.broadcast.emit('user:connected', {
|
||||
userId: socket.handshake.auth.userId,
|
||||
socketId: socket.id,
|
||||
});
|
||||
|
||||
socket.on('disconnect', async () => {
|
||||
const matchingSockets = await this.io.in(socket.data.userId).allSockets();
|
||||
const isDisconnected = matchingSockets.size === 0;
|
||||
|
||||
if (isDisconnected) {
|
||||
/**
|
||||
* Update connection flag of socket to flase
|
||||
*/
|
||||
this.sessionStore.saveSession(socket.data.sessionId, {
|
||||
sessionId: socket.data.sessionId,
|
||||
userId: socket.data.userId,
|
||||
userName: socket.data.userName,
|
||||
connected: false,
|
||||
});
|
||||
|
||||
this.sessionStore.getOnlineSessionsCount().then(online => socket.broadcast.emit('user:online', online));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private setupMiddlewares() {
|
||||
this.io.use(AuthMiddleware.authenticate);
|
||||
}
|
||||
}
|
||||
|
||||
export default new WsService();
|
||||
90
app/Store/SessionStore.ts
Normal file
90
app/Store/SessionStore.ts
Normal file
@ -0,0 +1,90 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import { RedisConnectionContract } from '@ioc:Adonis/Addons/Redis';
|
||||
|
||||
type SessionDataType = { userId: string; userName: string; connected: boolean };
|
||||
|
||||
const SESSION_TTL = 24 * 60 * 60; // 1 day
|
||||
|
||||
class RedisSessionStore {
|
||||
public redisClient: RedisConnectionContract;
|
||||
|
||||
constructor(redisClient: RedisConnectionContract) {
|
||||
this.redisClient = redisClient;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
private transform(values: (string | null)[]) {
|
||||
const [userId, userName, connected] = values;
|
||||
|
||||
if (!userId || !userName || !connected) return undefined;
|
||||
|
||||
return {
|
||||
userId,
|
||||
userName,
|
||||
connected: connected === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return session data by session id
|
||||
*
|
||||
* @param id Session id
|
||||
* @returns Session data
|
||||
*/
|
||||
public async findSession(id: string): Promise<SessionDataType | undefined> {
|
||||
const values = await this.redisClient.hmget(id, 'userId', 'userName', 'connected');
|
||||
return this.transform(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the all sessions from store
|
||||
*/
|
||||
public async getSessions(): Promise<Array<SessionDataType | undefined>> {
|
||||
const keys = new Set<string>();
|
||||
let nextIndex = 0;
|
||||
|
||||
do {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const [nextIndexAsStr, result] = await this.redisClient.scan(nextIndex, 'MATCH', '*', 'COUNT', 100);
|
||||
nextIndex = parseInt(nextIndexAsStr, 10);
|
||||
result.forEach(s => keys.add(s));
|
||||
} while (nextIndex !== 0);
|
||||
|
||||
const commands = Array.from(keys).map(key => ['hmget', key.split('-')[1], 'userId', 'userName', 'connected']);
|
||||
|
||||
return this.redisClient
|
||||
.multi(commands)
|
||||
.exec()
|
||||
.then(results => results.map(([err, session]) => (err ? undefined : this.transform(session))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique sessions with connected flag `true`
|
||||
*/
|
||||
public async getOnlineSessionsCount() {
|
||||
const sessions = await this.getSessions();
|
||||
|
||||
const online = new Set(sessions.filter(s => s?.connected === true).map(s => s?.userId));
|
||||
return online.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set session data to redis store
|
||||
*
|
||||
* @param id Session id
|
||||
* @param data Session data
|
||||
*/
|
||||
public async saveSession(id: string, data: SessionDataType): Promise<void> {
|
||||
this.redisClient
|
||||
.multi()
|
||||
.hmset(id, {
|
||||
userId: data.userId,
|
||||
userName: data.userName,
|
||||
connected: data.connected,
|
||||
})
|
||||
.expire(id, SESSION_TTL)
|
||||
.exec();
|
||||
}
|
||||
}
|
||||
|
||||
export default RedisSessionStore;
|
||||
@ -50,7 +50,7 @@ const databaseConfig: DatabaseConfig = {
|
||||
paths: ['./database/seeders/MainSeeder'],
|
||||
},
|
||||
healthCheck: true,
|
||||
debug: true,
|
||||
debug: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -43,6 +43,14 @@ const redisConfig: RedisConfig = {
|
||||
keyPrefix: '',
|
||||
healthCheck: true,
|
||||
},
|
||||
session: {
|
||||
host: Env.get('REDIS_HOST'),
|
||||
port: Env.get('REDIS_PORT'),
|
||||
password: Env.get('REDIS_PASSWORD', ''),
|
||||
db: 1,
|
||||
keyPrefix: 'session-',
|
||||
healthCheck: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -8,5 +8,6 @@
|
||||
declare module '@ioc:Adonis/Addons/Redis' {
|
||||
interface RedisConnectionsList {
|
||||
local: RedisConnectionConfig;
|
||||
session: RedisConnectionConfig;
|
||||
}
|
||||
}
|
||||
|
||||
2
env.ts
2
env.ts
@ -26,7 +26,7 @@ export default Env.rules({
|
||||
PG_USER: Env.schema.string(),
|
||||
PG_PASSWORD: Env.schema.string.optional(),
|
||||
PG_DB_NAME: Env.schema.string(),
|
||||
REDIS_CONNECTION: Env.schema.enum(['local'] as const),
|
||||
REDIS_CONNECTION: Env.schema.enum(['local', 'session'] as const),
|
||||
REDIS_HOST: Env.schema.string({ format: 'host' }),
|
||||
REDIS_PORT: Env.schema.number(),
|
||||
REDIS_PASSWORD: Env.schema.string.optional(),
|
||||
|
||||
103
package-lock.json
generated
103
package-lock.json
generated
@ -2420,6 +2420,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz",
|
||||
"integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw=="
|
||||
},
|
||||
"@socket.io/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ=="
|
||||
},
|
||||
"@szmarczak/http-timer": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
||||
@ -2449,6 +2454,21 @@
|
||||
"@types/responselike": "*"
|
||||
}
|
||||
},
|
||||
"@types/component-emitter": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz",
|
||||
"integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ=="
|
||||
},
|
||||
"@types/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"@types/cors": {
|
||||
"version": "2.8.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
||||
},
|
||||
"@types/faker": {
|
||||
"version": "5.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/faker/-/faker-5.5.9.tgz",
|
||||
@ -2561,6 +2581,14 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/socket.io": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz",
|
||||
"integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==",
|
||||
"requires": {
|
||||
"socket.io": "*"
|
||||
}
|
||||
},
|
||||
"@types/stack-trace": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz",
|
||||
@ -3199,6 +3227,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"base64id": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
||||
},
|
||||
"bcrypt": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
|
||||
@ -3620,8 +3653,7 @@
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
@ -3685,6 +3717,15 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"requires": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
}
|
||||
},
|
||||
"cp-file": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cp-file/-/cp-file-9.1.0.tgz",
|
||||
@ -4066,6 +4107,31 @@
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.2.tgz",
|
||||
"integrity": "sha512-v/7eGHxPvO2AWsksyx2PUsQvBafuvqs0jJJQ0FdmJG1b9qIvgSbqDRGwNhfk2XHaTTbTXiC4quRE8Q9nRjsrQQ==",
|
||||
"requires": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/node": ">=10.0.0",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.4.1",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.0",
|
||||
"ws": "~8.2.3"
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz",
|
||||
"integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==",
|
||||
"requires": {
|
||||
"@socket.io/base64-arraybuffer": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"enquirer": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
|
||||
@ -7969,6 +8035,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz",
|
||||
"integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.1.0",
|
||||
"socket.io-adapter": "~2.3.3",
|
||||
"socket.io-parser": "~4.0.4"
|
||||
}
|
||||
},
|
||||
"socket.io-adapter": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz",
|
||||
"integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ=="
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz",
|
||||
"integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==",
|
||||
"requires": {
|
||||
"@types/component-emitter": "^1.2.10",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.3.1"
|
||||
}
|
||||
},
|
||||
"sonic-boom": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.2.3.tgz",
|
||||
@ -8695,6 +8789,11 @@
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
|
||||
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA=="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"@adonisjs/redis": "^7.0.9",
|
||||
"@adonisjs/repl": "^3.1.7",
|
||||
"@adonisjs/view": "^6.1.1",
|
||||
"@types/socket.io": "^3.0.2",
|
||||
"cloudinary": "^1.26.3",
|
||||
"luxon": "^1.28.0",
|
||||
"nanoid": "^3.1.25",
|
||||
@ -50,6 +51,7 @@
|
||||
"phc-bcrypt": "^1.0.7",
|
||||
"proxy-addr": "^2.0.7",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"socket.io": "^4.4.1",
|
||||
"source-map-support": "^0.5.19"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,11 @@ export default class AppProvider {
|
||||
|
||||
public async boot() {}
|
||||
|
||||
public async ready() {}
|
||||
public async ready() {
|
||||
if (this.app.environment === 'web') {
|
||||
await import('../start/socket');
|
||||
}
|
||||
}
|
||||
|
||||
public async shutdown() {}
|
||||
}
|
||||
|
||||
6
start/socket.ts
Normal file
6
start/socket.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import Ws from 'App/Services/WsService';
|
||||
|
||||
/**
|
||||
* Start websocket server
|
||||
*/
|
||||
Ws.boot();
|
||||
Loading…
x
Reference in New Issue
Block a user