feat: add realtime with socket.io

This commit is contained in:
Divlo 2022-01-13 16:33:16 +00:00
parent 97b1d04261
commit 5643ab420f
No known key found for this signature in database
GPG Key ID: 8F9478F220CE65E9
7 changed files with 483 additions and 363 deletions

View File

@ -12,7 +12,10 @@
"prettier/prettier": "error", "prettier/prettier": "error",
"import/order": [ "import/order": [
"error", "error",
{ "groups": ["builtin", "external", "internal"] } {
"groups": ["builtin", "external", "internal"],
"newlines-between": "always"
}
], ],
"import/extensions": ["error", "always"], "import/extensions": ["error", "always"],
"unicorn/prefer-node-protocol": "error", "unicorn/prefer-node-protocol": "error",

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ node_modules
# production # production
build build
.swc
# testing # testing
coverage coverage

721
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@
"generate": "plop", "generate": "plop",
"lint:commit": "commitlint", "lint:commit": "commitlint",
"lint:editorconfig": "editorconfig-checker", "lint:editorconfig": "editorconfig-checker",
"lint:markdown": "markdownlint '**/*.md' --dot --ignore 'node_modules'", "lint:markdown": "markdownlint '**/*.md' --dot --ignore-path '.gitignore'",
"lint:typescript": "eslint '**/*.{js,ts,jsx,tsx}'", "lint:typescript": "eslint '**/*.{js,ts,jsx,tsx}'",
"lint:prettier": "prettier '.' --check", "lint:prettier": "prettier '.' --check",
"lint:staged": "lint-staged", "lint:staged": "lint-staged",
@ -31,21 +31,22 @@
"postinstall": "husky install" "postinstall": "husky install"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "3.7.0", "@prisma/client": "3.8.0",
"@sinclair/typebox": "0.23.2", "@sinclair/typebox": "0.23.2",
"@thream/socketio-jwt": "2.1.1",
"axios": "0.24.0", "axios": "0.24.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"dotenv": "10.0.0", "dotenv": "11.0.0",
"ejs": "3.1.6", "ejs": "3.1.6",
"fastify": "3.25.3", "fastify": "3.25.3",
"fastify-cors": "6.0.2", "fastify-cors": "6.0.2",
"fastify-helmet": "5.3.2", "fastify-helmet": "6.0.0",
"fastify-multipart": "5.2.1", "fastify-multipart": "5.2.1",
"fastify-plugin": "3.0.0", "fastify-plugin": "3.0.0",
"fastify-rate-limit": "5.7.0", "fastify-rate-limit": "5.7.0",
"fastify-sensible": "3.1.2", "fastify-sensible": "3.1.2",
"fastify-static": "4.5.0", "fastify-static": "4.5.0",
"fastify-swagger": "4.13.0", "fastify-swagger": "4.13.1",
"fastify-url-data": "3.0.3", "fastify-url-data": "3.0.3",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "8.5.1",
@ -55,18 +56,18 @@
"socket.io": "4.4.1" "socket.io": "4.4.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "16.0.1", "@commitlint/cli": "16.0.2",
"@commitlint/config-conventional": "16.0.0", "@commitlint/config-conventional": "16.0.0",
"@saithodev/semantic-release-backmerge": "2.1.0", "@saithodev/semantic-release-backmerge": "2.1.0",
"@swc/cli": "0.1.55", "@swc/cli": "0.1.55",
"@swc/core": "1.2.127", "@swc/core": "1.2.129",
"@swc/jest": "0.2.15", "@swc/jest": "0.2.17",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/busboy": "0.3.1", "@types/busboy": "1.3.0",
"@types/ejs": "3.1.0", "@types/ejs": "3.1.0",
"@types/http-errors": "1.8.1", "@types/http-errors": "1.8.1",
"@types/jest": "27.4.0", "@types/jest": "27.4.0",
"@types/jsonwebtoken": "8.5.6", "@types/jsonwebtoken": "8.5.7",
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/node": "17.0.8", "@types/node": "17.0.8",
"@types/nodemailer": "6.4.4", "@types/nodemailer": "6.4.4",
@ -86,12 +87,12 @@
"jest": "27.4.7", "jest": "27.4.7",
"jest-mock-extended": "2.0.4", "jest-mock-extended": "2.0.4",
"jest-ts-webcompat-resolver": "1.0.0", "jest-ts-webcompat-resolver": "1.0.0",
"lint-staged": "12.1.5", "lint-staged": "12.1.7",
"markdownlint-cli": "0.30.0", "markdownlint-cli": "0.30.0",
"nodemon": "2.0.15", "nodemon": "2.0.15",
"plop": "3.0.5", "plop": "3.0.5",
"prettier": "2.5.1", "prettier": "2.5.1",
"prisma": "3.7.0", "prisma": "3.8.0",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"semantic-release": "18.0.1", "semantic-release": "18.0.1",
"typescript": "4.5.4" "typescript": "4.5.4"

