Skip to content
This repository has been archived by the owner on Jan 29, 2024. It is now read-only.

Commit

Permalink
archive
Browse files Browse the repository at this point in the history
  • Loading branch information
koh110 committed Jan 29, 2024
1 parent f9c1e24 commit fd896c9
Show file tree
Hide file tree
Showing 20 changed files with 510 additions and 120 deletions.
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# https://hub.docker.com/_/node
FROM node:20 as builder

WORKDIR /usr/app

COPY ./package.json ./package-lock.json ./
COPY ./packages/shared ./packages/shared
COPY ./packages/socket/package.json \
./packages/socket/tsconfig.json \
./packages/socket/
COPY ./packages/socket/src/ ./packages/socket/src/

RUN npm install -w mzm-socket -w mzm-shared && npm run -w mzm-socket cleanbuild

FROM node:20-alpine

WORKDIR /usr/app

COPY --from=builder /usr/app/package.json /usr/app/package-lock.json ./
COPY --from=builder /usr/app/packages/shared/dist/ packages/shared/dist/
COPY --from=builder /usr/app/packages/shared/package.json packages/shared/package.json
COPY --from=builder /usr/app/packages/socket/package.json ./packages/socket/
COPY --from=builder /usr/app/packages/socket/dist/ packages/socket/dist/
RUN npm install -w mzm-socket -w mzm-shared --production

ENV NODE_ENV=production
CMD [ "node", "packages/socket/dist/server.js" ]
3 changes: 0 additions & 3 deletions jest/testUtil.ts

This file was deleted.

48 changes: 19 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,32 @@
"private": true,
"name": "mzm-socket",
"version": "0.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"prestart": "npm run build",
"start": "node dist/src/socket.js | bunyan",
"build": "tsc -p tsconfig.json",
"cleanbuild": "rm -rf dist && tsc -p tsconfig.json",
"format": "prettier --write src",
"start": "node --watch dist/src/server.js | bunyan",
"build": "tsc --build tsconfig.json",
"build:test": "tsc --build tsconfig.test.json",
"cleanbuild": "rm -rf dist && npm run build",
"format": "prettier --write src ./*.{js,json}",
"lint": "eslint --ext .ts,.js src",
"test": "npm run lint && jest"
"pretest": "concurrently \"npm run lint\" \"npm run build\"",
"test": "vitest run --config ./vitest.config.ts",
"test:ci": "vitest run --config ./vitest.config.ts",
"test:watch": "vitest --config ./vitest.config.ts"
},
"dependencies": {
"bunyan": "^1.8.14",
"dotenv": "^8.2.0",
"ioredis": "^4.17.3",
"request": "^2.88.2",
"uuid": "^8.3.0",
"ws": "^7.3.1"
"@types/validator": "^13.7.10",
"bunyan": "^1.8.15",
"dotenv": "^16.0.3",
"ioredis": "^5.3.1",
"undici": "^6.0.1",
"ws": "^8.11.0"
},
"devDependencies": {
"@types/bunyan": "^1.8.6",
"@types/ioredis": "^4.17.3",
"@types/jest": "^26.0.10",
"@types/node": "^14.6.0",
"@types/request": "^2.48.5",
"@types/ws": "^7.2.6",
"@typescript-eslint/eslint-plugin": "^3.9.1",
"@typescript-eslint/parser": "^3.9.1",
"eslint": "^7.7.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-jest": "^23.20.0",
"eslint-plugin-prettier": "^3.1.4",
"jest": "^26.4.0",
"prettier": "^2.0.5",
"rmtcmd": "^0.3.0",
"ts-jest": "^26.2.0",
"typescript": "^3.9.7"
"@types/bunyan": "^1.8.8",
"@types/ws": "^8.5.4",
"typescript": "^5.1.6"
}
}
126 changes: 126 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import type WebSocket from 'ws'
import { verifyAccessToken } from 'mzm-shared/src/auth/index'
import { randomUUID } from 'crypto'
import { SocketToBackendType, TO_SERVER_CMD } from 'mzm-shared/src/type/socket'
import { requestSocketAPI } from './lib/req.js'
import { saveSocket, removeSocket } from './lib/sender.js'
import { consume } from './lib/consumer.js'
import { logger } from './lib/logger.js'
import { JWT } from './config.js'
import { ExtWebSocket } from './types.js'

