improve, compress and sign the javascript

pull/83/head
Thibault Duplessis 2012-10-20 15:36:14 +02:00
parent af563653bd
commit 110e2a871d
18 changed files with 94 additions and 28 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ logs
project/project
project/target
public/trans
public/compiled
serve/
serve/README
target

View File

@ -83,6 +83,9 @@ trait LilaController
protected def JsonIOk(map: IO[Map[String, Any]]) = JsonOk(map.unsafePerformIO)
protected def JsIOk(js: IO[String], headers: (String, String)*) =
Ok(js.unsafePerformIO) as JAVASCRIPT withHeaders (headers: _*)
protected def ValidOk(valid: Valid[Unit]) = valid.fold(
e BadRequest(e.shows),
_ Ok("ok")

View File

@ -24,6 +24,7 @@ object Round extends LilaController with TheftPrevention with RoundEventPerforme
private def userRepo = env.user.userRepo
private def analyser = env.analyse.analyser
private def tournamentRepo = env.tournament.repo
private def gameJs = env.game.gameJs
def websocketWatcher(gameId: String, color: String) = WebSocket.async[JsValue] { req
implicit val ctx = reqToCtx(req)
@ -32,16 +33,22 @@ object Round extends LilaController with TheftPrevention with RoundEventPerforme
color,
getInt("version"),
get("sri"),
get("tk"),
ctx).unsafePerformIO
}
def websocketPlayer(fullId: String) = WebSocket.async[JsValue] { req
implicit val ctx = reqToCtx(req)
socket.joinPlayer(
fullId,
getInt("version"),
get("sri"),
ctx).unsafePerformIO
socket.joinPlayer(
fullId,
getInt("version"),
get("sri"),
get("tk"),
ctx).unsafePerformIO
}
def signedJs(gameId: String) = Open { implicit ctx
JsIOk(gameRepo token gameId map gameJs.sign, CACHE_CONTROL -> "max-age=3600")
}
def player(fullId: String) = Open { implicit ctx

View File

@ -25,6 +25,7 @@ final class Settings(config: Config, val IsDev: Boolean) {
val GamePaginatorMaxPerPage = getInt("game.paginator.max_per_page")
val GameCollectionGame = getString("game.collection.game")
val GameCollectionPgn = getString("game.collection.pgn")
val GameJsPath = getString("game.js_path")
val SearchESHost = getString("search.elasticsearch.host")
val SearchESPort = getInt("search.elasticsearch.port")

View File

@ -15,6 +15,7 @@ import scala.math.min
case class DbGame(
id: String,
token: String,
whitePlayer: DbPlayer,
blackPlayer: DbPlayer,
status: Status,
@ -322,6 +323,7 @@ case class DbGame(
def encode = RawDbGame(
id = id,
tk = token.some filter (DbGame.defaultToken !=),
p = players map (_.encode),
s = status.id,
t = turns,
@ -366,6 +368,8 @@ object DbGame {
val gameIdSize = 8
val playerIdSize = 4
val fullIdSize = 12
val tokenSize = 4
val defaultToken = "-tk-"
def abandonedDate = DateTime.now - 10.days
@ -380,6 +384,7 @@ object DbGame {
mode: Mode,
variant: Variant): DbGame = DbGame(
id = IdGenerator.game,
token = IdGenerator.token,
whitePlayer = whitePlayer withEncodedPieces game.allPieces,
blackPlayer = blackPlayer withEncodedPieces game.allPieces,
status = Status.Created,
@ -398,6 +403,7 @@ object DbGame {
case class RawDbGame(
@Key("_id") id: String,
tk: Option[String] = None,
p: List[RawDbPlayer],
s: Int,
t: Int,
@ -423,6 +429,7 @@ case class RawDbGame(
trueStatus Status(s)
} yield DbGame(
id = id,
token = tk | DbGame.defaultToken,
whitePlayer = whitePlayer,
blackPlayer = blackPlayer,
status = trueStatus,

View File

@ -33,4 +33,6 @@ final class GameEnv(
lazy val listMenu = ListMenu(cached) _
lazy val rewind = new Rewind
lazy val gameJs = new GameJs(settings.GameJsPath)
}

View File

@ -0,0 +1,14 @@
package lila
package game
final class GameJs(path: String) {
lazy val unsigned: String = {
val source = scala.io.Source fromFile path
source.mkString ~ { _ => source.close }
}
val placeholder = "--tkph--"
def sign(token: String) = unsigned.replace(placeholder, token)
}

View File

@ -48,6 +48,10 @@ final class GameRepo(collection: MongoCollection)
def pov(ref: PovRef): IO[Option[Pov]] = pov(ref.gameId, ref.color)
def token(id: String): IO[String] = io {
primitiveProjection[String](idSelector(id), "tk") | DbGame.defaultToken
}
def save(game: DbGame): IO[Unit] = io {
update(idSelector(game), _grater asDBObject game.encode)
}

View File

@ -7,5 +7,7 @@ object IdGenerator {
def game = Random nextString DbGame.gameIdSize
def token = Random nextString DbGame.tokenSize
def player = Random nextString DbGame.playerIdSize
}

View File

@ -112,15 +112,17 @@ final class Socket(
colorName: String,
version: Option[Int],
uid: Option[String],
token: Option[String],
ctx: Context): IO[SocketPromise] =
getWatcherPov(gameId, colorName) map { join(_, false, version, uid, ctx) }
getWatcherPov(gameId, colorName) map { join(_, false, version, uid, token, ctx) }
def joinPlayer(
fullId: String,
version: Option[Int],
uid: Option[String],
token: Option[String],
ctx: Context): IO[SocketPromise] =
getPlayerPov(fullId) map { join(_, true, version, uid, ctx) }
getPlayerPov(fullId) map { join(_, true, version, uid, token, ctx) }
private def parseMove(event: JsValue) = for {
d event obj "d"
@ -136,9 +138,10 @@ final class Socket(
owner: Boolean,
versionOption: Option[Int],
uidOption: Option[String],
tokenOption: Option[String],
ctx: Context): SocketPromise =
((povOption |@| uidOption |@| versionOption) apply {
(pov: Pov, uid: String, version: Int)
((povOption |@| uidOption |@| tokenOption |@| versionOption) apply {
(pov: Pov, uid: String, token: String, version: Int)
(for {
hub hubMaster ? GetHub(pov.gameId) mapTo manifest[ActorRef]
socket hub ? Join(
@ -146,10 +149,12 @@ final class Socket(
user = ctx.me,
version = version,
color = pov.color,
owner = owner
owner = owner && token == pov.game.token
) map {
case Connected(enumerator, member) {
if (owner && !member.owner) println("Websocket hijacking detected (%s) %s".format(pov.gameId, ctx.toString))
if (owner && !member.owner) {
println("Websocket hijacking detected %s %s".format(pov.gameId, ctx.toString))
}
(Iteratee.foreach[JsValue](
controller(hub, uid, member, PovRef(pov.gameId, member.color))
) mapDone { _

View File

@ -7,7 +7,7 @@ import play.api.templates.Html
trait AssetHelper {
val assetVersion = 4
val assetVersion = 6
def cssTag(name: String) = css("stylesheets/" + name)
@ -20,10 +20,13 @@ trait AssetHelper {
def jsTag(name: String) = js("javascripts/" + name)
def jsTagC(name: String) = js("compiled/" + name)
def jsVendorTag(name: String) = js("vendor/" + name)
def js(path: String) = Html {
"""<script src="%s?v=%d"></script>"""
.format(routes.Assets.at(path), assetVersion)
private def js(path: String) = jsAt(routes.Assets.at(path).toString)
def jsAt(path: String) = Html {
"""<script src="%s?v=%d"></script>""".format(path, assetVersion)
}
}

View File

@ -1,4 +1,4 @@
@(title: String, active: Option[ui.SiteMenu.Elem] = None, baseline: Option[Html] = None, goodies: Option[Html] = None, menu: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None, robots: Boolean = true, moreCss: Html = Html(""), moreJs: Html = Html(""))(body: Html)(implicit ctx: Context)
@(title: String, active: Option[ui.SiteMenu.Elem] = None, baseline: Option[Html] = None, goodies: Option[Html] = None, menu: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None, robots: Boolean = true, moreCss: Html = Html(""), moreJs: Html = Html(""), signedJs: Option[String] = None)(body: Html)(implicit ctx: Context)
<!doctype html>
<html lang="@lang.language">
@ -105,7 +105,7 @@
</div>
</div>
@jsTag("deps.min.js")
@jsTag("big.js")
@signedJs.fold(jsAt, jsTagC("big.js"))
@moreJs
@if(lang.language != "en") {
<script src="@routes.Assets.at("trans/" + lang.language + ".js")?v=@assetVersion"></script>

View File

@ -1,8 +1,10 @@
@(title: String, goodies: Html, chat: Option[Html] = None, underchat: Option[Html] = None, robots: Boolean = true)(body: Html)(implicit ctx: Context)
@(title: String, goodies: Html, chat: Option[Html] = None, underchat: Option[Html] = None, robots: Boolean = true, signedJs: Option[String] = None)(body: Html)(implicit ctx: Context)
@base.layout(
title = title,
goodies = goodies.some,
active = siteMenu.play.some,
chat = chat,
underchat = underchat,
robots = robots)(body)
robots = robots,
signedJs = signedJs)(body)

View File

@ -14,7 +14,8 @@
title = title,
goodies = views.html.game.infoBox(pov, tour),
chat = roomHtml.map(round.room(_, false)),
underchat = underchat.some) {
underchat = underchat.some,
signedJs = routes.Round.signedJs(pov.gameId).toString.some) {
<div class="lichess_game clearfix lichess_player_@color not_spectator"
data-socket-url="@routes.Round.websocketPlayer(fullId)"
data-table-url="@routes.Round.tablePlayer(fullId)"

7
bin/closure 100755
View File

@ -0,0 +1,7 @@
#!/bin/sh
. bin/lilarc
lilalog "Compiling javascript"
mkdir -p public/compiled
closure --js public/javascripts/big.js --js_output_file public/compiled/big.js

View File

@ -11,6 +11,7 @@ if [ -z $1 ]; then
elif [ $1 = "main" ]; then
REMOTE="balrog"
REMOTE_DIR="/home/lila"
bin/closure
elif [ $1 = "ai" ]; then
REMOTE="marty"
REMOTE_DIR="/home/lila3"

View File

@ -17,6 +17,7 @@ GET /$gameId<[\w\-]{8}> controllers.Round.watcher(g
GET /$gameId<[\w\-]{8}>/$color<white|black> controllers.Round.watcher(gameId: String, color: String)
GET /$fullId<[\w\-]{12}> controllers.Round.player(fullId: String)
GET /$gameId<[\w\-]{8}>/$color<white|black>/socket controllers.Round.websocketWatcher(gameId: String, color: String)
GET /$gameId<[\w\-]{8}>/signed.js controllers.Round.signedJs(gameId: String)
GET /$fullId<[\w\-]{12}>/socket controllers.Round.websocketPlayer(fullId: String)
GET /$fullId<[\w\-]{12}>/abort controllers.Round.abort(fullId: String)
GET /$fullId<[\w\-]{12}>/resign controllers.Round.resign(fullId: String)

View File

@ -1,3 +1,7 @@
// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/jquery-1.7.js
// ==/ClosureCompiler==
if (typeof console == "undefined" || typeof console.log == "undefined") console = {
log: function() {}
};
@ -512,7 +516,7 @@ $.widget("lichess.game", {
self.options.tableUrl = self.element.data('table-url');
self.options.playersUrl = self.element.data('players-url');
self.options.socketUrl = self.element.data('socket-url');
self.socketAckTimeout;
self.socketAckTimeout = null;
$("div.game_tournament .clock").each(function() {
$(this).clock({time: $(this).data("time")}).clock("start");
@ -567,6 +571,7 @@ $.widget("lichess.game", {
options: {
name: "game"
},
params: { tk: "--tkph--"},
events: {
ack: function() {
clearTimeout(self.socketAckTimeout);
@ -871,12 +876,12 @@ $.widget("lichess.game", {
moveData.promotion = "queen";
sendMoveRequest(moveData);
} else {
var $choices = $('<div class="lichess_promotion_choice">').appendTo(self.$board).html('\
<div data-piece="queen" class="lichess_piece queen ' + color + '"></div>\
<div data-piece="knight" class="lichess_piece knight ' + color + '"></div>\
<div data-piece="rook" class="lichess_piece rook ' + color + '"></div>\
<div data-piece="bishop" class="lichess_piece bishop ' + color + '"></div>').fadeIn(self.options.animation_delay).find('div.lichess_piece').click(function() {
moveData.promotion = $(this).attr('data-piece');
var $choices = $('<div class="lichess_promotion_choice">')
.appendTo(self.$board)
.html('<div data-piece="queen" class="lichess_piece queen ' + color + '"></div><div data-piece="knight" class="lichess_piece knight ' + color + '"></div><div data-piece="rook" class="lichess_piece rook ' + color + '"></div><div data-piece="bishop" class="lichess_piece bishop ' + color + '"></div>')
.fadeIn(self.options.animation_delay)
.find('div.lichess_piece')
.click(function() { moveData.promotion = $(this).attr('data-piece');
sendMoveRequest(moveData);
$choices.fadeOut(self.options.animation_delay, function() {
$choices.remove();
@ -1169,7 +1174,7 @@ $.widget("lichess.chat", {
},
append: function(msg) {
var self = this;
self.$msgs.append(urlToLink(msg))[0];
self.$msgs.append(urlToLink(msg));
$('body').trigger('lichess.content_loaded');
self.$msgs[0].scrollTop = 9999999;
}