From: Brendan Hansen Date: Fri, 27 Sep 2019 04:33:40 +0000 (-0500) Subject: Added better live updater and various changes X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=14e822a204686a1aa9c6b16722ca4a2db4ca90d4;p=codebox.git Added better live updater and various changes --- diff --git a/codebox/app/app.moon b/codebox/app/app.moon index 39a3812..dc17507 100644 --- a/codebox/app/app.moon +++ b/codebox/app/app.moon @@ -3,6 +3,7 @@ console = require "lapis.console" bind = require "utils.binding" bind\bind_static 'executer', require 'facades.executer' +bind\bind_static 'updater', require 'facades.updater' bind\bind_static 'crypto', require 'services.crypto' bind\bind_static 'uuidv4', require 'services.uuid' bind\bind_static 'queries', require 'services.queries' @@ -29,6 +30,7 @@ class extends lapis.Application @navbar.selected = -1 @scripts = {} + @raw_scripts = {} ['index': "/"]: => redirect_to: @url_for 'problem' @@ -38,6 +40,7 @@ class extends lapis.Application ['account.account': "/account"]: controller "account.account" ['leaderboard': '/leaderboard']: controller "leaderboard.view" + ['leaderboard.update': '/leaderboard/update']: controller "leaderboard.update" ['problem': '/problems']: controller "problem.problem" ['problem.description': '/problems/:problem_name']: controller "problem.problem" diff --git a/codebox/config.moon b/codebox/config.moon index e622cca..1a67555 100644 --- a/codebox/config.moon +++ b/codebox/config.moon @@ -7,6 +7,7 @@ config "development", -> req_secret (os.getenv 'REQ_SECRET') executer_addr 'http://192.168.0.4:8080' + updater_addr 'http://192.168.0.5:5000' postgres -> -- Have to use a fixed ip since the container name diff --git a/codebox/controllers/account/register.moon b/codebox/controllers/account/register.moon index 4262eb8..34fa808 100644 --- a/codebox/controllers/account/register.moon +++ b/codebox/controllers/account/register.moon @@ -18,8 +18,8 @@ make_controller @flow 'csrf_validate' assert_valid @params, { - { "username", exists: true, min_length: 2, matches_pattern: "^%w+$" } - { "nickname", exists: true, min_length: 2 } + { "username", exists: true, min_length: 1, matches_pattern: "^%w+$" } + { "nickname", exists: true, min_length: 1 } { "email", exists: true, min_length: 4, matches_pattern: "^%S+@%S+%.%S+$" } { "password", exists: true, min_length: 2 } { "password_confirmation", exists: true, min_length: 2, equals: @params.password, 'Passwords must be the same' } diff --git a/codebox/controllers/controller.moon b/codebox/controllers/controller.moon index 3ab6bfe..8816f9b 100644 --- a/codebox/controllers/controller.moon +++ b/codebox/controllers/controller.moon @@ -12,6 +12,10 @@ import respond_to from require "lapis.application" for s in *routes.scripts table.insert @scripts, s + if routes.raw_scripts + for s in *routes.raw_scripts + table.insert @raw_scripts, s + return if not routes.middleware for middleware in *routes.middleware diff --git a/codebox/controllers/executer/status_update.moon b/codebox/controllers/executer/status_update.moon index 9949072..665c4a8 100644 --- a/codebox/controllers/executer/status_update.moon +++ b/codebox/controllers/executer/status_update.moon @@ -7,6 +7,7 @@ import capture_errors, yield_error from require 'lapis.application' make_controller inject: scoring: 'scoring' + updater: 'updater' middleware: { 'internal_request' } @@ -32,6 +33,8 @@ make_controller @scoring\score job.user_id, job.problem_id @scoring\place! + @updater\push_submission_update job.id + json: { status: 'success' } ), => json: { status: 'error', errors: @errors } diff --git a/codebox/controllers/leaderboard/update.moon b/codebox/controllers/leaderboard/update.moon new file mode 100644 index 0000000..59efe8d --- /dev/null +++ b/codebox/controllers/leaderboard/update.moon @@ -0,0 +1,13 @@ +import make_controller from require "controllers.controller" +Leaderboard = require 'views.ssr.leaderboard' + +make_controller + layout: false + middleware: { 'logged_in', 'competition_started' } + + get: => + @placements = @competition\get_leaderboard! + + leaderboard_widget = Leaderboard @placements + leaderboard_widget\include_helper @ + return { layout: false, status_code: 200, leaderboard_widget\render_to_string! } diff --git a/codebox/controllers/problem/problem.moon b/codebox/controllers/problem/problem.moon index 66f9090..087b9fa 100644 --- a/codebox/controllers/problem/problem.moon +++ b/codebox/controllers/problem/problem.moon @@ -8,6 +8,10 @@ make_controller middleware: { 'logged_in', 'competition_started' } scripts: { "pie_chart" } + raw_scripts: { + "https://polyfill.io/v3/polyfill.min.js?features=es6" + "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" + } get: capture_errors_json => @navbar.selected = 1 diff --git a/codebox/controllers/submission/status.moon b/codebox/controllers/submission/status.moon index 6b1ff30..e641e68 100644 --- a/codebox/controllers/submission/status.moon +++ b/codebox/controllers/submission/status.moon @@ -28,4 +28,4 @@ make_controller status_widget = JobResult @job status_widget\include_helper @ - return { layout: false, status: status_code, status_widget\render_to_string! } \ No newline at end of file + return { layout: false, status: status_code, status_widget\render_to_string! } diff --git a/codebox/controllers/submission/view.moon b/codebox/controllers/submission/view.moon index f55f70a..9ca09d0 100644 --- a/codebox/controllers/submission/view.moon +++ b/codebox/controllers/submission/view.moon @@ -10,6 +10,7 @@ make_controller middleware: { 'logged_in' } scripts: { 'vendor/ace/ace', 'submission_reloader' } + raw_scripts: { '/socket.io/socket.io.js' } get: capture_errors_json => @navbar.selected = 2 @@ -22,4 +23,4 @@ make_controller unless @job.user_id == @user.id yield_error "You are not allowed to view this submission!" - render: 'submission.view' \ No newline at end of file + render: 'submission.view' diff --git a/codebox/facades/updater.moon b/codebox/facades/updater.moon new file mode 100644 index 0000000..5557dd3 --- /dev/null +++ b/codebox/facades/updater.moon @@ -0,0 +1,6 @@ +config = (require 'lapis.config').get! +http = require 'lapis.nginx.http' + +class UpdaterFacade + push_submission_update: (job_id) => + http.simple "#{config.updater_addr}/submission_update?submission_ida=#{job_id}" diff --git a/codebox/nginx.conf b/codebox/nginx.conf index 6649d8f..fd664e7 100644 --- a/codebox/nginx.conf +++ b/codebox/nginx.conf @@ -60,5 +60,17 @@ http { proxy_http_version 1.1; proxy_pass $_url; } + + location ~* \.io { + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_pass http://192.168.0.5:5000; + proxy_redirect off; + } } } diff --git a/codebox/services/scoring.moon b/codebox/services/scoring.moon index 25eaf51..a7d705f 100644 --- a/codebox/services/scoring.moon +++ b/codebox/services/scoring.moon @@ -118,13 +118,11 @@ class Scoring extends Injectable score_user: (user_id) => for p in *@comp_problems - problem = p\get_problem! - @score_problem_for_user user_id, problem + @score user_id, p.problem_id - score_problem: (problem_name) => - problem = Problems\find short_name: problem_name + score_problem: (problem_id) => for u in *@users - @score_problem_for_user u.id, problem + @score u.id, problem_id score_all: => for p in *@comp_problems diff --git a/codebox/static/coffee/leaderboard_update.coffee b/codebox/static/coffee/leaderboard_update.coffee new file mode 100644 index 0000000..2156982 --- /dev/null +++ b/codebox/static/coffee/leaderboard_update.coffee @@ -0,0 +1,2 @@ +$(document).ready -> + console.log "Hello!" diff --git a/codebox/static/coffee/submission_reloader.coffee b/codebox/static/coffee/submission_reloader.coffee index 4515b2d..3b13791 100644 --- a/codebox/static/coffee/submission_reloader.coffee +++ b/codebox/static/coffee/submission_reloader.coffee @@ -4,8 +4,9 @@ updateStatus = -> $.get '/submissions/status', { submission_id: submission_id }, (html, _, data) -> $('#status-container').html html - if data.status == 200 - setTimeout updateStatus, 100 - $(document).ready -> - updateStatus() \ No newline at end of file + socket = io() + socket.emit "request-submission-updates", submission_id + + socket.on 'update', -> + updateStatus() diff --git a/codebox/static/js/leaderboard_update.js b/codebox/static/js/leaderboard_update.js new file mode 100644 index 0000000..748dde3 --- /dev/null +++ b/codebox/static/js/leaderboard_update.js @@ -0,0 +1,9 @@ +// Generated by CoffeeScript 2.4.1 +(function() { + $(document).ready(function() { + return console.log("Hello!"); + }); + +}).call(this); + +//# sourceMappingURL=leaderboard_update.js.map diff --git a/codebox/static/js/leaderboard_update.js.map b/codebox/static/js/leaderboard_update.js.map new file mode 100644 index 0000000..b4ee4fa --- /dev/null +++ b/codebox/static/js/leaderboard_update.js.map @@ -0,0 +1,13 @@ +{ + "version": 3, + "file": "leaderboard_update.js", + "sourceRoot": "..", + "sources": [ + "coffee/leaderboard_update.coffee" + ], + "names": [], + "mappings": ";AAAA;EAAA,CAAA,CAAE,QAAF,CAAW,CAAC,KAAZ,CAAkB,QAAA,CAAA,CAAA;WACd,OAAO,CAAC,GAAR,CAAY,QAAZ;EADc,CAAlB;AAAA", + "sourcesContent": [ + "$(document).ready ->\n console.log \"Hello!\"\n" + ] +} \ No newline at end of file diff --git a/codebox/static/js/submission_reloader.js b/codebox/static/js/submission_reloader.js index add5a3d..ea24824 100644 --- a/codebox/static/js/submission_reloader.js +++ b/codebox/static/js/submission_reloader.js @@ -8,15 +8,17 @@ return $.get('/submissions/status', { submission_id: submission_id }, function(html, _, data) { - $('#status-container').html(html); - if (data.status === 200) { - return setTimeout(updateStatus, 100); - } + return $('#status-container').html(html); }); }; $(document).ready(function() { - return updateStatus(); + var socket; + socket = io(); + socket.emit("request-submission-updates", submission_id); + return socket.on('update', function() { + return updateStatus(); + }); }); }).call(this); diff --git a/codebox/static/js/submission_reloader.js.map b/codebox/static/js/submission_reloader.js.map index 1157375..a00c822 100644 --- a/codebox/static/js/submission_reloader.js.map +++ b/codebox/static/js/submission_reloader.js.map @@ -6,8 +6,8 @@ "coffee/submission_reloader.coffee" ], "names": [], - "mappings": ";AAAA;AAAA,MAAA,aAAA,EAAA;;EAAA,aAAA,GAAgB,CAAC,IAAI,eAAJ,CAAoB,MAAM,CAAC,QAAQ,CAAC,MAApC,CAAD,CAA4C,CAAC,GAA7C,CAAiD,eAAjD;;EAEhB,YAAA,GAAe,QAAA,CAAA,CAAA;WACX,CAAC,CAAC,GAAF,CAAM,qBAAN,EAA6B;MAAE,aAAA,EAAe;IAAjB,CAA7B,EAA+D,QAAA,CAAC,IAAD,EAAO,CAAP,EAAU,IAAV,CAAA;MAC3D,CAAA,CAAE,mBAAF,CAAsB,CAAC,IAAvB,CAA4B,IAA5B;MAEA,IAAG,IAAI,CAAC,MAAL,KAAe,GAAlB;eACI,UAAA,CAAW,YAAX,EAAyB,GAAzB,EADJ;;IAH2D,CAA/D;EADW;;EAOf,CAAA,CAAE,QAAF,CAAW,CAAC,KAAZ,CAAkB,QAAA,CAAA,CAAA;WACd,YAAA,CAAA;EADc,CAAlB;AATA", + "mappings": ";AAAA;AAAA,MAAA,aAAA,EAAA;;EAAA,aAAA,GAAgB,CAAC,IAAI,eAAJ,CAAoB,MAAM,CAAC,QAAQ,CAAC,MAApC,CAAD,CAA4C,CAAC,GAA7C,CAAiD,eAAjD;;EAEhB,YAAA,GAAe,QAAA,CAAA,CAAA;WACX,CAAC,CAAC,GAAF,CAAM,qBAAN,EAA6B;MAAE,aAAA,EAAe;IAAjB,CAA7B,EAA+D,QAAA,CAAC,IAAD,EAAO,CAAP,EAAU,IAAV,CAAA;aAC3D,CAAA,CAAE,mBAAF,CAAsB,CAAC,IAAvB,CAA4B,IAA5B;IAD2D,CAA/D;EADW;;EAIf,CAAA,CAAE,QAAF,CAAW,CAAC,KAAZ,CAAkB,QAAA,CAAA,CAAA;AACjB,QAAA;IAAA,MAAA,GAAS,EAAA,CAAA;IACT,MAAM,CAAC,IAAP,CAAY,4BAAZ,EAA0C,aAA1C;WAEA,MAAM,CAAC,EAAP,CAAU,QAAV,EAAoB,QAAA,CAAA,CAAA;aACnB,YAAA,CAAA;IADmB,CAApB;EAJiB,CAAlB;AANA", "sourcesContent": [ - "submission_id = (new URLSearchParams window.location.search).get 'submission_id'\n\nupdateStatus = ->\n $.get '/submissions/status', { submission_id: submission_id }, (html, _, data) ->\n $('#status-container').html html\n\n if data.status == 200\n setTimeout updateStatus, 100\n\n$(document).ready ->\n updateStatus()" + "submission_id = (new URLSearchParams window.location.search).get 'submission_id'\n\nupdateStatus = ->\n $.get '/submissions/status', { submission_id: submission_id }, (html, _, data) ->\n $('#status-container').html html\n\n$(document).ready ->\n\tsocket = io()\n\tsocket.emit \"request-submission-updates\", submission_id\n\n\tsocket.on 'update', ->\n\t\tupdateStatus()\n" ] } \ No newline at end of file diff --git a/codebox/views/account/register.moon b/codebox/views/account/register.moon index 1061516..d2790c9 100644 --- a/codebox/views/account/register.moon +++ b/codebox/views/account/register.moon @@ -9,15 +9,18 @@ class Register extends html.Widget div class: 'split-1-1', -> form method: 'POST', -> input type: 'hidden', name: 'csrf_token', value: @csrf_token - + label for: 'username', 'Username' p -> input type: 'text', placeholder: 'Username', name: 'username', required: true, '' - label for: 'password', 'Password' - p -> input type: 'password', placeholder: 'Password', name: 'password', required: true, '' + div class: 'split-2', -> + div -> + label for: 'password', 'Password' + p -> input type: 'password', placeholder: 'Password', name: 'password', required: true, '' - label for: 'password_confirmation', 'Confirm Password' - p -> input type: 'password', placeholder: 'Confirm Password', name: 'password_confirmation', required: true, '' + div -> + label for: 'password_confirmation', 'Confirm Password' + p -> input type: 'password', placeholder: 'Confirm Password', name: 'password_confirmation', required: true, '' label for: 'email', 'Email' p -> input type: 'text', placeholder: 'Email', name: 'email', required: true, '' diff --git a/codebox/views/leaderboard/view.moon b/codebox/views/leaderboard/view.moon index 6a9a9b7..4cf3ff5 100644 --- a/codebox/views/leaderboard/view.moon +++ b/codebox/views/leaderboard/view.moon @@ -1,50 +1,10 @@ html = require 'lapis.html' import CompetitionProblems, LeaderboardProblems from require 'models' +Leaderboard = require 'views.ssr.leaderboard' -class Leaderboard extends html.Widget +class LeaderboardView extends html.Widget content: => h1 "#{@competition.name} - Leaderboard" div class: 'content', -> - div class: 'leaderboard', -> - drawn_labels = false - for place in *@placements - @problems = place\get_problems! - CompetitionProblems\include_in @problems, "problem_id", - as: 'cp' - flip: true - local_key: 'problem_id' - where: { competition_id: @competition.id } - - -- Sort the problems by letter - prob.lnum = (prob.cp.letter\byte 1) for prob in *@problems - table.sort @problems, (a, b) -> - a.lnum < b.lnum - - unless drawn_labels - div class: 'placement-labels', -> - div "Place" - div "Name" - div class: 'problem', style: "grid-template-columns: repeat(#{#@problems}, 1fr)", -> - for prob in *@problems - div "#{prob.cp.letter}" - div "Score" - drawn_labels = true - - div class: 'placement', -> - div "#{place.place}" - div "#{place\get_user!.nickname}" - - div class: 'problem', style: "grid-template-columns: repeat(#{#@problems}, 1fr)", -> - for prob in *@problems - prob_status = switch prob.status - when LeaderboardProblems.statuses.correct then "correct" - when LeaderboardProblems.statuses.wrong then "wrong" - when LeaderboardProblems.statuses.attempted then "attempted" - - div class: "#{prob_status}", -> - div "#{prob.points}" - div "#{prob.attempts}" - - div "#{place.score}" - + widget (Leaderboard @placements) diff --git a/codebox/views/partials/layout.moon b/codebox/views/partials/layout.moon index 67f288a..17c18ac 100644 --- a/codebox/views/partials/layout.moon +++ b/codebox/views/partials/layout.moon @@ -14,6 +14,9 @@ class DefaultLayout extends html.Widget for s in *@scripts script type: "text/javascript", src: "/static/js/#{s}.js" + for s in *@raw_scripts + script type: "text/javascript", src: s + body -> widget Navbar widget ErrorList diff --git a/codebox/views/problem/problem.moon b/codebox/views/problem/problem.moon index 58eb465..6f9a9a4 100644 --- a/codebox/views/problem/problem.moon +++ b/codebox/views/problem/problem.moon @@ -4,9 +4,6 @@ import Problems from require 'models' class ProblemsView extends html.Widget content: => - raw ' - ' - div class: 'sidebar-page-container', -> div class: 'sidebar-problem-list', -> widget (require 'views.partials.problem_sidebar') diff --git a/codebox/views/ssr/leaderboard.moon b/codebox/views/ssr/leaderboard.moon new file mode 100644 index 0000000..2c5752f --- /dev/null +++ b/codebox/views/ssr/leaderboard.moon @@ -0,0 +1,48 @@ +html = require 'lapis.html' +import CompetitionProblems, LeaderboardProblems from require 'models' + +class Leaderboard extends html.Widget + new: (@placements) => + + content: => + div class: 'leaderboard', -> + drawn_labels = false + for place in *@placements + @problems = place\get_problems! + CompetitionProblems\include_in @problems, "problem_id", + as: 'cp' + flip: true + local_key: 'problem_id' + where: { competition_id: @competition.id } + -- Sort the problems by letter + prob.lnum = (prob.cp.letter\byte 1) for prob in *@problems + + table.sort @problems, (a, b) -> + a.lnum < b.lnum + + unless drawn_labels + div class: 'placement-labels', -> + div "Place" + div "Name" + div class: 'problem', style: "grid-template-columns: repeat(#{#@problems}, 1fr)", -> + for prob in *@problems + div "#{prob.cp.letter}" + div "Score" + drawn_labels = true + + div class: 'placement', -> + div "#{place.place}" + div "#{place\get_user!.nickname}" + + div class: 'problem', style: "grid-template-columns: repeat(#{#@problems}, 1fr)", -> + for prob in *@problems + prob_status = switch prob.status + when LeaderboardProblems.statuses.correct then "correct" + when LeaderboardProblems.statuses.wrong then "wrong" + when LeaderboardProblems.statuses.attempted then "attempted" + + div class: "#{prob_status}", -> + div "#{prob.points}" + div "#{prob.attempts}" + + div "#{place.score}" diff --git a/docker-compose.yaml b/docker-compose.yaml index 92278a6..6de52ed 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -38,6 +38,19 @@ services: appnet: ipv4_address: 192.168.0.4 + updater: + env_file: + - config.env + build: + context: . + dockerfile: ./docker/updater/Dockerfile + volumes: + - ./updater/app:/app/app + command: node main.js + networks: + appnet: + ipv4_address: 192.168.0.5 + postgres: env_file: - config.env diff --git a/docker/updater/Dockerfile b/docker/updater/Dockerfile new file mode 100644 index 0000000..b11ba78 --- /dev/null +++ b/docker/updater/Dockerfile @@ -0,0 +1,11 @@ +FROM node:12.9.1 + +RUN yarn global add coffeescript + +COPY ./updater/package.json /app/package.json +WORKDIR /app +RUN yarn + +COPY ./updater/main.js /app/main.js + +ENV PATH $(yarn global bin):$PATH diff --git a/executer/app/routes.coffee b/executer/app/routes.coffee index c7f97a3..4df44b8 100644 --- a/executer/app/routes.coffee +++ b/executer/app/routes.coffee @@ -24,7 +24,6 @@ async function handle_job(job_id, lang, code, cases, time_limit) { rej(-1); } - console.log("Updated job: ", job_id, status.status) resolve(1); } ) diff --git a/updater/.gitignore b/updater/.gitignore new file mode 100644 index 0000000..c2f7987 --- /dev/null +++ b/updater/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +.tup/ +yarn.lock +*.js diff --git a/updater/Tupfile b/updater/Tupfile new file mode 100644 index 0000000..f0fe651 --- /dev/null +++ b/updater/Tupfile @@ -0,0 +1 @@ +include_rules diff --git a/updater/Tuprules.tup b/updater/Tuprules.tup new file mode 100644 index 0000000..d3dceca --- /dev/null +++ b/updater/Tuprules.tup @@ -0,0 +1 @@ +: foreach *.coffee |> coffee -c -o %B.js %f |> %B.js \ No newline at end of file diff --git a/updater/app/Tupfile b/updater/app/Tupfile new file mode 100644 index 0000000..f0fe651 --- /dev/null +++ b/updater/app/Tupfile @@ -0,0 +1 @@ +include_rules diff --git a/updater/app/app.coffee b/updater/app/app.coffee new file mode 100644 index 0000000..71d9ff2 --- /dev/null +++ b/updater/app/app.coffee @@ -0,0 +1,67 @@ +express = require 'express' +app = express() +server = require('http').createServer(app) +io = require('socket.io')(server) + +class UpdateForwarder + constructor: -> + @channels = new Map() + + add_channel: (channel_name) -> + unless @channels.has channel_name + @channels.set channel_name, [] + + join_channel: (channel_name, socket) -> + return unless @channels.has channel_name + @channels.get(channel_name).push(socket) + + leave_channel: (channel_name, socket) -> + return unless @channels.has channel_name + + sockets = @channels.get channel_name + idx = sockets.indexOf socket + sockets.splice idx, 1 + + leave: (socket) -> + for chan from @channels.values() + idx = chan.indexOf socket + if idx != -1 + chan.splice idx, 1 + return + + push_update: (channel_name, param_match="") -> + return unless @channels.has channel_name + + for sock in @channels.get channel_name + if param_match != "" + if sock.param == param_match + sock.emit 'update', {} + else + sock.emit 'update', {} + return + +update_forwarder = new UpdateForwarder() +update_forwarder.add_channel "submission-updates" + +io.on 'connection', (socket) -> + # data is the submission id + socket.on 'request-submission-updates', (data) -> + socket.param = data + update_forwarder.join_channel "submission-updates", socket + + socket.once 'disconnect', -> + update_forwarder.leave socket + +app.get '/submission_update', (req, res) -> + submission_id = req.query.submission_id + update_forwarder.push_update "submission-updates", submission_id + + res.status 200 + res.end() + +main = -> + port = 5000 + console.log "Socket IO server running on port #{port}" + server.listen port + +module.exports = main diff --git a/updater/main.coffee b/updater/main.coffee new file mode 100644 index 0000000..0fd2414 --- /dev/null +++ b/updater/main.coffee @@ -0,0 +1,2 @@ +main = require "./app/app.js" +main() diff --git a/updater/package.json b/updater/package.json new file mode 100644 index 0000000..6c5fd88 --- /dev/null +++ b/updater/package.json @@ -0,0 +1,13 @@ +{ + "name": "codebox-socketio", + "version": "1.0.0", + "description": "SocketIO part of Codebox", + "main": "main.js", + "author": "Brendan Hansen", + "license": "MIT", + "private": true, + "dependencies": { + "express": "^4.17.1", + "socket.io": "^2.3.0" + } +}