View File

@ -102,7 +102,7 @@ export const postMessageByChannelIdService: FastifyPluginAsync = async (
} }
}) })
reply.statusCode = 201 reply.statusCode = 201
return { const item = {
...message, ...message,
member: { member: {
...memberCheck, ...memberCheck,
@ -112,6 +112,12 @@ export const postMessageByChannelIdService: FastifyPluginAsync = async (
} }
} }
} }
await fastify.io.emitToMembers({
event: 'messages',
guildId: item.member.guildId,
payload: { action: 'create', item }
})
return item
} }
}) })
} }

View File

@ -107,7 +107,7 @@ export const postMessageUploadsByChannelIdService: FastifyPluginAsync = async (
} }
}) })
reply.statusCode = 201 reply.statusCode = 201
return { const item = {
...message, ...message,
member: { member: {
...memberCheck, ...memberCheck,
@ -117,6 +117,12 @@ export const postMessageUploadsByChannelIdService: FastifyPluginAsync = async (
} }
} }
} }
await fastify.io.emitToMembers({
event: 'messages',
guildId: item.member.guildId,
payload: { action: 'create', item }
})
return item
} }
}) })
} }

View File

@ -1,18 +1,88 @@
import fastifyPlugin from 'fastify-plugin' import fastifyPlugin from 'fastify-plugin'
import { Server as SocketIoServer, ServerOptions } from 'socket.io' import { Server as SocketIoServer, ServerOptions } from 'socket.io'
import { authorize } from '@thream/socketio-jwt'
import prisma from '../database/prisma.js'
import { JWT_ACCESS_SECRET } from '../configurations/index.js'
interface EmitEventOptions {
event: string
payload: {
action: 'create' | 'delete' | 'update'
item: object
}
}
interface EmitToAuthorizedUsersOptions extends EmitEventOptions {
/** tests whether the current connected userId is authorized to get the event, if the callback returns true, the server will emit the event to that user */
isAuthorizedCallback: (userId: number) => Promise<boolean>
}
type EmitToAuthorizedUsers = (
options: EmitToAuthorizedUsersOptions
) => Promise<void>
interface EmitToMembersOptions extends EmitEventOptions {
guildId: number
}
type EmitToMembers = (options: EmitToMembersOptions) => Promise<void>
interface FastifyIo {
instance: SocketIoServer
emitToAuthorizedUsers: EmitToAuthorizedUsers
emitToMembers: EmitToMembers
}
declare module 'fastify' { declare module 'fastify' {
export interface FastifyInstance { export interface FastifyInstance {
io: SocketIoServer io: FastifyIo
} }
} }
export default fastifyPlugin( export default fastifyPlugin(
async (fastify, options: Partial<ServerOptions>) => { async (fastify, options: Partial<ServerOptions>) => {
const socket = new SocketIoServer(fastify.server, options) const instance = new SocketIoServer(fastify.server, options)
fastify.decorate('io', socket) instance.use(
authorize({
secret: JWT_ACCESS_SECRET
})
)
const emitToAuthorizedUsers: EmitToAuthorizedUsers = async (options) => {
const { event, payload, isAuthorizedCallback } = options
const clients = await instance.sockets.allSockets()
for (const clientId of clients) {
const client = instance.sockets.sockets.get(clientId)
if (client != null) {
const userId = client.decodedToken.id
const isAuthorized = await isAuthorizedCallback(userId)
if (isAuthorized) {
client.emit(event, payload)
}
}
}
}
const emitToMembers: EmitToMembers = async (options) => {
const { event, payload, guildId } = options
await emitToAuthorizedUsers({
event,
payload,
isAuthorizedCallback: async (userId) => {
const memberCount = await prisma.member.count({
where: { userId, guildId }
})
return memberCount > 0
}
})
}
const io: FastifyIo = {
instance,
emitToAuthorizedUsers,
emitToMembers
}
fastify.decorate('io', io)
fastify.addHook('onClose', async (fastify) => { fastify.addHook('onClose', async (fastify) => {
fastify.io.close() fastify.io.instance.close()
}) })
}, },
{ fastify: '3.x' } { fastify: '3.x' }