/client/src/ConversationStore.js
// Register the LocalStorage source
new enyo.LocalStorageSource({name: "localstorage"});

enyo.kind({
    name: "MessageModel",
    kind: "enyo.Model",
    source: "localstorage",
    options: {
        commit: true
    },
    attributes: {
        message: "",
        receivedTimestamp: 0,
        outbound: false
    }
});

enyo.kind({
    name: "ConversationCollection",
    kind: "enyo.Collection",
    options: {
        commit: true
    },
    model: "MessageModel",
    source: "localstorage",
    // Because LocalStorageSource doesn't support dynamic getUrl()
    constructor: enyo.inherit(function(sup) {
        return function() {
            sup.apply(this, arguments);
            this.set('url', 'conversation:' + this.jid);
        }
    })
});

enyo.kind({
    name: "ConversationStore",
    kind: "Component",
    conversations: {},
    retentionWindow: 86400,
    create: function() {
        this.inherited(arguments);
    },
    loadConversations: function() {
        if (!localStorage.conversationJids) {
            return;
        }
        var jids = localStorage.conversationJids.split(' ');
        var len = jids.length;
        for (var i = 0; i < len; i++) {
            var jid = jids[i];
            this.conversations[jid] = new ConversationCollection({jid: jid});
            this.conversations[jid].fetch();
        }
    },
    saveConversationList: function() {
        localStorage.conversationJids = Object.keys(this.conversations).join(' ');
    },
    getConversationCollection: function(jid) {
        if (!(jid in this.conversations)) {
            this.conversations[jid] = new ConversationCollection({jid: jid});
        }
        return this.conversations[jid];
    },
    getConversationSummary: function() {
        var res = []
        for (jid in this.conversations) {
            var len = this.conversations[jid].length;
            var lastMessage = len > 0 ? this.conversations[jid].at(len - 1) : '';
            res.push({
                jid: jid,
                outbound: lastMessage.outbound,
                message: lastMessage.message,
                receivedTimestamp: lastMessage.receivedTimestamp
            });
        }

        return res.sort(function(a, b) {
            if (a.receivedTimestamp > b.receivedTimestamp)
                return -1;
            if (a.receivedTimestamp < b.receivedTimestamp)
                return 1;
            return 0;
        });
    },
    appendConversation: function(jid, data) {
        if (!(jid in this.conversations)) {
            this.conversations[jid] = new ConversationCollection({jid: jid});
        }
        this.conversations[jid].add(data);
        this.cullConversation(jid);
        this.saveConversationList();
    },
    clearConversation: function(jid) {
        if (!(jid in this.conversations)) {
            return;
        }
        this.conversations[jid].forEach(function(v) {
            v.destroy();
        });
    },
    deleteConversation: function(jid) {
        if (!(jid in this.conversations)) {
            return;
        }
        this.clearConversation(jid);
        this.conversations[jid].destroy();
        delete this.conversations[jid];
        this.saveConversationList();
    },
    cullConversation: function(jid) {
        // TODO: implement message culling by retentionWindow
    }
});