2020-10-30 22:15:51 +08:00
import fs from 'fs' ;
2020-10-28 22:31:51 +08:00
import path from 'path' ;
2021-01-28 05:15:14 +08:00
import v8 from 'v8' ;
2020-11-04 23:02:33 +08:00
2020-10-28 23:27:41 +08:00
import express from 'express' ;
2020-10-31 07:24:19 +08:00
import bodyParser from 'body-parser' ;
2020-10-28 23:27:41 +08:00
import http from 'http' ;
2020-12-04 04:06:56 +08:00
import CRC32 from 'crc-32' ;
2023-09-20 22:23:00 +08:00
import fetch from 'node-fetch' ;
2023-10-04 03:30:25 +08:00
import crawlers from 'crawler-user-agents' assert { type : 'json' } ; ;
2020-10-29 01:41:30 +08:00
2020-11-06 00:41:23 +08:00
import WebSocket from './server/websocket.mjs' ;
import Player from './server/player.mjs' ;
import Room from './server/room.mjs' ;
2023-09-20 22:23:00 +08:00
import MinifyHTML from './server/minify.mjs' ;
2021-04-06 22:59:42 +08:00
import Logging from './server/logging.mjs' ;
2021-12-01 22:33:07 +08:00
import Config from './server/config.mjs' ;
2022-12-30 17:46:55 +08:00
import Statistics from './server/statistics.mjs' ;
2020-10-28 22:31:51 +08:00
2020-10-28 23:27:41 +08:00
const app = express ( ) ;
const server = http . Server ( app ) ;
2022-03-13 20:49:02 +08:00
const router = express . Router ( ) ;
2020-10-28 22:31:51 +08:00
2021-12-01 22:33:07 +08:00
const savedir = Config . directory ( 'save' ) ;
const assetsdir = Config . directory ( 'assets' ) ;
2020-12-04 21:21:31 +08:00
const sharedLinks = fs . existsSync ( savedir + '/shares.json' ) ? JSON . parse ( fs . readFileSync ( savedir + '/shares.json' ) ) : { } ;
2021-04-10 19:58:10 +08:00
const serverStart = + new Date ( ) ;
2022-03-13 20:49:02 +08:00
app . use ( Config . get ( 'urlPrefix' ) , router ) ;
2021-12-01 22:33:07 +08:00
fs . mkdirSync ( assetsdir , { recursive : true } ) ;
2020-12-04 21:21:31 +08:00
fs . mkdirSync ( savedir + '/rooms' , { recursive : true } ) ;
fs . mkdirSync ( savedir + '/states' , { recursive : true } ) ;
fs . mkdirSync ( savedir + '/links' , { recursive : true } ) ;
fs . mkdirSync ( savedir + '/errors' , { recursive : true } ) ;
2020-10-30 22:15:51 +08:00
2021-04-06 22:59:42 +08:00
async function ensureRoomIsLoaded ( id ) {
2021-01-22 18:21:22 +08:00
if ( ! id . match ( /^[A-Za-z0-9_-]+$/ ) )
return false ;
2020-11-12 06:24:47 +08:00
if ( ! activeRooms . has ( id ) ) {
2021-04-06 22:59:42 +08:00
const room = new Room ( id , function ( ) {
2020-11-12 06:24:47 +08:00
activeRooms . delete ( id ) ;
2023-07-09 08:53:30 +08:00
} , function ( ) {
Logging . log ( ` The public library was edited in room ${ id } . Reloading in every room... ` ) ;
for ( const [ _ , room ] of activeRooms )
room . reloadPublicLibraryGames ( ) ;
2021-04-06 22:59:42 +08:00
} ) ;
await room . load ( ) ;
activeRooms . set ( id , room ) ;
2020-11-12 06:24:47 +08:00
}
2021-01-22 18:21:22 +08:00
return true ;
2020-11-12 06:24:47 +08:00
}
2024-01-26 16:59:10 +08:00
function getEmptyRoomID ( ) {
let id = null ;
while ( ! id || fs . existsSync ( savedir + '/rooms/' + id + '.json' ) )
id = Math . random ( ) . toString ( 36 ) . substring ( 3 , 7 ) ;
return id ;
}
2022-12-30 17:46:55 +08:00
function validateInput ( res , next , values ) {
for ( const value of values ) {
if ( value && ! value . match ( /^[A-Za-z0-9.: _-]+$/ ) ) {
next ( new Logging . UserError ( 403 , 'Invalid characters in parameters' ) ) ;
return false ;
}
}
return true ;
}
2021-01-28 05:35:49 +08:00
async function downloadState ( res , roomID , stateID , variantID ) {
2021-04-06 22:59:42 +08:00
if ( await ensureRoomIsLoaded ( roomID ) ) {
const d = await activeRooms . get ( roomID ) . download ( stateID , variantID ) ;
res . setHeader ( 'Content-Type' , d . type ) ;
res . setHeader ( 'Content-Disposition' , ` attachment; filename=" ${ d . name . replace ( /[^A-Za-z0-9._-]/g , '_' ) } " ` ) ;
res . send ( d . content ) ;
2021-01-22 18:21:22 +08:00
}
2020-11-28 00:28:02 +08:00
}
2021-01-28 01:21:41 +08:00
function autosaveRooms ( ) {
setInterval ( function ( ) {
2021-04-14 21:09:44 +08:00
for ( const [ _ , room ] of activeRooms ) {
try {
2022-12-30 17:46:55 +08:00
room . updateTimeStatistics ( ) ;
2021-04-14 21:09:44 +08:00
room . writeToFilesystem ( ) ;
} catch ( e ) {
Logging . handleGenericException ( 'autosaveRooms' , e ) ;
}
}
2022-12-30 17:46:55 +08:00
Statistics . writeToFilesystem ( ) ;
2021-01-28 01:21:41 +08:00
} , 60 * 1000 ) ;
}
2023-09-20 22:23:00 +08:00
MinifyHTML ( ) . then ( function ( result ) {
2022-03-13 20:49:02 +08:00
router . use ( '/' , express . static ( path . resolve ( ) + '/client' ) ) ;
2023-01-14 08:50:33 +08:00
2023-09-29 13:36:23 +08:00
if ( Config . get ( 'adminURL' ) ) {
router . get ( Config . get ( 'adminURL' ) , function ( req , res , next ) {
let output = '<h1>Active rooms</h1>' ;
for ( const [ roomID , room ] of activeRooms ) {
let game = '' ;
if ( room . state && room . state . _meta && room . state . _meta . activeState && room . state . _meta . states && room . state . _meta . states [ room . state . _meta . activeState . stateID ] )
game = ` playing ${ room . state . _meta . states [ room . state . _meta . activeState . stateID ] . name } ` ;
output += ` <p><b><a href=' ${ roomID } '> ${ roomID } </a></b> ${ game } : ${ room . players . map ( p => p . name ) . join ( ', ' ) } ( ${ room . deltaID } deltas transmitted)</p> ` ;
}
res . send ( output ) ;
} ) ;
}
2023-01-14 08:50:33 +08:00
// fonts.css is specifically made available for use from card html iframe. It must
// be fetched from the root in order for the relative paths to fonts to work.
// Additionally allow cached use of fonts for a short period of time to allow
// immediate rendering in subframes.
function cache5m ( req , res , next ) {
res . setHeader ( 'Cache-Control' , 'public, max-age=300' ) ;
res . setHeader ( 'Expires' , new Date ( Date . now ( ) + 300000 ) . toUTCString ( ) ) ;
next ( ) ;
}
router . get ( '/fonts.css' , cache5m ) ;
router . get ( '/i/fonts/' , cache5m ) ;
router . use ( '/fonts.css' , express . static ( path . resolve ( ) + '/client/css/fonts.css' ) ) ;
2022-03-13 20:49:02 +08:00
router . use ( '/i' , express . static ( path . resolve ( ) + '/assets' ) ) ;
2022-12-30 17:46:55 +08:00
router . get ( '/scripts/:name' , function ( req , res ) {
res . setHeader ( 'Content-Type' , 'application/javascript' ) ;
if ( req . params . name == 'jszip' )
res . send ( fs . readFileSync ( 'node_modules/jszip/dist/jszip.min.js' ) ) ;
} ) ;
2020-10-28 23:27:41 +08:00
2022-03-13 20:49:02 +08:00
router . post ( '/assetcheck' , bodyParser . json ( { limit : '10mb' } ) , function ( req , res ) {
2021-05-12 17:15:58 +08:00
const result = { } ;
if ( Array . isArray ( req . body ) )
for ( const asset of req . body )
if ( asset . match ( /^[0-9_-]+$/ ) )
2022-12-30 17:46:55 +08:00
result [ asset ] = ! ! Config . resolveAsset ( asset ) ;
2021-05-12 17:15:58 +08:00
res . send ( result ) ;
} ) ;
2022-03-13 20:49:02 +08:00
router . get ( '/assets/:name' , function ( req , res ) {
2022-12-30 17:46:55 +08:00
if ( ! req . params . name . match ( /^[0-9_-]+$/ ) || ! Config . resolveAsset ( req . params . name ) ) {
2022-09-26 21:07:21 +08:00
res . sendStatus ( 404 ) ;
2021-01-22 18:21:22 +08:00
return ;
2022-09-26 21:07:21 +08:00
}
2022-12-30 17:46:55 +08:00
fs . readFile ( Config . resolveAsset ( req . params . name ) , function ( err , content ) {
2021-01-05 05:52:03 +08:00
if ( ! content ) {
res . sendStatus ( 404 ) ;
2021-04-14 21:13:16 +08:00
Logging . log ( ` WARNING: Could not load asset ${ req . params . name } ` ) ;
2021-01-05 05:52:03 +08:00
return ;
}
2020-11-19 23:01:15 +08:00
if ( content [ 0 ] == 0xff )
res . setHeader ( 'Content-Type' , 'image/jpeg' ) ;
else if ( content [ 0 ] == 0x89 )
res . setHeader ( 'Content-Type' , 'image/png' ) ;
else if ( content [ 0 ] == 0x3c )
res . setHeader ( 'Content-Type' , 'image/svg+xml' ) ;
else if ( content [ 0 ] == 0x47 )
res . setHeader ( 'Content-Type' , 'image/gif' ) ;
else if ( content [ 0 ] == 0x52 )
res . setHeader ( 'Content-Type' , 'image/webp' ) ;
else
2021-04-14 21:13:16 +08:00
Logging . log ( ` WARNING: Unknown file type of asset ${ req . params . name } ` ) ;
2021-01-05 05:52:03 +08:00
2021-10-05 16:24:32 +08:00
res . setHeader ( 'Cache-Control' , 'public, max-age=30000000' ) ;
res . setHeader ( 'Expires' , new Date ( Date . now ( ) + 30000000000 ) . toUTCString ( ) ) ;
2020-11-19 23:01:15 +08:00
res . send ( content ) ;
} ) ;
} ) ;
2020-11-02 06:02:12 +08:00
2022-03-13 20:49:02 +08:00
router . post ( '/heapsnapshot' , function ( req , res ) {
2021-01-28 05:15:14 +08:00
v8 . getHeapSnapshot ( ) . pipe ( fs . createWriteStream ( 'memory.heapsnapshot' ) ) ;
} ) ;
2022-03-13 20:49:02 +08:00
router . post ( '/quit' , function ( req , res ) {
2020-11-06 00:41:23 +08:00
process . exit ( ) ;
} ) ;
2020-10-29 21:38:28 +08:00
2022-03-13 20:49:02 +08:00
router . get ( '/' , function ( req , res ) {
2024-01-26 16:59:10 +08:00
res . redirect ( getEmptyRoomID ( ) ) ;
2020-11-06 00:41:23 +08:00
} ) ;
2020-10-28 23:27:41 +08:00
2022-03-13 20:49:02 +08:00
router . get ( '/dl/:room/:state/:variant' , function ( req , res , next ) {
2021-04-06 22:59:42 +08:00
downloadState ( res , req . params . room , req . params . state , req . params . variant ) . catch ( next ) ;
2020-11-28 00:28:02 +08:00
} ) ;
2022-03-13 20:49:02 +08:00
router . get ( '/dl/:room/:state' , function ( req , res , next ) {
2021-04-06 22:59:42 +08:00
downloadState ( res , req . params . room , req . params . state ) . catch ( next ) ;
2020-11-28 00:28:02 +08:00
} ) ;
2022-03-13 20:49:02 +08:00
router . get ( '/dl/:room' , function ( req , res , next ) {
2021-04-06 22:59:42 +08:00
downloadState ( res , req . params . room ) . catch ( next ) ;
2020-12-03 01:00:23 +08:00
} ) ;
2022-12-30 17:46:55 +08:00
function allowCORS ( req , res , next ) {
2022-09-29 19:19:47 +08:00
res . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
res . setHeader ( 'Access-Control-Allow-Methods' , 'GET,PUT,OPTIONS' ) ;
res . setHeader ( 'Access-Control-Allow-Headers' , 'Content-Type' ) ;
res . sendStatus ( 200 ) ;
2022-12-30 17:46:55 +08:00
}
router . options ( '/state/:room' , allowCORS ) ;
2022-09-29 19:19:47 +08:00
2022-03-13 20:49:02 +08:00
router . get ( '/state/:room' , function ( req , res , next ) {
2021-04-06 22:59:42 +08:00
ensureRoomIsLoaded ( req . params . room ) . then ( function ( isLoaded ) {
if ( isLoaded ) {
2021-04-14 06:18:30 +08:00
res . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
2021-04-06 22:59:42 +08:00
res . setHeader ( 'Content-Type' , 'application/json' ) ;
const state = { ... activeRooms . get ( req . params . room ) . state } ;
delete state . _meta ;
res . send ( JSON . stringify ( state , null , ' ' ) ) ;
2024-01-26 16:59:10 +08:00
} else {
res . status ( 404 ) . send ( 'Invalid room.' ) ;
2021-04-06 22:59:42 +08:00
}
} ) . catch ( next ) ;
2020-12-25 05:37:37 +08:00
} ) ;
2022-03-13 20:49:02 +08:00
router . put ( '/state/:room' , bodyParser . json ( { limit : '10mb' } ) , function ( req , res , next ) {
2021-04-14 06:18:30 +08:00
res . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
2020-12-26 00:14:37 +08:00
if ( typeof req . body == 'object' ) {
2021-04-06 22:59:42 +08:00
ensureRoomIsLoaded ( req . params . room ) . then ( function ( isLoaded ) {
if ( isLoaded ) {
activeRooms . get ( req . params . room ) . setState ( req . body ) ;
res . send ( 'OK' ) ;
2024-01-26 16:59:10 +08:00
} else {
res . status ( 404 ) . send ( 'Invalid room.' ) ;
2021-04-06 22:59:42 +08:00
}
} ) . catch ( next ) ;
2020-12-26 00:14:37 +08:00
} else {
res . send ( 'not a valid JSON object' ) ;
}
2020-12-25 05:37:37 +08:00
} ) ;
2022-12-30 17:46:55 +08:00
router . options ( '/api/addShareToRoom/:room/:share' , allowCORS ) ;
router . get ( '/api/addShareToRoom/:room/:share' , function ( req , res , next ) {
res . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
2024-01-26 16:59:10 +08:00
const isPublicLibraryGame = req . params . share . match ( /^PL:(game|tutorial):([a-z-]+)$/ ) ;
if ( ! isPublicLibraryGame && ! sharedLinks [ ` /s/ ${ req . params . share } ` ] )
2022-12-30 17:46:55 +08:00
return res . sendStatus ( 404 ) ;
2024-01-26 16:59:10 +08:00
ensureRoomIsLoaded ( req . params . room ) . then ( async function ( isLoaded ) {
2022-12-30 17:46:55 +08:00
if ( isLoaded ) {
2024-01-26 16:59:10 +08:00
const newStateID = await activeRooms . get ( req . params . room ) . addShare ( req . params . share ) ;
res . send ( newStateID ) ;
} else {
res . status ( 404 ) . send ( 'Invalid room.' ) ;
2022-12-30 17:46:55 +08:00
}
} ) . catch ( next ) ;
} ) ;
2024-01-26 16:59:10 +08:00
async function shareDetails ( shareID ) {
const isPublicLibraryGame = shareID . match ( /^PL:(game|tutorial):([a-z-]+)$/ ) ;
if ( ! isPublicLibraryGame && ! sharedLinks [ ` /s/ ${ shareID } ` ] )
return null ;
const roomID = isPublicLibraryGame ? 'dummy' : sharedLinks [ ` /s/ ${ shareID } ` ] . split ( '/' ) [ 2 ] ;
const stateID = isPublicLibraryGame ? shareID : sharedLinks [ ` /s/ ${ shareID } ` ] . split ( '/' ) [ 3 ] ;
if ( ! await ensureRoomIsLoaded ( roomID ) )
return null ;
return Object . assign ( { } , activeRooms . get ( roomID ) . getStateDetails ( stateID ) , { emptyRoomID : getEmptyRoomID ( ) } ) ;
}
2022-12-30 17:46:55 +08:00
router . options ( '/api/shareDetails/:share' , allowCORS ) ;
2024-01-26 16:59:10 +08:00
router . get ( '/api/shareDetails/:share' , async function ( req , res , next ) {
2022-12-30 17:46:55 +08:00
res . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
2024-01-26 16:59:10 +08:00
try {
const details = await shareDetails ( req . params . share ) ;
res . setHeader ( 'Content-Type' , 'application/json' ) ;
res . send ( JSON . stringify ( details ) ) ;
} catch ( e ) {
return res . status ( 404 ) . send ( 'Invalid share.' ) ;
}
2022-12-30 17:46:55 +08:00
} ) ;
2022-03-13 20:49:02 +08:00
router . get ( '/s/:link/:junk' , function ( req , res , next ) {
2020-12-04 21:21:31 +08:00
if ( ! sharedLinks [ ` /s/ ${ req . params . link } ` ] )
2024-01-26 16:59:10 +08:00
return res . status ( 404 ) . send ( 'Invalid share.' ) ;
2020-12-04 21:21:31 +08:00
const tokens = sharedLinks [ ` /s/ ${ req . params . link } ` ] . split ( '/' ) ;
2021-04-06 22:59:42 +08:00
downloadState ( res , tokens [ 2 ] , tokens [ 3 ] ) . catch ( next ) ;
2020-12-04 21:21:31 +08:00
} ) ;
2022-12-30 17:46:55 +08:00
router . get ( '/share/:room/:state' , function ( req , res , next ) {
const target = ` /dl/ ${ req . params . room } / ${ req . params . state } ` ;
2020-12-04 21:21:31 +08:00
for ( const link in sharedLinks )
if ( sharedLinks [ link ] == target )
2024-01-26 16:59:10 +08:00
return res . send ( Config . get ( 'urlPrefix' ) + link . replace ( /^\/s\// , '/game/' ) ) ;
2020-12-04 21:21:31 +08:00
2022-12-30 17:46:55 +08:00
ensureRoomIsLoaded ( req . params . room ) . then ( function ( isLoaded ) {
if ( isLoaded )
activeRooms . get ( req . params . room ) . writeToFilesystem ( ) ;
const newLink = ` /s/ ${ Math . random ( ) . toString ( 36 ) . substring ( 3 , 11 ) } ` ;
sharedLinks [ newLink ] = target ;
fs . writeFileSync ( savedir + '/shares.json' , JSON . stringify ( sharedLinks ) ) ;
2024-01-26 16:59:10 +08:00
res . send ( Config . get ( 'urlPrefix' ) + newLink . replace ( /^\/s\// , '/game/' ) ) ;
2022-12-30 17:46:55 +08:00
} ) . catch ( next ) ;
2020-12-04 21:21:31 +08:00
} ) ;
2023-09-20 22:23:00 +08:00
router . get ( '/edit.js' , function ( req , res , next ) {
res . setHeader ( 'Content-Type' , 'text/javascript' ) ;
if ( req . headers [ 'accept-encoding' ] && req . headers [ 'accept-encoding' ] . match ( /\bgzip\b/ ) ) {
res . setHeader ( 'Content-Encoding' , 'gzip' ) ;
res . send ( result . editorJSgzipped ) ;
} else {
res . send ( result . editorJSmin ) ;
}
} ) ;
2023-10-04 03:30:25 +08:00
function createBotPattern ( crawlers ) {
// Join all the patterns using the | operator
const combinedPattern = crawlers . filter ( c => c . pattern != 'HeadlessChrome' ) . map ( c => c . pattern ) . join ( '|' ) ;
// Create and return the compiled regex pattern
return new RegExp ( combinedPattern ) ;
}
const botPattern = createBotPattern ( crawlers ) ;
2024-01-26 16:59:10 +08:00
router . get ( '/:room' , gameRoomHandler ) ;
router . get ( '/game/:plName' , gameRoomHandler ) ;
router . get ( '/game/:shareID/:name' , gameRoomHandler ) ;
router . get ( '/tutorial/:plName' , gameRoomHandler ) ;
async function gameRoomHandler ( req , res , next ) {
2024-10-07 01:33:17 +08:00
try {
if ( ! String ( req . params . room ) . match ( /^[A-Za-z0-9_-]+$/ ) ) {
res . send ( 'Invalid characters in room ID.' ) ;
return ;
}
2024-01-26 16:59:10 +08:00
2024-10-07 01:33:17 +08:00
if ( botPattern . test ( req . headers [ 'user-agent' ] ) ) {
let ogOutput = ` <meta property="og:title" content=" ${ Config . get ( 'serverName' ) } " /> ` ;
res . setHeader ( 'Content-Type' , 'text/html' ) ;
if ( req . params . room ) {
if ( await ensureRoomIsLoaded ( req . params . room ) ) {
const room = activeRooms . get ( req . params . room ) ;
let game = null ;
if ( room . state && room . state . _meta && room . state . _meta . activeState && room . state . _meta . states && room . state . _meta . states [ room . state . _meta . activeState . stateID ] )
game = room . state . _meta . states [ room . state . _meta . activeState . stateID ] ;
if ( game ) {
ogOutput += ` <meta property="og:description" content="Come play the game ${ game . name } with me!" /> ` ;
ogOutput += ` <meta property="og:image" content=" ${ Config . get ( 'externalURL' ) } / ${ game . image ? game . image . substr ( 1 ) : 'i/branding/android-512.png' } " /> ` ;
} else {
ogOutput += ` <meta property="og:description" content="Come play with me!" /> ` ;
ogOutput += ` <meta property="og:image" content=" ${ Config . get ( 'externalURL' ) } /i/branding/android-512.png" /> ` ;
}
}
} else {
const share = await shareDetails ( req . params . shareID || ` PL: ${ req . url . split ( '/' ) [ 1 ] } : ${ req . params . plName } ` ) ;
if ( share && req . url . split ( '/' ) [ 1 ] == 'tutorial' ) {
ogOutput += ` <meta property="og:description" content="Come look at the tutorial ${ share . name } !" /> ` ;
ogOutput += ` <meta property="og:image" content=" ${ Config . get ( 'externalURL' ) } / ${ share . image ? share . image . substr ( 1 ) : 'i/branding/android-512.png' } " /> ` ;
} else if ( share ) {
ogOutput += ` <meta property="og:description" content="Come play the game ${ share . name } with your friends!" /> ` ;
ogOutput += ` <meta property="og:image" content=" ${ Config . get ( 'externalURL' ) } / ${ share . image ? share . image . substr ( 1 ) : 'i/branding/android-512.png' } " /> ` ;
2024-01-26 16:59:10 +08:00
} else {
2024-10-07 01:33:17 +08:00
ogOutput += ` <meta property="og:description" content="Come play with your friends!" /> ` ;
2024-01-26 16:59:10 +08:00
ogOutput += ` <meta property="og:image" content=" ${ Config . get ( 'externalURL' ) } /i/branding/android-512.png" /> ` ;
}
2023-10-04 03:30:25 +08:00
}
2024-10-07 01:33:17 +08:00
ogOutput += ` <p>Your browser identifies as a bot and therefor only receives metadata. Please use a different browser and/or <a href="https://github.com/ArnoldSmith86/virtualtabletop/issues/new">open an issue on GitHub</a>.</p> ` ;
res . send ( ogOutput ) ;
2021-04-06 22:59:42 +08:00
} else {
2024-10-07 01:33:17 +08:00
res . setHeader ( 'Content-Type' , 'text/html' ) ;
if ( req . headers [ 'accept-encoding' ] && req . headers [ 'accept-encoding' ] . match ( /\bgzip\b/ ) ) {
res . setHeader ( 'Content-Encoding' , 'gzip' ) ;
res . send ( result . gzipped ) ;
2023-10-04 03:30:25 +08:00
} else {
2024-10-07 01:33:17 +08:00
res . send ( result . min ) ;
2023-10-04 03:30:25 +08:00
}
2021-04-06 22:59:42 +08:00
}
2024-10-07 01:33:17 +08:00
} catch ( e ) {
next ( e ) ;
2024-01-26 16:59:10 +08:00
}
}
2020-10-28 22:31:51 +08:00
2022-12-30 17:46:55 +08:00
router . get ( '/createTempState/:room' , function ( req , res , next ) {
ensureRoomIsLoaded ( req . params . room ) . then ( async function ( isLoaded ) {
if ( isLoaded )
res . send ( await activeRooms . get ( req . params . room ) . createTempState ( ) ) ;
} ) . catch ( next ) ;
} ) ;
router . put ( '/createTempState/:room/:tempID' , bodyParser . raw ( { limit : '500mb' } ) , function ( req , res , next ) {
ensureRoomIsLoaded ( req . params . room ) . then ( async function ( isLoaded ) {
if ( isLoaded && req . params . tempID . match ( /^[a-z0-9]{8}$/ ) )
res . send ( await activeRooms . get ( req . params . room ) . createTempState ( req . params . tempID , req . body ) ) ;
} ) . catch ( next ) ;
} ) ;
2023-09-20 22:23:00 +08:00
router . put ( '/asset/:link' , async function ( req , res ) {
2023-10-25 21:41:16 +08:00
try {
const content = Buffer . from ( await ( await fetch ( req . params . link ) ) . arrayBuffer ( ) ) ;
const filename = ` / ${ CRC32 . buf ( content ) } _ ${ content . length } ` ;
if ( ! Config . resolveAsset ( filename . substr ( 1 ) ) )
fs . writeFileSync ( assetsdir + filename , content ) ;
res . send ( ` /assets ${ filename } ` ) ;
} catch ( e ) {
res . status ( 404 ) . send ( 'Downloading external asset failed.' ) ;
}
2023-09-20 22:23:00 +08:00
} ) ;
2022-03-13 20:49:02 +08:00
router . put ( '/asset' , bodyParser . raw ( { limit : '10mb' } ) , function ( req , res ) {
2021-12-01 22:33:07 +08:00
const filename = ` / ${ CRC32 . buf ( req . body ) } _ ${ req . body . length } ` ;
2022-12-30 17:46:55 +08:00
if ( ! Config . resolveAsset ( filename . substr ( 1 ) ) )
2021-12-01 22:33:07 +08:00
fs . writeFileSync ( assetsdir + filename , req . body ) ;
2021-12-02 03:48:03 +08:00
res . send ( ` /assets ${ filename } ` ) ;
2020-12-04 04:06:56 +08:00
} ) ;
2022-03-13 20:49:02 +08:00
router . put ( '/addState/:room/:id/:type/:name/:addAsVariant?' , bodyParser . raw ( { limit : '500mb' } ) , async function ( req , res , next ) {
2022-12-30 17:46:55 +08:00
if ( ! validateInput ( res , next , [ req . params . id , req . params . addAsVariant ] ) ) return ;
2021-05-12 17:15:58 +08:00
ensureRoomIsLoaded ( req . params . room ) . then ( function ( isLoaded ) {
if ( isLoaded ) {
activeRooms . get ( req . params . room ) . addState ( req . params . id , req . params . type , req . body , req . params . name , req . params . addAsVariant ) . then ( function ( ) {
res . send ( 'OK' ) ;
} ) . catch ( next ) ;
}
} ) . catch ( next ) ;
} ) ;
2023-11-03 23:58:43 +08:00
router . get ( '/saveCurrentState/:room/:mode/:name' , async function ( req , res , next ) {
if ( ! validateInput ( res , next , [ req . params . mode ] ) ) return ;
ensureRoomIsLoaded ( req . params . room ) . then ( function ( isLoaded ) {
if ( isLoaded ) {
activeRooms . get ( req . params . room ) . saveCurrentState ( req . params . mode , req . params . name ) ;
res . send ( 'OK' ) ;
}
} ) . catch ( next ) ;
} ) ;
2022-03-13 20:49:02 +08:00
router . put ( '/moveServer/:room/:returnServer/:returnState' , bodyParser . raw ( { limit : '500mb' } ) , async function ( req , res , next ) {
2021-12-01 22:33:07 +08:00
ensureRoomIsLoaded ( req . params . room ) . then ( function ( isLoaded ) {
if ( isLoaded ) {
activeRooms . get ( req . params . room ) . receiveState ( req . body , req . params . returnServer , req . params . returnState ) . then ( function ( ) {
res . send ( 'OK' ) ;
} ) . catch ( next ) ;
}
} ) . catch ( next ) ;
} ) ;
2024-10-07 01:42:24 +08:00
router . put ( '/clientError' , bodyParser . json ( { limit : '50mb' } ) , function ( req , res , next ) {
if ( typeof req . body == 'object' ) {
const errorID = Math . random ( ) . toString ( 36 ) . substring ( 2 , 10 ) ;
fs . writeFileSync ( savedir + '/errors/' + errorID + '.json' , JSON . stringify ( req . body , null , ' ' ) ) ;
Logging . log ( ` ERROR: Client error ${ errorID } : ${ req . body . message } ` ) ;
res . send ( errorID ) ;
} else {
res . send ( 'not a valid JSON object' ) ;
}
} ) ;
2022-03-13 20:49:02 +08:00
router . use ( Logging . userErrorHandler ) ;
2021-04-06 22:59:42 +08:00
2022-03-13 20:49:02 +08:00
router . use ( Logging . errorHandler ) ;
2021-04-06 22:59:42 +08:00
2021-12-01 22:33:07 +08:00
server . listen ( Config . get ( 'port' ) , function ( ) {
2021-04-14 21:13:16 +08:00
Logging . log ( ` Listening on ${ server . address ( ) . port } ` ) ;
2020-11-06 00:41:23 +08:00
} ) ;
2020-10-28 23:27:41 +08:00
} ) ;
2020-10-29 01:41:30 +08:00
const activeRooms = new Map ( ) ;
2021-04-10 19:58:10 +08:00
const ws = new WebSocket ( server , serverStart , function ( connection , { playerName , roomID } ) {
2021-04-06 22:59:42 +08:00
ensureRoomIsLoaded ( roomID ) . then ( function ( isLoaded ) {
if ( isLoaded )
activeRooms . get ( roomID ) . addPlayer ( new Player ( connection , playerName , activeRooms . get ( roomID ) ) ) ;
} ) . catch ( e => Logging . handleGenericException ( ` player ${ playerName } connected to room ${ roomID } ` , e ) ) ;
2020-10-28 22:31:51 +08:00
} ) ;
2020-10-30 22:15:51 +08:00
2021-01-28 01:21:41 +08:00
autosaveRooms ( ) ;
2020-11-03 07:18:52 +08:00
[ 'exit' , 'SIGINT' , 'SIGUSR1' , 'SIGUSR2' , 'SIGTERM' ] . forEach ( ( eventType ) => {
2020-10-30 22:15:51 +08:00
process . on ( eventType , function ( ) {
for ( const [ _ , room ] of activeRooms )
room . unload ( ) ;
2022-12-30 17:46:55 +08:00
Statistics . writeToFilesystem ( ) ;
2020-10-31 00:06:08 +08:00
if ( eventType != 'exit' )
process . exit ( ) ;
2020-10-30 22:15:51 +08:00
} ) ;
2020-11-03 07:18:52 +08:00
} ) ;