Client-side trade using Firebase

This demonstrates trading items in Firebase using only client-side code and audit records.

by Firebase

HTML

<script src="http://cdn.firebase.com/js/client/1.0.11/firebase.js"></script>
<script src="https://cdn.firebase.com/js/simple-login/1.3.2/firebase-simple-login.js"></script>
<script src="https://rawgit.com/firebase/firebase-util/master/firebase-util.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.6/angular.min.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/0.7.1/angularfire.min.js"></script>
<p> <a href="#" ng-click="login('user1')">join as Chuck</a> | <a href="#" ng-click="login('user2')">join as Bruce</a> |
<a href="https://gist.github.com/katowulf/d858d9809670dce4ab31" target="_blank">check out the security rules</a> |
<a href="https://kato-game.firebaseio.com/.json?print=pretty" target="_blank">check out the data</a> 
    (Open <a href="http://jsfiddle.net/katowulf/J4kay/embedded/result/" target="_blank">two windows</a> for great justice!)</p>

<p>How this works: Both users suggest an item to trade. One both have accepted, the items trade places and no changes can be made.</p>

<div class="require-login">
    <h1>I am logged in as: {{profile.name}} ({{uid}})</h1>
    <div class="col">
         <h2>{{users[selectedUser].name}}'s Offer</h2>

        <div ng-show="!offeredItem">Nothing yet; offer a trade!</div>
        <div ng-show="!!offeredItem">{{offeredItem.name}} (${{offeredItem.value}})
            <button ng-click="accept()" ng-class="{active: isAccepted===true}">accept</button>
            <button ng-click="decline()" ng-class="{active: isAccepted===false}">decline</button>
        </div>
    </div>
    <div class="col require-login">
         <h2>My Items</h2>

        <ul>
            <li ng-repeat="(key,item) in myItems">{{item.name}} (${{item.value}})
                <button ng-click="setOffer(key)" ng-class="{active: myOffer === key}">trade this</button> <span ng-show="myOffer === key" class="{{responseMessage()}}">{{responseMessage()}}</span>

            </li>
        </ul>
    </div>
</div>

CSS

.col {
    width: 45%;
    float: left;
}
body.not-initialized .require-login {
    display: none;
}
.declined, .accepted, .pending {
    background-color: yellow;
}
.error, .declined {
    color: red;
}
.accepted: {
    color: green;
}
.pending {
    color: blue;
}
button.active {
    background-color: green;
}

JavaScript

var fb = new Firebase('https://kato-game.firebaseio.com/');

var app = angular.module('app', ['firebase']);
app.controller('ctrl', function ($scope, myItems, $firebase, tradeQueue, loginManager, $timeout) {

    // we just load all the data because it's easier; yay demo! 
    $scope.users = $firebase(fb.child('users'));
    $scope.items = $firebase(fb.child('items'));

    // we only have one offer for simplicity; yay mocks!
    $scope.offer = $firebase(fb.child('offers/singleton'));
    $scope.offer.$on('change', checkOfferStatus);

    loginManager.setCallback(loadProfile);

    var myRole, otherRole;

    // A key point in the trade! When I change or set my offered item
    // I must revoke the accepted status for the other user; this is
    // forced by security rules so that each time I change what I offer
    // his acceptance is automagically cancelled. Also note that I set it
    // to null (pendng) rather than false (declined)
    $scope.setOffer = function (key) {
        var data = {};
        data[myRole + '_offer'] = key;
        data['accepted_by_' + otherRole] = null;
        console.log('update', data);
        $scope.offer.$update(data);
    };

    $scope.accept = function () {
        // we can't just set value on offer and call $scope.offer.$save() here
        // because we're not actually allowed to write to the whole offer, just
        // the accepted_* fields and *_item fields
        $scope.offer.$getRef().child('accepted_by_' + myRole).set(true);
    };

    $scope.decline = function () {
        $scope.offer.$getRef().child('accepted_by_' + myRole).set(false);
    }

    function checkOfferStatus() {
        var offer = $scope.offer;
        updateOfferedItem();
        if( !$scope.uid ) { return; }
        
        // another key point in the trade! When both parties have accepted the trade agreement
        // then the items cannot be changed and the booleans cannot be changed. Then I take my
        // item. Once both parties have...