# node libraries redis = (require 'redis') crypto = (require 'crypto') logger = (require 'logging').default 'session' # expiry times in days EXPIRY = # games expire 1 week after creation game: 7 # logins expire 1 month after last action login: 30 class FFTCGSESSION constructor: -> @db = redis.createClient host: 'redis' port: 6379 @db.on 'error', (err) => logger.error err.message sessionKey: (digest) -> "session.#{digest}" gameKey: (digest) -> "game.#{digest}" start: (userid) -> new Promise (resolve) => # hash userid hmac = crypto.createHmac 'sha256', Math.random().toString() hmac.update userid.toString() digest = hmac.digest 'base64' # push (hash, userid) into DB for the configured timespan @db.setex (@sessionKey digest), EXPIRY.login * 86400, userid, => logger.info "OK '#{@sessionKey digest}' created" # return cookie data resolve value: digest properties: expires: EXPIRY.login destroy: (digest) -> new Promise (resolve, reject) => # delete hash immediately @db.del (@sessionKey digest), (err, res) => if res == 0 reject null else logger.info "OK '#{@sessionKey digest}' deleted" resolve null check: (digest) -> new Promise (resolve, reject) => # refresh expiry timer on digest @db.expire (@sessionKey digest), EXPIRY.login * 86400, (err, res) => if res == 0 reject null else @db.get (@sessionKey digest), (err, res) => logger.debug "OK '#{@sessionKey digest}' resumed" resolve res newGame: (userid) -> new Promise (resolve, reject) => # generate hash hmac = crypto.createHmac 'sha256', Math.random().toString() hmac.update userid.toString() digest = hmac.digest 'base64' # insert game key @db.hsetnx (@gameKey digest), 'owner', userid, (err, res) => if res == 0 @db.del (@gameKey digest) reject null else @db.expire (@gameKey digest), EXPIRY.game * 86400, (err, res) => if res == 0 @db.del (@gameKey digest) reject null else # add game to active set @db.sadd (@gameKey 'active'), (@gameKey digest), (err, res) => # return game ID logger.info "OK '#{@gameKey digest}' created" resolve digest getGames: -> # function to return all active gameKeys activeGameKeys = (set, cursor) => # start iteration set ?= new Set() cursor ?= '0' return new Promise (resolve, reject) => # scan "active" gameKey @db.sscan (@gameKey 'active'), cursor, 'COUNT', '100', (err, res) => # add to results set cursor = res[0] for key in res[1] set.add key if cursor == '0' # done on cursor = 0 resolve set else # recursive call (resolve one step deeper) allGames set, cursor .then (set) => resolve set activeGameKeys().then (set) => logger.info "game count: #{Array.from(set).length}" joinGame: (digest, userid) -> new Promise (resolve, reject) => # refresh expiry timer on digest @db.expire (@gameKey digest), EXPIRY.game * 86400, (err, res) => if res == 0 reject null else # insert opponent value @db.hsetnx (@gameKey digest), 'opponent', userid, (err, res) => if res == 0 reject null else # return game ID logger.info "OK '#{@gameKey digest}' joined" resolve digest updateGame: (digest, state) -> new Promise (resolve, reject) => # refresh expiry timer on digest @db.expire (@gameKey digest), EXPIRY.game * 86400, (err, res) => if res == 0 reject null else # update state value @db.hset (@gameKey digest), 'state', (JSON.stringify state), (err, res) => if res == 0 reject null else # return game ID logger.info "OK '#{@gameKey digest}' updated" resolve digest module.exports = new FFTCGSESSION