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/project
project/target project/target
public/trans public/trans
public/compiled
serve/ serve/
serve/README serve/README
target target

View File

@ -83,6 +83,9 @@ trait LilaController
protected def JsonIOk(map: IO[Map[String, Any]]) = JsonOk(map.unsafePerformIO) 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( protected def ValidOk(valid: Valid[Unit]) = valid.fold(
e BadRequest(e.shows), e BadRequest(e.shows),
_ Ok("ok") _ Ok("ok")

View File

@ -24,6 +24,7 @@ object Round extends LilaController with TheftPrevention with RoundEventPerforme
private def userRepo = env.user.userRepo private def userRepo = env.user.userRepo
private def analyser = env.analyse.analyser private def analyser = env.analyse.analyser
private def tournamentRepo = env.tournament.repo private def tournamentRepo = env.tournament.repo
private def gameJs = env.game.gameJs
def websocketWatcher(gameId: String, color: String) = WebSocket.async[JsValue] { req def websocketWatcher(gameId: String, color: String) = WebSocket.async[JsValue] { req
implicit val ctx = reqToCtx(req) implicit val ctx = reqToCtx(req)
@ -32,16 +33,22 @@ object Round extends LilaController with TheftPrevention with RoundEventPerforme
color, color,
getInt("version"), getInt("version"),
get("sri"), get("sri"),
get("tk"),
ctx).unsafePerformIO ctx).unsafePerformIO
} }
def websocketPlayer(fullId: String) = WebSocket.async[JsValue] { req def websocketPlayer(fullId: String) = WebSocket.async[JsValue] { req
implicit val ctx = reqToCtx(req) implicit val ctx = reqToCtx(req)
socket.joinPlayer( socket.joinPlayer(
fullId, fullId,
getInt("version"), getInt("version"),
get("sri"), get("sri"),
ctx).unsafePerformIO 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 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 GamePaginatorMaxPerPage = getInt("game.paginator.max_per_page")
val GameCollectionGame = getString("game.collection.game") val GameCollectionGame = getString("game.collection.game")
val GameCollectionPgn = getString("game.collection.pgn") val GameCollectionPgn = getString("game.collection.pgn")
val GameJsPath = getString("game.js_path")
val SearchESHost = getString("search.elasticsearch.host") val SearchESHost = getString("search.elasticsearch.host")
val SearchESPort = getInt("search.elasticsearch.port") val SearchESPort = getInt("search.elasticsearch.port")

View File

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

View File

@ -33,4 +33,6 @@ final class GameEnv(
lazy val listMenu = ListMenu(cached) _ lazy val listMenu = ListMenu(cached) _
lazy val rewind = new Rewind 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 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 { def save(game: DbGame): IO[Unit] = io {
update(idSelector(game), _grater asDBObject game.encode) update(idSelector(game), _grater asDBObject game.encode)
} }

View File

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

View File

@ -112,15 +112,17 @@ final class Socket(
colorName: String, colorName: String,
version: Option[Int], version: Option[Int],
uid: Option[String], uid: Option[String],
token: Option[String],
ctx: Context): IO[SocketPromise] = 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( def joinPlayer(
fullId: String, fullId: String,
version: Option[Int], version: Option[Int],
uid: Option[String], uid: Option[String],
token: Option[String],
ctx: Context): IO[SocketPromise] = 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 { private def parseMove(event: JsValue) = for {
d event obj "d" d event obj "d"
@ -136,9 +138,10 @@ final class Socket(
owner: Boolean, owner: Boolean,
versionOption: Option[Int], versionOption: Option[Int],
uidOption: Option[String], uidOption: Option[String],
tokenOption: Option[String],
ctx: Context): SocketPromise = ctx: Context): SocketPromise =
((povOption |@| uidOption |@| versionOption) apply { ((povOption |@| uidOption |@| tokenOption |@| versionOption) apply {
(pov: Pov, uid: String, version: Int) (pov: Pov, uid: String, token: String, version: Int)
(for { (for {
hub hubMaster ? GetHub(pov.gameId) mapTo manifest[ActorRef] hub hubMaster ? GetHub(pov.gameId) mapTo manifest[ActorRef]
socket hub ? Join( socket hub ? Join(
@ -146,10 +149,12 @@ final class Socket(
user = ctx.me, user = ctx.me,
version = version, version = version,
color = pov.color, color = pov.color,
owner = owner owner = owner && token == pov.game.token
) map { ) map {
case Connected(enumerator, member) { 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]( (Iteratee.foreach[JsValue](
controller(hub, uid, member, PovRef(pov.gameId, member.color)) controller(hub, uid, member, PovRef(pov.gameId, member.color))
) mapDone { _ ) mapDone { _

View File

@ -7,7 +7,7 @@ import play.api.templates.Html
trait AssetHelper { trait AssetHelper {
val assetVersion = 4 val assetVersion = 6
def cssTag(name: String) = css("stylesheets/" + name) def cssTag(name: String) = css("stylesheets/" + name)
@ -20,10 +20,13 @@ trait AssetHelper {
def jsTag(name: String) = js("javascripts/" + name) def jsTag(name: String) = js("javascripts/" + name)
def jsTagC(name: String) = js("compiled/" + name)
def jsVendorTag(name: String) = js("vendor/" + name) def jsVendorTag(name: String) = js("vendor/" + name)
def js(path: String) = Html { private def js(path: String) = jsAt(routes.Assets.at(path).toString)
"""<script src="%s?v=%d"></script>"""
.format(routes.Assets.at(path), assetVersion) 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> <!doctype html>
<html lang="@lang.language"> <html lang="@lang.language">
@ -105,7 +105,7 @@
</div> </div>
</div> </div>
@jsTag("deps.min.js") @jsTag("deps.min.js")
@jsTag("big.js") @signedJs.fold(jsAt, jsTagC("big.js"))
@moreJs @moreJs
@if(lang.language != "en") { @if(lang.language != "en") {
<script src="@routes.Assets.at("trans/" + lang.language + ".js")?v=@assetVersion"></script> <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( @base.layout(
title = title, title = title,
goodies = goodies.some, goodies = goodies.some,
active = siteMenu.play.some, active = siteMenu.play.some,
chat = chat, chat = chat,
underchat = underchat, underchat = underchat,
robots = robots)(body) robots = robots,
signedJs = signedJs)(body)

View File

@ -14,7 +14,8 @@
title = title, title = title,
goodies = views.html.game.infoBox(pov, tour), goodies = views.html.game.infoBox(pov, tour),
chat = roomHtml.map(round.room(_, false)), 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" <div class="lichess_game clearfix lichess_player_@color not_spectator"
data-socket-url="@routes.Round.websocketPlayer(fullId)" data-socket-url="@routes.Round.websocketPlayer(fullId)"
data-table-url="@routes.Round.tablePlayer(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 elif [ $1 = "main" ]; then
REMOTE="balrog" REMOTE="balrog"
REMOTE_DIR="/home/lila" REMOTE_DIR="/home/lila"
bin/closure
elif [ $1 = "ai" ]; then elif [ $1 = "ai" ]; then
REMOTE="marty" REMOTE="marty"
REMOTE_DIR="/home/lila3" 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 /$gameId<[\w\-]{8}>/$color<white|black> controllers.Round.watcher(gameId: String, color: String)
GET /$fullId<[\w\-]{12}> controllers.Round.player(fullId: 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}>/$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}>/socket controllers.Round.websocketPlayer(fullId: String)
GET /$fullId<[\w\-]{12}>/abort controllers.Round.abort(fullId: String) GET /$fullId<[\w\-]{12}>/abort controllers.Round.abort(fullId: String)
GET /$fullId<[\w\-]{12}>/resign controllers.Round.resign(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 = { if (typeof console == "undefined" || typeof console.log == "undefined") console = {
log: function() {} log: function() {}
}; };
@ -512,7 +516,7 @@ $.widget("lichess.game", {
self.options.tableUrl = self.element.data('table-url'); self.options.tableUrl = self.element.data('table-url');
self.options.playersUrl = self.element.data('players-url'); self.options.playersUrl = self.element.data('players-url');
self.options.socketUrl = self.element.data('socket-url'); self.options.socketUrl = self.element.data('socket-url');
self.socketAckTimeout; self.socketAckTimeout = null;
$("div.game_tournament .clock").each(function() { $("div.game_tournament .clock").each(function() {
$(this).clock({time: $(this).data("time")}).clock("start"); $(this).clock({time: $(this).data("time")}).clock("start");
@ -567,6 +571,7 @@ $.widget("lichess.game", {
options: { options: {
name: "game" name: "game"
}, },
params: { tk: "--tkph--"},
events: { events: {
ack: function() { ack: function() {
clearTimeout(self.socketAckTimeout); clearTimeout(self.socketAckTimeout);
@ -871,12 +876,12 @@ $.widget("lichess.game", {
moveData.promotion = "queen"; moveData.promotion = "queen";
sendMoveRequest(moveData); sendMoveRequest(moveData);
} else { } else {
var $choices = $('<div class="lichess_promotion_choice">').appendTo(self.$board).html('\ var $choices = $('<div class="lichess_promotion_choice">')
<div data-piece="queen" class="lichess_piece queen ' + color + '"></div>\ .appendTo(self.$board)
<div data-piece="knight" class="lichess_piece knight ' + color + '"></div>\ .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>')
<div data-piece="rook" class="lichess_piece rook ' + color + '"></div>\ .fadeIn(self.options.animation_delay)
<div data-piece="bishop" class="lichess_piece bishop ' + color + '"></div>').fadeIn(self.options.animation_delay).find('div.lichess_piece').click(function() { .find('div.lichess_piece')
moveData.promotion = $(this).attr('data-piece'); .click(function() { moveData.promotion = $(this).attr('data-piece');
sendMoveRequest(moveData); sendMoveRequest(moveData);
$choices.fadeOut(self.options.animation_delay, function() { $choices.fadeOut(self.options.animation_delay, function() {
$choices.remove(); $choices.remove();
@ -1169,7 +1174,7 @@ $.widget("lichess.chat", {
}, },
append: function(msg) { append: function(msg) {
var self = this; var self = this;
self.$msgs.append(urlToLink(msg))[0]; self.$msgs.append(urlToLink(msg));
$('body').trigger('lichess.content_loaded'); $('body').trigger('lichess.content_loaded');
self.$msgs[0].scrollTop = 9999999; self.$msgs[0].scrollTop = 9999999;
} }