export const createApp = ({ wss }: { wss: WebSocket.Server }) => {
consume()

wss.on('connection', async function connection(ws: ExtWebSocket, req) {
let userId: string | null = null

if (!req.url) {
return
}
const url = new URL(req.url, `http://${req.headers.host}`)
const token = url.searchParams.get('token')
if (token) {
const { err, decoded } = await verifyAccessToken(
token,
JWT.accessTokenSecret,
{
issuer: JWT.issuer,
audience: JWT.audience
}
)
logger.info({
label: 'ws:verifyAccessToken',
err,
decoded
})
if (!err && decoded) {
userId = decoded.user._id
}
}
logger.info({
label: 'ws:connection',
userId
})

if (!userId) {
ws.close()
return
}

const id = randomUUID()
ws.id = id
saveSocket(id, userId, ws)

ws.on('message', async function incoming(data) {
const message = data.toString()
logger.info({
label: 'ws:message',
userId,
id,
message
})
if (message === 'pong') {
return
}
if (!userId) {
return
}
try {
const res = await requestSocketAPI(message, userId, id)
if (res.body) {
ws.send(res.body)
logger.info({
label: 'ws:send',
userId,
id,
body: res.body
})
}
} catch (e) {
logger.error({
label: 'post:error',
err: e
})
}
})

ws.on('close', function close() {
if (!userId) {
return
}
logger.info('ws:closed', userId, ws.id)
removeSocket(ws.id, userId)
})

ws.on('error', function error(e) {
logger.error({
label: 'ws:error',
err: e
})
})

const data: SocketToBackendType = {
cmd: TO_SERVER_CMD.CONNECTION
}

requestSocketAPI(JSON.stringify(data), userId, id)
.then(({ body }) => {
if (body) {
ws.send(body)
}
})
.catch((e) => {
logger.error({
label: 'post:error',
err: e
})
})
})

setInterval(() => {
wss.clients.forEach((ws) => {
ws.send('ping')
})
}, 50000)
}
32 changes: 24 additions & 8 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import type { RedisOptions } from 'ioredis'
import { config } from 'dotenv'
config()
if (process.env.NODE_ENV !== 'test') {
config()
}

export const WORKER_NUM = Number(process.env.WORKER_NUM) ?? 1

export const WORKER_NUM = 2
export const SOCKET_LISTEN = 3000
export const PORT: number = process.env.PORT
? parseInt(process.env.PORT, 10)
: 3000

export const INTERNAL_API_URL = process.env.INTERNAL_API
export const INTERNAL_API_URL = process.env.INTERNAL_API!

export const redis = {
export const REDIS = {
options: {
host: process.env.REDIS_HOST,
enableOfflineQueue: false
}
}
enableOfflineQueue: false,
connectTimeout: Number(process.env.SESSION_REDIS_TIMEOUT) ?? 30000
} satisfies RedisOptions
} as const

export const JWT = {
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
internalAccessTokenSecret: process.env.INTERNAL_ACCESS_TOKEN_SECRET!,
issuer: process.env.JWT_ISSURE ?? 'https://mzm.dev',
audience: process.env.JWT_AUDIENCE
? process.env.JWT_AUDIENCE.split(',')
: (['https://mzm.dev'] as string[])
} as const
20 changes: 11 additions & 9 deletions src/lib/consumer.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
jest.mock('./logger')
jest.mock('./redis', () => {
import { vi, test, expect } from 'vitest'
vi.mock('./logger.js')
vi.mock('./redis.js', () => {
return {
xread: jest.fn(),
xdel: jest.fn()
redis: {
xread: vi.fn(),
xdel: vi.fn()
}
}
})

jest.mock('./sender')
import { getMockType } from '../../jest/testUtil'
import * as sender from './sender'
vi.mock('./sender.js')
import * as sender from './sender.js'

const sendToUser = getMockType(sender.sendToUser)
const sendToUser = vi.mocked(sender.sendToUser)

import { parser } from './consumer'
import { parser } from './consumer.js'

test('parser sendToUser', async () => {
sendToUser.mockClear()
Expand Down
31 changes: 22 additions & 9 deletions src/lib/consumer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import redis from './redis'
import logger from './logger'
import { sendToUser } from './sender'
import { redis } from './redis.js'
import { logger } from './logger.js'
import { sendToUser } from './sender.js'

type ReceiveQueue = {
user?: string
Expand All @@ -10,7 +10,8 @@ type ReceiveQueue = {

const READ_STREAM = 'stream:socket:message'

export const parser = async (read) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const parser = async (read: any) => {
if (!read) {
return
}
Expand All @@ -22,6 +23,10 @@ export const parser = async (read) => {
nextId = id
try {
const queue = JSON.parse(messages[1]) as ReceiveQueue
logger.info({
label: 'queue',
message: queue
})
if (queue.user) {
sendToUser(queue.user, queue)
}
Expand All @@ -34,22 +39,30 @@ export const parser = async (read) => {
return nextId
}

export const consume = async (startId: string = '$') => {
export const consume = async (startId = '$') => {
logger.info({
label: 'consume',
message: `consume ${startId}`
})
let nextId = startId ? startId : '$'

try {
const res = await redis.xread(
'BLOCK',
'0',
const res = await redis().xread(
'COUNT',
'100',
'BLOCK',
'0',
'STREAMS',
READ_STREAM,
startId
)
nextId = await parser(res)
} catch (e) {
logger.error('[read]', 'stream:socket:message', e)
logger.error({
label: 'consume',
message: 'error',
err: e
})
}
if (!nextId) {
nextId = '$'
Expand Down
4 changes: 1 addition & 3 deletions src/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import bunyan from 'bunyan'

const logger = bunyan.createLogger({
export const logger = bunyan.createLogger({
name: 'socket'
})

export default logger
Loading

0 comments on commit fd896c9

Please sign in to comment.