firebase timer

a multiple client synchronized timer for Firebase

by Kato Richardson

HTML

<script src="http://static.firebase.com/v0/firebase.js"></script>
<script src="https://cdn.firebase.com/v0/firebase-simple-login.js"></script>
<script src="https://login.persona.org/include.js"></script>
<div id="timer">00:00</div>
<button id="master" disabled>make me the master</button>
<p>
    <button id="start" data-master class="hide">start</button>
    <button id="stop" data-master class="hide">stop</button>
    <button id="reset" data-master class="hide">reset</button>
</p>
<p><a href="http://jsfiddle.net/katowulf/WEUZt" target="_blank">add a client</a>

</p>

CSS

body, button {
    font-size: 36px;
    line-height: 32px;
}
#timer {
    padding: 20px;
    background-color: #eee;
    border: 1px solid #ddd;
    border-radius: 10px;
    text-align: center;
    margin: 20px;
}
.hide {
    display: none;
}

JavaScript

var FB = new Firebase('https://katowulf-timer.firebaseio-demo.com');
var myOffset = 0;
var myId = FB.push().name(); // a random id
var RESET_SECONDS = 15;

/***** CLIENT FUNCTIONS *******/
FB.child('.info/serverTimeOffset').on('value', function(snap) {
   myOffset = snap.val()||0;
});

var endsAt = 0,
    timeout;

FB.child('running').on('value', toggleRunning);
FB.child('endtime').on('value', updateEndTime);

function toggleRunning(snap) {
    var b = !! snap.val();
    if (b) {
        countDown();
        timeout = setInterval(countDown, 1000);
    } else {
        timeout && clearTimeout(timeout);
        timeout = null;
    }
}

function updateEndTime(snap) {
    endsAt = snap.val()||0;
    countDown();
}

function countDown() {
    setTime(Math.max(0, endsAt - now()));
}

function setTime(remaining) {
    var minutes = Math.floor(remaining / 60000);
    var seconds = Math.round(remaining / 1000);
    $('#timer').text(lpad(minutes) + ':' + lpad(seconds));
}

/***** MASTER FUNCTIONS *********/
var masterTimeout;
FB.child('master').on('value', masterUpdated);

function masterUpdated(snap) {
    var isMe = snap.val() === myId;
    $('[data-master]').toggleClass('hide', !isMe);
    console.log('master', isMe, snap.val()); //debug
    $('#master').prop('disabled', snap.val() !== null);
    if (isMe) {
        $('#master').text('I am master');
    }
}

$('#start').click(function () {
    FB.child('running').set(true);
});

$('#stop').click(function () {
    FB.child('running').set(false);
});

$('#reset').click(function () {
    FB.child('running').set(false);
    FB.child('endtime').set(now() + RESET_SECONDS * 1000);
});

$('#master').click(function () {
    FB.child('master').set(myId);
    FB.child('master').onDisconnect().remove();
});

/******* UTILS **********/

function now() {
    return Date.now() + myOffset;
}

function lpad(n) {
    if (n < 10) {
        return '0' + n;
    }
    else {
        return n;
    }
}