Skip to content

Commit

Permalink
split steam into its own service
Browse files Browse the repository at this point in the history
  • Loading branch information
Geczy committed Sep 17, 2023
1 parent aa9c22b commit c9be6c8
Show file tree
Hide file tree
Showing 31 changed files with 2,479 additions and 655 deletions.
5 changes: 5 additions & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ services:
volumes:
- ./packages/twitch/chat/src:/app/packages/twitch/chat/src

steam:
image: "ghcr.io/dotabod/steam:v2.0"
volumes:
- ./packages/steam/src:/app/packages/steam/src

twitch-events:
image: "ghcr.io/dotabod/twitch-events:v2.0"
volumes:
Expand Down
25 changes: 23 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ services:
volumes:
- ./packages/twitch/chat/locales:/app/packages/twitch/chat/locales

steam:
image: "ghcr.io/dotabod/steam:v2.0"
platform: linux/amd64
container_name: steam
restart: on-failure
build:
context: .
dockerfile: ./packages/Dockerfile.steam
args:
- NODE_ENV=${NODE_ENV:-development}
- BUILD_CONTEXT=packages/steam
hostname: steam
ports:
- "5035:5035"
environment:
- MONGO_URL
- STEAM_PASS
- STEAM_USER
- STEAM_WEB_API
- NODE_ENV
volumes:
- ./packages/steam/locales:/app/packages/steam/locales

twitch-events:
image: "ghcr.io/dotabod/twitch-events:v2.0"
platform: linux/amd64
Expand Down Expand Up @@ -97,8 +120,6 @@ services:
- NEW_RELIC_LOG=stdout
- NEW_RELIC_LICENSE_KEY
- NODE_ENV
- STEAM_PASS
- STEAM_USER
- STEAM_WEB_API
- TWITCH_BOT_PROVIDERID
- TWITCH_CLIENT_ID
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"packages/settings",
"packages/twitch/events",
"packages/twitch/chat",
"packages/dota"
"packages/dota",
"packages/steam"
],
"scripts": {
"up": "yarn upgrade-interactive --latest",
Expand Down
62 changes: 62 additions & 0 deletions packages/Dockerfile.steam
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Using a more specific version to ensure reproducibility
FROM node:16-alpine3.17 as base

# Add Git, Python3, make, g++ in a single RUN command
RUN apk add --no-cache git python3 make g++ \
&& yarn cache clean

# Set build context and work directories
ARG BUILD_CONTEXT
WORKDIR /app

# Copy just the relevant package.json and yarn.lock files
COPY package.json yarn.lock* ./
COPY $BUILD_CONTEXT/package.json ./$BUILD_CONTEXT/

# Install dependencies
RUN yarn install --pure-lockfile --non-interactive

#-------------------------

FROM base as builder

# Copy source code and build configurations
COPY tsconfig.json ./
COPY $BUILD_CONTEXT/tsconfig.json $BUILD_CONTEXT/
COPY $BUILD_CONTEXT/src $BUILD_CONTEXT/src/

WORKDIR /app/
RUN yarn build

#-------------------------

FROM node:16-alpine3.17 as prod

ARG BUILD_CONTEXT
ARG NODE_ENV

# Meta-data and labels
LABEL org.opencontainers.image.source="https://github.com/dotabod/backend" \
org.opencontainers.image.description="Dotabod container: ${BUILD_CONTEXT}" \
org.opencontainers.image.licenses="AGPL-3.0"

# Create unprivileged user and switch to it
RUN adduser -D dotadockeruser
USER dotadockeruser

# Copy relevant build artifacts
WORKDIR /app
COPY --from=builder --chown=dotadockeruser /app/package.json /app/tsconfig.json ./
COPY --from=builder --chown=dotadockeruser /app/$BUILD_CONTEXT/package.json ./$BUILD_CONTEXT/
COPY --from=builder --chown=dotadockeruser /app/$BUILD_CONTEXT/dist $BUILD_CONTEXT/dist/

# Create required directories
RUN mkdir -p $BUILD_CONTEXT/src/steam/volumes

# Copy node_modules
COPY --from=base --chown=dotadockeruser /app/node_modules ./node_modules

# Environment and CMD
WORKDIR /app/$BUILD_CONTEXT
ENV NODE_ENV=$NODE_ENV
CMD yarn docker:$NODE_ENV
3 changes: 0 additions & 3 deletions packages/dota/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"body-parser": "^1.20.2",
"chokidar": "^3.5.3",
"country-code-emoji": "^2.3.0",
"dota2": "https://github.com/dotabod/node-dota2.git",
"dotaconstants": "^7.18.0",
"express": "^4.18.2",
"lodash.isequal": "^4.5.0",
Expand All @@ -43,8 +42,6 @@
"retry": "^0.13.1",
"socket.io": "^4.7.2",
"socket.io-client": "^4.7.2",
"steam": "https://github.com/dotabod/node-steam",
"steam-errors": "^1.0.0",
"winston": "^3.10.0"
},
"devDependencies": {
Expand Down
18 changes: 2 additions & 16 deletions packages/dota/src/dota/GSIServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import http from 'http'
import { Server, Socket } from 'socket.io'

import getDBUser from '../db/getDBUser.js'
import Dota from '../steam/index.js'
import { logger } from '../utils/logger.js'
import { newData, processChanges } from './globalEventEmitter.js'
import { emitMinimapBlockerStatus } from './GSIHandler.js'
import { gsiHandlers, isDev } from './lib/consts.js'
import { gsiHandlers } from './lib/consts.js'
import { validateToken } from './validateToken.js'

function handleSocketAuth(socket: Socket, next: (err?: Error) => void) {
Expand Down Expand Up @@ -47,11 +46,9 @@ async function handleSocketConnection(socket: Socket) {

class GSIServer {
io: Server
dota: Dota

constructor() {
logger.info('Starting GSI Server!')
this.dota = Dota.getInstance()

const app = express()
const httpServer = http.createServer(app)
Expand All @@ -65,18 +62,7 @@ class GSIServer {
app.use(express.json({ limit: '1mb' }))
app.use(express.urlencoded({ extended: true, limit: '1mb' }))

const setupPostRoute = () => {
app.post('/', validateToken, processChanges('previously'), processChanges('added'), newData)
}

if (isDev) {
setupPostRoute()
} else {
this.dota.dota2.on('ready', () => {
logger.info('[SERVER] Connected to dota game coordinator')
setupPostRoute()
})
}
app.post('/', validateToken, processChanges('previously'), processChanges('added'), newData)

app.get('/', (req: Request, res: Response) => {
res.status(200).json({ status: 'ok' })
Expand Down
63 changes: 39 additions & 24 deletions packages/dota/src/dota/events/gsi-events/newdata.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { DBSettings, getValueOrDefault } from '@dotabod/settings'
import { t } from 'i18next'

import { Packet, SocketClient, validEventTypes } from '../../../types.js'
import { steamSocket } from '../../../steam/ws.js'
import { DelayedGames, Packet, SocketClient, validEventTypes } from '../../../types.js'
import { logger } from '../../../utils/logger.js'
import { events } from '../../globalEventEmitter.js'
import { GSIHandler, redisClient } from '../../GSIHandler.js'
import { server } from '../../index.js'
import { checkPassiveMidas } from '../../lib/checkMidas.js'
import { checkPassiveTp } from '../../lib/checkPassiveTp.js'
import { calculateManaSaved } from '../../lib/checkTreadToggle.js'
Expand Down Expand Up @@ -35,8 +35,8 @@ function chatterMatchFound(client: SocketClient) {
}
}

const steamServerLookupMap = new Map()
const steamDelayDataLookupMap = new Map()
const steamServerLookupMap = new Set()
const steamDelayDataLookupMap = new Set()

// Runs every gametick
async function saveMatchData(client: SocketClient) {
Expand All @@ -55,20 +55,27 @@ async function saveMatchData(client: SocketClient) {
.get(`${matchId}:lobbyType`)
.exec()

let [steamServerId] = res
const [steamServerId] = res
const [, lobbyType] = res

if (steamServerId && lobbyType) return

if (!steamServerId && !lobbyType) {
if (steamServerLookupMap.has(matchId)) return

const promise = server.dota.getUserSteamServer(client.steam32Id).catch((e) => {
logger.error('err getUserSteamServer', { e })
return null
// Wrap the steamSocket.emit in a Promise
const getDelayedDataPromise = new Promise<string>((resolve, reject) => {
steamSocket.emit('getUserSteamServer', client.steam32Id, (err: any, cards: any) => {
if (err) {
reject(err)
} else {
resolve(cards)
}
})
})
steamServerLookupMap.set(matchId, promise)
steamServerId = await promise

steamServerLookupMap.add(matchId)
const steamServerId = await getDelayedDataPromise
steamServerLookupMap.delete(matchId) // Remove the promise once it's resolved

if (!steamServerId) return
Expand All @@ -78,20 +85,28 @@ async function saveMatchData(client: SocketClient) {
if (steamServerId && !lobbyType) {
if (steamDelayDataLookupMap.has(matchId)) return

const promise = server.dota
.GetRealTimeStats({
match_id: matchId!,
refetchCards: true,
steam_server_id: steamServerId.toString(),
token: client.token,
})
.catch((e) => {
logger.error('err GetRealTimeStats', { e })
return null
})
steamDelayDataLookupMap.set(matchId, promise)
const delayedData = await promise
steamDelayDataLookupMap.delete(matchId) // Remove the promise once it's resolved
steamDelayDataLookupMap.add(matchId)
const getDelayedDataPromise = new Promise<DelayedGames>((resolve, reject) => {
steamSocket.emit(
'getRealTimeStats',
{
match_id: matchId!,
refetchCards: true,
steam_server_id: steamServerId?.toString(),
token: client.token,
},
(err: any, data: DelayedGames) => {
if (err) {
reject(err)
} else {
resolve(data)
}
},
)
})

const delayedData = await getDelayedDataPromise
steamDelayDataLookupMap.delete(matchId)

if (!delayedData?.match.lobby_type) return
await redisClient.client.set(`${matchId}:lobbyType`, delayedData.match.lobby_type)
Expand Down
18 changes: 13 additions & 5 deletions packages/dota/src/dota/lib/getPlayers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { t } from 'i18next'

import Dota from '../../steam/index.js'
import MongoDBSingleton from '../../steam/MongoDBSingleton.js'
import { DelayedGames } from '../../types.js'
import { steamSocket } from '../../steam/ws.js'
import { Cards, DelayedGames } from '../../types.js'
import CustomError from '../../utils/customError.js'
import { getAccountsFromMatch } from './getAccountsFromMatch.js'

const dota = Dota.getInstance()

export async function getPlayers({
locale,
currentMatchId,
Expand Down Expand Up @@ -42,7 +40,17 @@ export async function getPlayers({
searchPlayers: players,
})

const cards = await dota.getCards(accountIds)
const getCardsPromise = new Promise<Cards[]>((resolve, reject) => {
steamSocket.emit('getCards', accountIds, (err: any, cards: any) => {
if (err) {
reject(err)
} else {
resolve(cards)
}
})
})

const cards = await getCardsPromise

return {
gameMode: response ? Number(response.match.game_mode) : undefined,
Expand Down
15 changes: 13 additions & 2 deletions packages/dota/src/dota/lib/ranks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { t } from 'i18next'

import RedisClient from '../../db/RedisClient.js'
import { steamSocket } from '../../steam/ws.js'
import { Cards } from '../../types.js'
import { logger } from '../../utils/logger.js'
import { server } from '../index.js'
import { leaderRanks, ranks } from './consts.js'

export function rankTierToMmr(rankTier: string | number) {
Expand Down Expand Up @@ -56,8 +57,18 @@ export async function lookupLeaderRank(
result = medalCache as unknown as LeaderRankData
} else {
try {
const getCardPromise = new Promise<Cards>((resolve, reject) => {
steamSocket.emit('getCard', steam32Id, (err: any, card: Cards) => {
if (err) {
reject(err)
} else {
resolve(card)
}
})
})

// Fetch the leaderboard rank from the Dota 2 server
const data = await server.dota.getCard(steam32Id)
const data = await getCardPromise
const standing: number = data?.leaderboard_rank

// If the rank is not available, return default values
Expand Down
9 changes: 9 additions & 0 deletions packages/dota/src/steam/ws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { io } from 'socket.io-client'

import { logger } from '../utils/logger.js'

export const steamSocket = io('ws://steam:5035')

steamSocket.on('connect', () => {
logger.info('We alive on dotabod steam server!')
})
Loading

0 comments on commit c9be6c8

Please sign in to comment.