<commit_before>define([
    'jquery',
    '/bower_components/chainpad-listmap/chainpad-listmap.js',
    '/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
    '/common/userObject.js',
    '/common/migrate-user-object.js',
], function ($, Listmap, Crypto, FO, Migrate) {
    /*
        This module uses localStorage, which is synchronous, but exposes an
        asyncronous API. This is so that we can substitute other storage
        methods.

        To override these methods, create another file at:
        /customize/storage.js
    */

    var Store = {};
    var store;

    var initStore = function (filesOp, storeObj, exp) {
        var ret = {};

        var safeSet = function (key, val) {
            storeObj[key] = val;
        };

        // Store uses nodebacks...
        ret.set = function (key, val, cb) {
            safeSet(key, val);
            cb();
        };

        // implement in alternative store
        ret.setBatch = function (map, cb) {
            Object.keys(map).forEach(function (key) {
                safeSet(key, map[key]);
            });
            cb(void 0, map);
        };

        ret.setDrive = function (key, val, cb) {
            storeObj.drive[key] = val;
            cb();
        };

        var safeGet = function (key) {
            return storeObj[key];
        };

        ret.get = function (key, cb) {
            cb(void 0, safeGet(key));
        };

        // implement in alternative store
        ret.getBatch = function (keys, cb) {
            var res = {};
            keys.forEach(function (key) {
                res[key] = safeGet(key);
            });
            cb(void 0, res);
        };

        var getAttributeObject = function (attr) {
            if (typeof attr === "string") {
                console.error('DEPRECATED: use setAttribute with an array, not a string');
                return {
                    obj: storeObj.settings,
                    key: attr
                };
            }
            if (!Array.isArray(attr)) { throw new Error("Attribute must be string or array"); }
            if (attr.length === 0) { throw new Error("Attribute can't be empty"); }
            var obj = storeObj.settings;
            attr.forEach(function (el, i) {
                if (i === attr.length-1) { return; }
                if (!obj[el]) {
                    obj[el] = {};
                }
                else if (typeof obj[el] !== "object") { throw new Error("Wrong attribute"); }
                obj = obj[el];
            });
            return {
                obj: obj,
                key: attr[attr.length-1]
            };
        };
        ret.setAttribute = function (attr, value, cb) {
            try {
                var object = getAttributeObject(attr);
                object.obj[object.key] = value;
            } catch (e) { return void cb(e); }
            cb();
        };
        ret.getAttribute = function (attr, cb) {
            var object;
            try {
                object = getAttributeObject(attr);
            } catch (e) { return void cb(e); }
            cb(null, object.obj[object.key]);
        };
        ret.setPadAttribute = filesOp.setPadAttribute;
        ret.getPadAttribute = filesOp.getPadAttribute;
        ret.getIdFromHref = filesOp.getIdFromHref;

        ret.getDrive = function (key, cb) {
            cb(void 0, storeObj.drive[key]);
        };

        var safeRemove = function (key) {
            delete storeObj[key];
        };

        ret.remove = function (key, cb) {
            safeRemove(key);
            cb();
        };

        // implement in alternative store
        ret.removeBatch = function (keys, cb) {
            keys.forEach(function (key) {
                safeRemove(key);
            });
            cb();
        };

        ret.keys = function (cb) {
            cb(void 0, Object.keys(storeObj));
        };

        ret.removeData = filesOp.removeData;
        ret.pushData = filesOp.pushData;
        ret.addPad = filesOp.add;

        ret.forgetPad = function (href, cb) {
            filesOp.forget(href);
            cb();
        };

        ret.listTemplates = function () {
            var templateFiles = filesOp.getFiles(['template']);
            var res = [];
            templateFiles.forEach(function (f) {
                var data = filesOp.getFileData(f);
                res.push(JSON.parse(JSON.stringify(data)));
            });
            return res;
        };

        ret.getProxy = function () {
            return exp;
        };

        ret.getLoginName = function () {
            return storeObj.login_name;
        };

        ret.repairDrive = function () {
            filesOp.fixFiles();
        };

        ret.getEmptyObject = function () {
            return filesOp.getStructure();
        };

        ret.replace = filesOp.replace;

        ret.restoreHref = filesOp.restoreHref;

        ret.changeHandlers = [];

        ret.change = function () {};

        ret.getProfile = function () {
            return storeObj.profile;
        };

        return ret;
    };

    var tryParsing = function (x) {
        try { return JSON.parse(x); }
        catch (e) {
            console.error(e);
            return null;
        }
    };

    var onReady = function (f, proxy, Cryptpad, exp) {
        var fo = exp.fo = FO.init(proxy.drive, {
            Cryptpad: Cryptpad,
            rt: exp.realtime
        });
        var todo = function () {
            fo.fixFiles();

            Migrate(proxy, Cryptpad);

            store = initStore(fo, proxy, exp);
            if (typeof(f) === 'function') {
                f(void 0, store);
            }
            //storeObj = proxy;

            var requestLogin = function () {
                // log out so that you don't go into an endless loop...
                Cryptpad.logout();

                // redirect them to log in, and come back when they're done.
                sessionStorage.redirectTo = window.location.href;
                window.location.href = '/login/';
            };

            var tokenKey = 'loginToken';
            if (Cryptpad.isLoggedIn()) {
    /*  This isn't truly secure, since anyone who can read the user's object can
        set their local loginToken to match that in the object. However, it exposes
        a UI that will work most of the time. */

                // every user object should have a persistent, random number
                if (typeof(proxy.loginToken) !== 'number') {
                    proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
                }

                // copy User_hash into sessionStorage because cross-domain iframes
                // on safari replaces localStorage with sessionStorage or something
                if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); }

                var localToken = tryParsing(localStorage.getItem(tokenKey));
                if (localToken === null) {
                    // if that number hasn't been set to localStorage, do so.
                    localStorage.setItem(tokenKey, proxy.loginToken);
                } else if (localToken !== proxy[tokenKey]) {
                    // if it has been, and the local number doesn't match that in
                    // the user object, request that they reauthenticate.
                    return void requestLogin();
                }
            }

            if (!proxy.settings || !proxy.settings.general ||
                    typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') {
                proxy.settings = proxy.settings || {};
                proxy.settings.general = proxy.settings.general || {};
                proxy.settings.general.allowUserFeedback = true;
            }

            if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) {
                // even anonymous users should have a persistent, unique-ish id
                console.log('generating a persistent identifier');
                proxy.uid = Cryptpad.createChannelId();
            }

            // if the user is logged in, but does not have signing keys...
            if (Cryptpad.isLoggedIn() && (!Cryptpad.hasSigningKeys(proxy) ||
                !Cryptpad.hasCurveKeys(proxy))) {
                return void requestLogin();
            }

            proxy.on('change', [Cryptpad.displayNameKey], function (o, n) {
                if (typeof(n) !== "string") { return; }
                Cryptpad.changeDisplayName(n);
            });
            proxy.on('change', ['profile'], function () {
                // Trigger userlist update when the avatar has changed
                Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]);
            });
            proxy.on('change', ['friends'], function () {
                // Trigger userlist update when the avatar has changed
                Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]);
            });
            proxy.on('change', [tokenKey], function () {
                var localToken = tryParsing(localStorage.getItem(tokenKey));
                if (localToken !== proxy[tokenKey]) {
                    return void requestLogin();
                }
            });
        };
        fo.migrate(todo);
    };

    var initialized = false;

    var init = function (f, Cryptpad) {
        if (!Cryptpad || initialized) { return; }
        initialized = true;

        var hash = Cryptpad.getUserHash() || localStorage.FS_hash || Cryptpad.createRandomHash();
        if (!hash) {
            throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
        }
        var secret = Cryptpad.getSecrets('drive', hash);
        var listmapConfig = {
            data: {},
            websocketURL: Cryptpad.getWebsocketURL(),
            channel: secret.channel,
            readOnly: false,
            validateKey: secret.keys.validateKey || undefined,
            crypto: Crypto.createEncryptor(secret.keys),
            userName: 'fs',
            logLevel: 1,
        };

        var exp = {};

        window.addEventListener('storage', function (e) {
            if (e.key !== Cryptpad.userHashKey) { return; }
            var o = e.oldValue;
            var n = e.newValue;
            if (!o && n) {
                window.location.reload();
            } else if (o && !n) {
                $(window).on('keyup', function (e) {
                    if (e.keyCode === 27) {
                        Cryptpad.removeLoadingScreen();
                    }
                });
                Cryptpad.logout();
                Cryptpad.addLoadingScreen({hideTips: true});
                Cryptpad.errorLoadingScreen(Cryptpad.Messages.onLogout, true);
                if (exp.info) {
                    exp.info.network.disconnect();
                }
            }
        });

        var rt = window.rt = Listmap.create(listmapConfig);

        exp.realtime = rt.realtime;
        exp.proxy = rt.proxy;
        rt.proxy.on('create', function (info) {
            exp.info = info;
            if (!Cryptpad.getUserHash()) {
                localStorage.FS_hash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
            }
        }).on('ready', function () {
            if (store) { return; } // the store is already ready, it is a reconnection
            if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
            var drive = rt.proxy.drive;
            // Creating a new anon drive: import anon pads from localStorage
            if ((!drive[Cryptpad.oldStorageKey] || !Cryptpad.isArray(drive[Cryptpad.oldStorageKey]))
                && !drive['filesData']) {
                drive[Cryptpad.oldStorageKey] = [];
                onReady(f, rt.proxy, Cryptpad, exp);
                return;
            }
            // Drive already exist: return the existing drive, don't load data from legacy store
            onReady(f, rt.proxy, Cryptpad, exp);
        })
        .on('disconnect', function (info) {
            // We only manage errors during the loading screen here. Other websocket errors are handled by the apps
            if (info.error) {
                if (typeof Cryptpad.storeError === "function") {
                    Cryptpad.storeError();
                }
                return;
            }
        })
        .on('change', ['drive', 'migrate'], function () {
            var path = arguments[2];
            var value = arguments[1];
            if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
                rt.network.disconnect();
                rt.realtime.abort();
                Cryptpad.alert(Cryptpad.Messages.fs_migration, null, true);
            }
        });
    };

    Store.ready = function (f, Cryptpad) {
        if (store) { // Store.ready probably called twice, store already ready
            if (typeof(f) === 'function') {
                f(void 0, store);
            }
        } else {
            init(f, Cryptpad);
        }
    };

    return Store;
});
<commit_msg>Do content checks on the drive so if it gets corrupted, the patch will be rejected<commit_after>define([
    'jquery',
    '/bower_components/chainpad-listmap/chainpad-listmap.js',
    '/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
    '/common/userObject.js',
    '/common/migrate-user-object.js',
], function ($, Listmap, Crypto, FO, Migrate) {
    /*
        This module uses localStorage, which is synchronous, but exposes an
        asyncronous API. This is so that we can substitute other storage
        methods.

        To override these methods, create another file at:
        /customize/storage.js
    */

    var Store = {};
    var store;

    var initStore = function (filesOp, storeObj, exp) {
        var ret = {};

        var safeSet = function (key, val) {
            storeObj[key] = val;
        };

        // Store uses nodebacks...
        ret.set = function (key, val, cb) {
            safeSet(key, val);
            cb();
        };

        // implement in alternative store
        ret.setBatch = function (map, cb) {
            Object.keys(map).forEach(function (key) {
                safeSet(key, map[key]);
            });
            cb(void 0, map);
        };

        ret.setDrive = function (key, val, cb) {
            storeObj.drive[key] = val;
            cb();
        };

        var safeGet = function (key) {
            return storeObj[key];
        };

        ret.get = function (key, cb) {
            cb(void 0, safeGet(key));
        };

        // implement in alternative store
        ret.getBatch = function (keys, cb) {
            var res = {};
            keys.forEach(function (key) {
                res[key] = safeGet(key);
            });
            cb(void 0, res);
        };

        var getAttributeObject = function (attr) {
            if (typeof attr === "string") {
                console.error('DEPRECATED: use setAttribute with an array, not a string');
                return {
                    obj: storeObj.settings,
                    key: attr
                };
            }
            if (!Array.isArray(attr)) { throw new Error("Attribute must be string or array"); }
            if (attr.length === 0) { throw new Error("Attribute can't be empty"); }
            var obj = storeObj.settings;
            attr.forEach(function (el, i) {
                if (i === attr.length-1) { return; }
                if (!obj[el]) {
                    obj[el] = {};
                }
                else if (typeof obj[el] !== "object") { throw new Error("Wrong attribute"); }
                obj = obj[el];
            });
            return {
                obj: obj,
                key: attr[attr.length-1]
            };
        };
        ret.setAttribute = function (attr, value, cb) {
            try {
                var object = getAttributeObject(attr);
                object.obj[object.key] = value;
            } catch (e) { return void cb(e); }
            cb();
        };
        ret.getAttribute = function (attr, cb) {
            var object;
            try {
                object = getAttributeObject(attr);
            } catch (e) { return void cb(e); }
            cb(null, object.obj[object.key]);
        };
        ret.setPadAttribute = filesOp.setPadAttribute;
        ret.getPadAttribute = filesOp.getPadAttribute;
        ret.getIdFromHref = filesOp.getIdFromHref;

        ret.getDrive = function (key, cb) {
            cb(void 0, storeObj.drive[key]);
        };

        var safeRemove = function (key) {
            delete storeObj[key];
        };

        ret.remove = function (key, cb) {
            safeRemove(key);
            cb();
        };

        // implement in alternative store
        ret.removeBatch = function (keys, cb) {
            keys.forEach(function (key) {
                safeRemove(key);
            });
            cb();
        };

        ret.keys = function (cb) {
            cb(void 0, Object.keys(storeObj));
        };

        ret.removeData = filesOp.removeData;
        ret.pushData = filesOp.pushData;
        ret.addPad = filesOp.add;

        ret.forgetPad = function (href, cb) {
            filesOp.forget(href);
            cb();
        };

        ret.listTemplates = function () {
            var templateFiles = filesOp.getFiles(['template']);
            var res = [];
            templateFiles.forEach(function (f) {
                var data = filesOp.getFileData(f);
                res.push(JSON.parse(JSON.stringify(data)));
            });
            return res;
        };

        ret.getProxy = function () {
            return exp;
        };

        ret.getLoginName = function () {
            return storeObj.login_name;
        };

        ret.repairDrive = function () {
            filesOp.fixFiles();
        };

        ret.getEmptyObject = function () {
            return filesOp.getStructure();
        };

        ret.replace = filesOp.replace;

        ret.restoreHref = filesOp.restoreHref;

        ret.changeHandlers = [];

        ret.change = function () {};

        ret.getProfile = function () {
            return storeObj.profile;
        };

        return ret;
    };

    var tryParsing = function (x) {
        try { return JSON.parse(x); }
        catch (e) {
            console.error(e);
            return null;
        }
    };

    var onReady = function (f, proxy, Cryptpad, exp) {
        var fo = exp.fo = FO.init(proxy.drive, {
            Cryptpad: Cryptpad,
            rt: exp.realtime
        });
        var todo = function () {
            fo.fixFiles();

            Migrate(proxy, Cryptpad);

            store = initStore(fo, proxy, exp);
            if (typeof(f) === 'function') {
                f(void 0, store);
            }
            //storeObj = proxy;

            var requestLogin = function () {
                // log out so that you don't go into an endless loop...
                Cryptpad.logout();

                // redirect them to log in, and come back when they're done.
                sessionStorage.redirectTo = window.location.href;
                window.location.href = '/login/';
            };

            var tokenKey = 'loginToken';
            if (Cryptpad.isLoggedIn()) {
    /*  This isn't truly secure, since anyone who can read the user's object can
        set their local loginToken to match that in the object. However, it exposes
        a UI that will work most of the time. */

                // every user object should have a persistent, random number
                if (typeof(proxy.loginToken) !== 'number') {
                    proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
                }

                // copy User_hash into sessionStorage because cross-domain iframes
                // on safari replaces localStorage with sessionStorage or something
                if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); }

                var localToken = tryParsing(localStorage.getItem(tokenKey));
                if (localToken === null) {
                    // if that number hasn't been set to localStorage, do so.
                    localStorage.setItem(tokenKey, proxy.loginToken);
                } else if (localToken !== proxy[tokenKey]) {
                    // if it has been, and the local number doesn't match that in
                    // the user object, request that they reauthenticate.
                    return void requestLogin();
                }
            }

            if (!proxy.settings || !proxy.settings.general ||
                    typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') {
                proxy.settings = proxy.settings || {};
                proxy.settings.general = proxy.settings.general || {};
                proxy.settings.general.allowUserFeedback = true;
            }

            if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) {
                // even anonymous users should have a persistent, unique-ish id
                console.log('generating a persistent identifier');
                proxy.uid = Cryptpad.createChannelId();
            }

            // if the user is logged in, but does not have signing keys...
            if (Cryptpad.isLoggedIn() && (!Cryptpad.hasSigningKeys(proxy) ||
                !Cryptpad.hasCurveKeys(proxy))) {
                return void requestLogin();
            }

            proxy.on('change', [Cryptpad.displayNameKey], function (o, n) {
                if (typeof(n) !== "string") { return; }
                Cryptpad.changeDisplayName(n);
            });
            proxy.on('change', ['profile'], function () {
                // Trigger userlist update when the avatar has changed
                Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]);
            });
            proxy.on('change', ['friends'], function () {
                // Trigger userlist update when the avatar has changed
                Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]);
            });
            proxy.on('change', [tokenKey], function () {
                var localToken = tryParsing(localStorage.getItem(tokenKey));
                if (localToken !== proxy[tokenKey]) {
                    return void requestLogin();
                }
            });
        };
        fo.migrate(todo);
    };

    var initialized = false;

    var init = function (f, Cryptpad) {
        if (!Cryptpad || initialized) { return; }
        initialized = true;

        var hash = Cryptpad.getUserHash() || localStorage.FS_hash || Cryptpad.createRandomHash();
        if (!hash) {
            throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
        }
        var secret = Cryptpad.getSecrets('drive', hash);
        var listmapConfig = {
            data: {},
            websocketURL: Cryptpad.getWebsocketURL(),
            channel: secret.channel,
            readOnly: false,
            validateKey: secret.keys.validateKey || undefined,
            crypto: Crypto.createEncryptor(secret.keys),
            userName: 'fs',
            logLevel: 1,
            validateContent: function (content) {
                try {
                    JSON.parse(content);
                    return true;
                } catch (e) {
                    console.error("Failed to parse, rejecting patch");
                    return false;
                }
            },
        };

        var exp = {};

        window.addEventListener('storage', function (e) {
            if (e.key !== Cryptpad.userHashKey) { return; }
            var o = e.oldValue;
            var n = e.newValue;
            if (!o && n) {
                window.location.reload();
            } else if (o && !n) {
                $(window).on('keyup', function (e) {
                    if (e.keyCode === 27) {
                        Cryptpad.removeLoadingScreen();
                    }
                });
                Cryptpad.logout();
                Cryptpad.addLoadingScreen({hideTips: true});
                Cryptpad.errorLoadingScreen(Cryptpad.Messages.onLogout, true);
                if (exp.info) {
                    exp.info.network.disconnect();
                }
            }
        });

        var rt = window.rt = Listmap.create(listmapConfig);

        exp.realtime = rt.realtime;
        exp.proxy = rt.proxy;
        rt.proxy.on('create', function (info) {
            exp.info = info;
            if (!Cryptpad.getUserHash()) {
                localStorage.FS_hash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
            }
        }).on('ready', function () {
            if (store) { return; } // the store is already ready, it is a reconnection
            if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
            var drive = rt.proxy.drive;
            // Creating a new anon drive: import anon pads from localStorage
            if ((!drive[Cryptpad.oldStorageKey] || !Cryptpad.isArray(drive[Cryptpad.oldStorageKey]))
                && !drive['filesData']) {
                drive[Cryptpad.oldStorageKey] = [];
                onReady(f, rt.proxy, Cryptpad, exp);
                return;
            }
            // Drive already exist: return the existing drive, don't load data from legacy store
            onReady(f, rt.proxy, Cryptpad, exp);
        })
        .on('disconnect', function (info) {
            // We only manage errors during the loading screen here. Other websocket errors are handled by the apps
            if (info.error) {
                if (typeof Cryptpad.storeError === "function") {
                    Cryptpad.storeError();
                }
                return;
            }
        })
        .on('change', ['drive', 'migrate'], function () {
            var path = arguments[2];
            var value = arguments[1];
            if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
                rt.network.disconnect();
                rt.realtime.abort();
                Cryptpad.alert(Cryptpad.Messages.fs_migration, null, true);
            }
        });
    };

    Store.ready = function (f, Cryptpad) {
        if (store) { // Store.ready probably called twice, store already ready
            if (typeof(f) === 'function') {
                f(void 0, store);
            }
        } else {
            init(f, Cryptpad);
        }
    };

    return Store;
});
<|endoftext|>


<commit_before>/* Passphrases | https://github.com/micahflee/passphrases
   Copyright (C) 2015 Micah Lee <micah@micahflee.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

$(function(){
  // initialize memorize
  $('.memorize-input').show();
  $('.memorize-go').hide();

  // start memorizing
  function startMemorizing(passphrase) {
    // switch from input mode to go mode
    $('.memorize-input').hide();
    $('.memorize-go').show();

    $('.memorize-active').show();
    $('.memorize-waiting').hide();
    $('.memorize-ready').hide();

    $('.passphrase-test').attr('type', 'text');

    var delay = 0;
    var tries = 0;
    $('.hint-show').text(passphrase);

    var typedWithoutLooking = false;

    // countdown values are in seconds
    var countdownValues = [60, 120, 300, 600, 1800, 3600];
    var currentCountdown = 0;

    function updateStats() {
      if(tries == 0) {
        $('.memorize-note').html('Type the passphrase');
      } else if(tries == 1) {
        $('.memorize-note').html('Try to type the passphrase before the hint appears');
      } else if(typedWithoutLooking && tries < 10) {
        $('.memorize-note').html('Good job! Try making it to 10 tries');
      } else if(typedWithoutLooking && tries < 15) {
        $('.memorize-note').html('Try making it to 15 tries without seeing what you type');
      } else if(typedWithoutLooking) {
        $('.memorize-note').html('Keep practicing');
      }
      $('.memorize-tries').html(tries);
    }
    updateStats();

    function nextTry() {
      // reset the hint
      $('.hint-progress').stop();
      $('.hint-progress').css('width', '0%');
      $('.hint-progress-wrapper').show();
      $('.hint-show').hide();

      // show active, hide waiting
      $('.memorize-active').show();
      $('.memorize-waiting').hide();
      $('.memorize-ready').hide();

      // reset the passphrase
      $('.passphrase-test').val('').focus();

      // animate the progress bar
      $('.hint-progress').animate({ width: '100%' }, delay * 1000, function(){
        // delay is up, show the hint
        $('.hint-progress-wrapper').hide();
        $('.hint-show').show();
      });
    }

    function countdownComplete(countdownInterval) {
      // stop the timer
      clearInterval(countdownInterval);

      // remove the click handler for skip button
      $('.button-skip-countdown').unbind('click');

      // display ready
      $('.memorize-active').hide();
      $('.memorize-waiting').hide();
      $('.memorize-ready').show();

      // wait for space presses
      $('.passphrase-test').blur();
      $(window).keypress(function(e) {
        if (e.keyCode == 0 || e.keyCode == 32) {
          $(window).unbind('keypress');
          nextTry();
          return false;
        }
      });
    }

    function countdown() {
      typedWithoutLooking = false;

      // show waiting, hide active
      $('.memorize-active').hide();
      $('.memorize-waiting').show();
      $('.memorize-ready').hide();
      $('.memorize-note').html('Give your mind a rest');

      // how many seconds do we wait?
      var total_seconds = countdownValues[currentCountdown];

      // update the countdown each second
      function updateCountdown(){
        var seconds = total_seconds % 60;
        if(seconds < 10) seconds = '0' + seconds;
        var minutes = Math.floor(total_seconds / 60);
        $('.countdown').html(minutes + ':' + seconds);
      }
      updateCountdown();

      var countdownInterval = setInterval(function(){
        updateCountdown();

        // decrease the seconds
        total_seconds--;
        if(total_seconds < 0) {
          // countdown complete, move on to the next countdown
          if(currentCountdown < countdownValues.length - 1)
            currentCountdown++;

          // notification
          Passphrases.soundEffects['notification'].play();

          countdownComplete(countdownInterval);
        }
      }, 1000);

      // skip button
      $('.button-skip-countdown').click(function(){
        countdownComplete(countdownInterval);
      });
    }

    // test for success
    $('.passphrase-test').keyup(function(){
      var passphraseTest = $(this).val();

      // have we achieved success?
      if(passphraseTest == passphrase) {
        // success
        tries++;

        // play success sound effect
        if(typedWithoutLooking) {
          Passphrases.soundEffects['success'].play();
        }

        // is the animation still happening?
        if($('.hint-progress').is(':animated')) {
          typedWithoutLooking = true;
        }

        // 2nd try has a 5 second delay
        if(tries == 1) {
          delay = 5;
        } else {
          // tries 2-6 have slowly longer delays
          if(tries <= 6) {
            delay++;
          }
        }

        // increase delay, if needed
        if(delay <= 10)
          delay++;

        // update stats
        updateStats();

        // after 10 tries, change from text field to password field
        if(tries == 10) {
          $('.passphrase-test').attr('type', 'password');
        }

        if(tries < 15) {
          nextTry();
        } else {
          if(typedWithoutLooking) {
            countdown();
          } else {
            nextTry();
          }
        }
      }
      // check for typo
      else {
        var match = true;

        if(passphraseTest.length > passphrase.length) {
          match = false;
        } else {
          // does it match the passphrase correctly so far?
          for(var i = 0; i < passphraseTest.length; i++) {
            if(passphraseTest[i] != passphrase[i]) {
              match = false;
              break;
            }
          }
        }

        if(match) {
          $(this).removeClass('typo');
        } else {
          $(this).addClass('typo');
        }
      }
    });

    // start at zero seconds
    nextTry();
  }

  $('.button-start-memorizing').click(function(){
    var passphrase = $('.passphrase-to-memorize').val();
    startMemorizing(passphrase);
  });

  $('.button-cancel').click(function(){
    // switch from go mode to input mode
    $('.memorize-input').show();
    $('.memorize-go').hide();

    // stop listening for space keypress
    $(window).unbind('keypress');
  });

  // let the outside world start memorizing a passphrase
  Passphrases.memorizeStart = function(passphrase){
    $('.passphrase-to-memorize').val(passphrase);
    startMemorizing(passphrase);
  };

});
<commit_msg>remove "give your mind a rest" note when countdown completes<commit_after>/* Passphrases | https://github.com/micahflee/passphrases
   Copyright (C) 2015 Micah Lee <micah@micahflee.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

$(function(){
  // initialize memorize
  $('.memorize-input').show();
  $('.memorize-go').hide();

  // start memorizing
  function startMemorizing(passphrase) {
    // switch from input mode to go mode
    $('.memorize-input').hide();
    $('.memorize-go').show();

    $('.memorize-active').show();
    $('.memorize-waiting').hide();
    $('.memorize-ready').hide();

    $('.passphrase-test').attr('type', 'text');

    var delay = 0;
    var tries = 0;
    $('.hint-show').text(passphrase);

    var typedWithoutLooking = false;

    // countdown values are in seconds
    var countdownValues = [60, 120, 300, 600, 1800, 3600];
    var currentCountdown = 0;

    function updateStats() {
      if(tries == 0) {
        $('.memorize-note').html('Type the passphrase');
      } else if(tries == 1) {
        $('.memorize-note').html('Try to type the passphrase before the hint appears');
      } else if(typedWithoutLooking && tries < 10) {
        $('.memorize-note').html('Good job! Try making it to 10 tries');
      } else if(typedWithoutLooking && tries < 15) {
        $('.memorize-note').html('Try making it to 15 tries without seeing what you type');
      } else if(typedWithoutLooking) {
        $('.memorize-note').html('Keep practicing');
      }
      $('.memorize-tries').html(tries);
    }
    updateStats();

    function nextTry() {
      // reset the hint
      $('.hint-progress').stop();
      $('.hint-progress').css('width', '0%');
      $('.hint-progress-wrapper').show();
      $('.hint-show').hide();

      // show active, hide waiting
      $('.memorize-active').show();
      $('.memorize-waiting').hide();
      $('.memorize-ready').hide();

      // reset the passphrase
      $('.passphrase-test').val('').focus();

      // animate the progress bar
      $('.hint-progress').animate({ width: '100%' }, delay * 1000, function(){
        // delay is up, show the hint
        $('.hint-progress-wrapper').hide();
        $('.hint-show').show();
      });
    }

    function countdownComplete(countdownInterval) {
      // stop the timer
      clearInterval(countdownInterval);

      // remove the click handler for skip button
      $('.button-skip-countdown').unbind('click');

      // display ready
      $('.memorize-active').hide();
      $('.memorize-waiting').hide();
      $('.memorize-ready').show();

      // wait for space presses
      $('.passphrase-test').blur();
      $(window).keypress(function(e) {
        if (e.keyCode == 0 || e.keyCode == 32) {
          $(window).unbind('keypress');
          nextTry();
          return false;
        }
      });

      $('.memorize-note').html('Keep practicing');
    }

    function countdown() {
      typedWithoutLooking = false;

      // show waiting, hide active
      $('.memorize-active').hide();
      $('.memorize-waiting').show();
      $('.memorize-ready').hide();
      $('.memorize-note').html('Give your mind a rest');

      // how many seconds do we wait?
      var total_seconds = countdownValues[currentCountdown];

      // update the countdown each second
      function updateCountdown(){
        var seconds = total_seconds % 60;
        if(seconds < 10) seconds = '0' + seconds;
        var minutes = Math.floor(total_seconds / 60);
        $('.countdown').html(minutes + ':' + seconds);
      }
      updateCountdown();

      var countdownInterval = setInterval(function(){
        updateCountdown();

        // decrease the seconds
        total_seconds--;
        if(total_seconds < 0) {
          // countdown complete, move on to the next countdown
          if(currentCountdown < countdownValues.length - 1)
            currentCountdown++;

          // notification
          Passphrases.soundEffects['notification'].play();

          countdownComplete(countdownInterval);
        }
      }, 1000);

      // skip button
      $('.button-skip-countdown').click(function(){
        countdownComplete(countdownInterval);
      });
    }

    // test for success
    $('.passphrase-test').keyup(function(){
      var passphraseTest = $(this).val();

      // have we achieved success?
      if(passphraseTest == passphrase) {
        // success
        tries++;

        // play success sound effect
        if(typedWithoutLooking) {
          Passphrases.soundEffects['success'].play();
        }

        // is the animation still happening?
        if($('.hint-progress').is(':animated')) {
          typedWithoutLooking = true;
        }

        // 2nd try has a 5 second delay
        if(tries == 1) {
          delay = 5;
        } else {
          // tries 2-6 have slowly longer delays
          if(tries <= 6) {
            delay++;
          }
        }

        // increase delay, if needed
        if(delay <= 10)
          delay++;

        // update stats
        updateStats();

        // after 10 tries, change from text field to password field
        if(tries == 10) {
          $('.passphrase-test').attr('type', 'password');
        }

        if(tries < 15) {
          nextTry();
        } else {
          if(typedWithoutLooking) {
            countdown();
          } else {
            nextTry();
          }
        }
      }
      // check for typo
      else {
        var match = true;

        if(passphraseTest.length > passphrase.length) {
          match = false;
        } else {
          // does it match the passphrase correctly so far?
          for(var i = 0; i < passphraseTest.length; i++) {
            if(passphraseTest[i] != passphrase[i]) {
              match = false;
              break;
            }
          }
        }

        if(match) {
          $(this).removeClass('typo');
        } else {
          $(this).addClass('typo');
        }
      }
    });

    // start at zero seconds
    nextTry();
  }

  $('.button-start-memorizing').click(function(){
    var passphrase = $('.passphrase-to-memorize').val();
    startMemorizing(passphrase);
  });

  $('.button-cancel').click(function(){
    // switch from go mode to input mode
    $('.memorize-input').show();
    $('.memorize-go').hide();

    // stop listening for space keypress
    $(window).unbind('keypress');
  });

  // let the outside world start memorizing a passphrase
  Passphrases.memorizeStart = function(passphrase){
    $('.passphrase-to-memorize').val(passphrase);
    startMemorizing(passphrase);
  };

});
<|endoftext|>


<commit_before>"use strict";

var puckt = puckt || {};
puckt.Puck = (function () {
    var Puck = puckt.Obj.extend({
        init: function (world, props) {
            var s, puckImg;
            s = new box2d.b2CircleShape(puckt.util.pixelsToMetres(props.radius));

            this._super(world, "puck", {
                x: props.x,
                y: props.y,
                w: props.radius * 2,
                h: props.radius * 2,
                fixDef: {
                    density: Puck.realMass / (Math.PI * Math.pow(Puck.realRadius, 2)),
                    friction: 1,
                    restitution: 0.8,
                    shape: s
                },
                bodyDef: {
                    type: box2d.b2Body.b2_dynamicBody,
                    angle: props.angle || puckt.util.degreesToRadians(0)
                }
            });

            /*puckImg = new Image(props.w, props.h);
            puckImg.src = '/img/puck.svg';
            this.shape.graphics.beginBitmapFill(puckImg).drawCircle(0, 0, props.radius);*/

            this.shape.graphics.beginFill('#222222').drawCircle(props.radius, props.radius, props.radius);
        }
    });

    Puck.realMass = 0.17 * 100;
    Puck.realRadius = (0.0762 / 2) * 100;

    return Puck;
})();<commit_msg>Recommited Puck<commit_after>"use strict";

var puckt = puckt || {};
puckt.Puck = (function () {
    var Puck = puckt.Obj.extend({
        init: function (world, props) {
            var s, puckImg;
            s = new box2d.b2CircleShape(puckt.util.pixelsToMetres(props.radius));

            this._super(world, "puck", {
                x: props.x,
                y: props.y,
                w: props.radius * 2,
                h: props.radius * 2,
                fixDef: {
                    density: Puck.realMass / (Math.PI * Math.pow(Puck.realRadius, 2)),
                    friction: 1,
                    restitution: 0.8,
                    shape: s
                },
                bodyDef: {
                    type: box2d.b2Body.b2_dynamicBody,
                    angle: props.angle || puckt.util.degreesToRadians(0)
                }
            });

            // puckImg = new Image(props.w, props.h);
            // puckImg.src = 'img/puck.svg';
            // this.shape.graphics.beginBitmapFill(puckImg).drawCircle(props.radius, props.radius, props.radius);

            this.shape.graphics.beginFill('#222222').drawCircle(props.radius, props.radius, props.radius);
        }
    });

    Puck.realMass = 0.17 * 100;
    Puck.realRadius = (0.0762 / 2) * 100;

    return Puck;
})();<|endoftext|>


<commit_before>'use strict';

const {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLList,
  GraphQLID,
  GraphQLInt,
  GraphQLFloat,
  GraphQLBoolean,
  GraphQLString,
  GraphQLNonNull,
  printSchema: graphQLPrintSchema,
} = require('graphql');
const titleize = require('underscore.string/titleize');
const camelize = require('underscore.string/camelize');
const merge = require('lodash/merge');
const { plural, singular } = require('pluralize');
const uuidv4 = require('uuid/v4');

const { EngineError } = require('../../../error');

// Validate IDL definition
// Also performs some transformation, e.g. adding default values
// TODO: use JSON schema validation|transformation instead
// TODO: move all validation into this method
const validateIdlDefinition = function (obj) {
  const modelTypes = Object.keys(obj.models).map(key => obj.models[key].type);
  validateModelsDefinition(obj.models, { modelTypes });
  return obj;
};

const validateModelsDefinition = function (obj, { modelTypes }) {
  if (typeof obj !== 'object') { return obj; }

  Object.keys(obj).forEach(attrName => {
    const child = obj[attrName];
    if (typeof child === 'object') {
      // TODO: should detect whether child _could_ have `type` instead (i.e. is a JSON schema), as we want `type` to be optional
      // Adds def.title default value, by using parent property name
      if (child.type && !child.title) {
        child.title = attrName;
      }
      // Definitions of type `object` must have valid `properties`
      if (child.type === 'object') {
        if (!child.properties || typeof child.properties !== 'object' || Object.keys(child.properties).length === 0) {
          throw new EngineError(`The following definition of type 'object' is missing 'properties': ${JSON.stringify(child)}`, {
            reason: 'IDL_WRONG_DEFINITION',
          });
        }
      }
      // Replace { type: "Model" } by { type: "object", modelName: "Model" }
      const isModel = modelTypes.includes(child.type);
      if (isModel) {
        // Make sure we are not overriding user definitions
        if (child.modelName) {
          throw new EngineError(`The following model cannot have a property named 'modelName': ${JSON.stringify(child)}`, {
            reason: 'IDL_WRONG_DEFINITION',
          });
        }
        Object.assign(child, {
          type: 'object',
          modelName: child.type,
        });
      }
    }
    // Recurse over children
    validateModelsDefinition(child, { modelTypes });
  }, {});

  return obj;
};

// Transforms an IDL definition into an object easy to parse by GraphQL
const getRootDefinition = function ({ definitions, bulkOptions: { write: allowBulkWrite, delete: allowBulkDelete } }) {
  const models = validateIdlDefinition(definitions).models;

  const safeOperations = operations.filter(operation => operation.safe);
  const unsafeOperations = operations.filter(operation => {
    return !operation.safe
      && !(!allowBulkWrite && operation.isBulkWrite)
      && !(!allowBulkDelete && operation.isBulkDelete);
  });
  const safeProperties = getOperationDefinitions({ models, operations: safeOperations });
  const unsafeProperties = getOperationDefinitions({ models, operations: unsafeOperations });

  const rootDef = {
    query: {
      title: 'Query',
      type: 'object',
      description: 'Fetches information about different entities',
      properties: safeProperties,
    },
    mutation: {
      title: 'Mutation',
      type: 'object',
      description: 'Modifies information about different entities',
      properties: unsafeProperties,
    },
  };

  addUniqueId(rootDef);
  // This property is invalid in GraphQL, for the schema object
  delete rootDef.__uniqueId;

  return rootDef;
};

const getOperationDefinitions = function({ models, operations }) {
  return operations.reduce((memo, operation) => {
    const props = getOperationDefinition({ models, operation });
    // Make a deep copy for each definition object
    // Otherwise, each definition would refer each other, which would create some problems, e.g. when assigning different __uniqueId
    const copiedProps = merge({}, props);
    Object.assign(memo, copiedProps);
    return memo;
  }, {});
};

const getOperationDefinition = function ({ models, operation }) {
  return Object.keys(models).reduce((properties, modelName) => {
    const model = models[modelName];
    const name = model.title || modelName;
    const def = Object.assign({}, model, { name });

    // `find*` operations are aliased for convenience
    // E.g. `findPet` and `findPets` -> `pet` and `pets`
    const isFind = operation.prefix === 'find';

    // E.g. `updatePets` operation
    let operationName;
    if (operation.multiple) {
      operationName = isFind ? getPluralName(def) : getPluralOperationName(def, operation.prefix);
      properties[operationName] = { type: 'array', items: def };
    // E.g. `updatePet` operation
    } else {
      operationName = isFind ? getSingularName(def) : getSingularOperationName(def, operation.prefix);
      properties[operationName] = def;
    }

    properties[operationName].operation = operation.prefix;

    return properties;
  }, {});
};

/* eslint-disable no-multi-spaces */
const operations = [
  { name: 'findOne',      prefix: 'find',     safe: true,   multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'findMany',     prefix: 'find',     safe: true,   multiple: true,   isBulkWrite: false, isBulkDelete: false },
  { name: 'createOne',    prefix: 'create',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'createMany',   prefix: 'create',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'replaceOne',   prefix: 'replace',  safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'replaceMany',  prefix: 'replace',  safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'updateOne',    prefix: 'update',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'updateMany',   prefix: 'update',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'upsertOne',    prefix: 'upsert',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'upsertMany',   prefix: 'upsert',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'deleteOne',    prefix: 'delete',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'deleteMany',   prefix: 'delete',   safe: false,  multiple: true,   isBulkWrite: false, isBulkDelete: true  },
];
/* eslint-enable no-multi-spaces */

// Add unique ids to every object, used for caching and avoiding infinite recursion
const addUniqueId = function (obj) {
  if (typeof obj !== 'object') { return obj; }
  obj.__uniqueId = uuidv4();
  Object.keys(obj).forEach(key => addUniqueId(obj[key]));
  return obj;
};

// Retrieves all root model definitions, so that recursive sub-models can point to them
const getModels = function (rootDef) {
  return Object.keys(rootDef).reduce((memo, name) => {
    const props = rootDef[name].properties;
    const subProps = Object.keys(props).reduce((memo,val) => {
      if (typeof props[val] !== 'object') { return memo; }
      return memo.concat(props[val]);
    }, []);
    return memo.concat(subProps);
  }, []);
};

// Returns GraphQL schema
const getSchema = function ({ definitions, bulkOptions }) {
  const key = `schema/${JSON.stringify({ definitions, bulkOptions })}/top`;
  if (cache.exists(key)) {
    return cache.get(key);
  }
  // Deep copy, so we do not modify input
  definitions = merge({}, definitions);

  const rootDef = getRootDefinition({ definitions, bulkOptions });
  const schemaId = uuidv4();
  const models = getModels(rootDef);

  // Apply `getType` to each top-level operation, i.e. Query and Mutation
  const topLevelSchema = Object.keys(rootDef).reduce((memo, name) => {
    const def = rootDef[name];
    memo[name] = getType(def, { schemaId, models });
    return memo;
  }, {});

  const rootSchema = new GraphQLSchema(topLevelSchema);
  cache.set(key, rootSchema);
  return rootSchema;
};

// Retrieves a GraphQL field info for a given IDL definition, i.e. an object that can be passed to new GraphQLObjectType({ fields })
// Includes return type, resolve function, arguments, etc.
const getField = function (def, opts) {
  // When top-level model enters this function, `def.operation` will be defined.
  // This passed the operation to all sub-schemas, so that resolvers know the current operation
  const operation = def.operation || opts.operation;
  opts = Object.assign({}, opts, { operation });

  // Done so that children can get a cached reference of parent type, while avoiding infinite recursion
  // Only cache schemas that have a model name, because they are the only one that can recurse
  // Namespace by operation, because operations can have slightly different types
  const modelName = def.modelName;
  const key = modelName && `field/${opts.schemaId}/${modelName}/${operation}`;
  if (key && cache.exists(key)) {
    return cache.get(key);
  }

  // Retrieves correct field
  const fieldInfo = graphQLFieldsInfo.find(possibleType => possibleType.condition(def));
  if (!fieldInfo) {
    throw new EngineError(`Could not parse property into a GraphQL type: ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }

  // Retrieves field information
  const field = fieldInfo.value(def, opts);

  if (key) {
    cache.set(key, field);
  }
  return field;
};

// Retrieves the GraphQL type for a given IDL definition
const getType = function (def, opts) {
  return getField(def, opts).type;
};

/**
 * Maps an IDL definition into a GraphQL field information, including type
 * The first matching one will be used, i.e. order matters: required modifier, then array modifier come first
 */
const graphQLFieldsInfo = [

  {
    condition: def => def.required,
    value(def, opts) {
      // Goal is to avoid infinite recursion, i.e. without modification the same graphQLFieldsInfo would be hit again
      const modifiedDef = Object.assign({}, def, { required: false });
      const subType = getType(modifiedDef, opts);
      const type = new GraphQLNonNull(subType);
      const description = def.description || (def.items && def.items.description);
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'array' && typeof def.items === 'object',
    value(def, opts) {
      const subDef = def.items;
      const subType = getType(subDef, opts);
      const type = new GraphQLList(subType);
      const description = subDef.description;
      const fieldInfo = { type, description };

      // If this is a top-level model, assign resolver
      if (subDef.modelName) {
        Object.assign(fieldInfo, {
          args: {
            id: {
              type: GraphQLInt,
              description: 'id to look at',
              defaultValue: 10
            },
          },
          async resolve(_, args, { callback }) {
            const operation = operations.find(op => op.prefix === opts.operation && op.multiple);
            return await executeOperation({ operation, args, callback });
          },
        });
      }

      return fieldInfo;
    },
  },

  {
    condition: def => def.type === 'object',
    value(initialDef, opts) {
      // If this definition points to a top-level model, use that model instead
      const topDef = opts.models.find(model => initialDef.modelName
        && model.modelName === initialDef.modelName
        && opts.operation === model.operation);
      const def = topDef || initialDef;

      let name = getTypeName(def, opts.operation);
      // Cannot create two GraphQL object types with the same name
      // Fix it by appending underscores to the name
      // TODO: either prefix with top-level model name, or throw exception, or some other better solution
      let key = `typename/${opts.schemaId}/${name}`;
      while (cache.exists(key)) {
        name += '_';
        key += '_';
      }
      cache.set(key, true);

      const description = def.description;

      const type = new GraphQLObjectType({
        name,
        description,

        // This needs to be function, otherwise we run in an infinite recursion,
        // if the children try to reference a parent type
        fields() {
          return Object.keys(def.properties).reduce((fields, attrName) => {
            // Those are created by us, do not use them for GraphQL schema
            if (['__uniqueId'].includes(attrName)) { return fields; }

            const childDef = def.properties[attrName];
            // Recurse over children
            fields[attrName] = getField(childDef, opts);
            return fields;
          }, {});
        },
      });

      let fieldInfo = { type, description };

      // If this is a top-level model, assign resolver
      if (initialDef.modelName) {
        Object.assign(fieldInfo, {
          //description: `Fetches information about a ${getSingularName(def)}`,
          async resolve(_, args, { callback }) {
            const operation = operations.find(op => op.prefix === opts.operation && !op.multiple);
            return await executeOperation({ operation, args, callback });
          },
        });
      }

      return fieldInfo;
    },
  },

  {
    condition: def => def.type === 'integer' && def.format === 'id',
    value(def) {
      const type = GraphQLID;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'integer',
    value(def) {
      const type = GraphQLInt;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'number',
    value(def) {
      const type = GraphQLFloat;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'string',
    value(def) {
      const type = GraphQLString;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'boolean',
    value(def) {
      const type = GraphQLBoolean;
      const description = def.description;
      return { type, description };
    },
  },

];

const executeOperation = async function ({ operation, args = {}, callback }) {
  const response = await callback({ operation, args });
  return response;
};

// Returns def.title
const getDefinitionName = function (def) {
  const name = def.title;
  if (!name) {
    throw new EngineError(`Missing "title" key in definition ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }
  if (typeof def.title !== 'string') {
    throw new EngineError(`"title" must be a string in definition ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }
  return name;
};

// Returns def.title, in plural form, lowercased, e.g. `pets`, for findMany queries
const getPluralName = function (def) {
  const name = getDefinitionName(def);
  return plural(name).toLowerCase();
};

// Returns def.title, in plural form, lowercased, e.g. `pet`, for findOne queries
const getSingularName = function (def) {
  const name = getDefinitionName(def);
  return singular(name).toLowerCase();
};

// Returns operation name, camelized, in plural form, e.g. `findPets` or `deletePets`
const getPluralOperationName = function (def, operation) {
  const name = getPluralName(def);
  return camelize(`${operation} ${name}`);
};

// Returns operation name, camelized, in singular form, e.g. `findPet` or `deletePet`
const getSingularOperationName = function (def, operation) {
  const name = getSingularName(def);
  return camelize(`${operation} ${name}`);
};

// Returns def.title, titleized, e.g. `Pet`, for schema type name
const getTypeName = function (def, operation) {
  const name = getDefinitionName(def);
  return titleize(operation) + titleize(singular(name));
};

const printSchema = function (schema) {
  return graphQLPrintSchema(schema);
};

// General key-value cache, specific for each root IDL definition
const cache = {

  _data: {},

  get(key) {
    return this._data[key];
  },

  exists(key) {
    return this.get(key) != null;
  },

  set(key, value) {
    this._data[key] = value;
  },

};

module.exports = {
  graphqlGetSchema: getSchema,
  graphqlPrintSchema: printSchema,
};<commit_msg>Remove dead code<commit_after>'use strict';

const {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLList,
  GraphQLID,
  GraphQLInt,
  GraphQLFloat,
  GraphQLBoolean,
  GraphQLString,
  GraphQLNonNull,
  printSchema: graphQLPrintSchema,
} = require('graphql');
const titleize = require('underscore.string/titleize');
const camelize = require('underscore.string/camelize');
const merge = require('lodash/merge');
const { plural, singular } = require('pluralize');
const uuidv4 = require('uuid/v4');

const { EngineError } = require('../../../error');

// Validate IDL definition
// Also performs some transformation, e.g. adding default values
// TODO: use JSON schema validation|transformation instead
// TODO: move all validation into this method
const validateIdlDefinition = function (obj) {
  const modelTypes = Object.keys(obj.models).map(key => obj.models[key].type);
  validateModelsDefinition(obj.models, { modelTypes });
  return obj;
};

const validateModelsDefinition = function (obj, { modelTypes }) {
  if (typeof obj !== 'object') { return obj; }

  Object.keys(obj).forEach(attrName => {
    const child = obj[attrName];
    if (typeof child === 'object') {
      // TODO: should detect whether child _could_ have `type` instead (i.e. is a JSON schema), as we want `type` to be optional
      // Adds def.title default value, by using parent property name
      if (child.type && !child.title) {
        child.title = attrName;
      }
      // Definitions of type `object` must have valid `properties`
      if (child.type === 'object') {
        if (!child.properties || typeof child.properties !== 'object' || Object.keys(child.properties).length === 0) {
          throw new EngineError(`The following definition of type 'object' is missing 'properties': ${JSON.stringify(child)}`, {
            reason: 'IDL_WRONG_DEFINITION',
          });
        }
      }
      // Replace { type: "Model" } by { type: "object", modelName: "Model" }
      const isModel = modelTypes.includes(child.type);
      if (isModel) {
        // Make sure we are not overriding user definitions
        if (child.modelName) {
          throw new EngineError(`The following model cannot have a property named 'modelName': ${JSON.stringify(child)}`, {
            reason: 'IDL_WRONG_DEFINITION',
          });
        }
        Object.assign(child, {
          type: 'object',
          modelName: child.type,
        });
      }
    }
    // Recurse over children
    validateModelsDefinition(child, { modelTypes });
  }, {});

  return obj;
};

// Transforms an IDL definition into an object easy to parse by GraphQL
const getRootDefinition = function ({ definitions, bulkOptions: { write: allowBulkWrite, delete: allowBulkDelete } }) {
  const models = validateIdlDefinition(definitions).models;

  const safeOperations = operations.filter(operation => operation.safe);
  const unsafeOperations = operations.filter(operation => {
    return !operation.safe
      && !(!allowBulkWrite && operation.isBulkWrite)
      && !(!allowBulkDelete && operation.isBulkDelete);
  });
  const safeProperties = getOperationDefinitions({ models, operations: safeOperations });
  const unsafeProperties = getOperationDefinitions({ models, operations: unsafeOperations });

  const rootDef = {
    query: {
      title: 'Query',
      type: 'object',
      description: 'Fetches information about different entities',
      properties: safeProperties,
    },
    mutation: {
      title: 'Mutation',
      type: 'object',
      description: 'Modifies information about different entities',
      properties: unsafeProperties,
    },
  };

  return rootDef;
};

const getOperationDefinitions = function({ models, operations }) {
  return operations.reduce((memo, operation) => {
    const props = getOperationDefinition({ models, operation });
    // Make a deep copy for each definition object
    // Otherwise, each definition would refer each other, which would create some problems
    const copiedProps = merge({}, props);
    Object.assign(memo, copiedProps);
    return memo;
  }, {});
};

const getOperationDefinition = function ({ models, operation }) {
  return Object.keys(models).reduce((properties, modelName) => {
    const model = models[modelName];
    const name = model.title || modelName;
    const def = Object.assign({}, model, { name });

    // `find*` operations are aliased for convenience
    // E.g. `findPet` and `findPets` -> `pet` and `pets`
    const isFind = operation.prefix === 'find';

    // E.g. `updatePets` operation
    let operationName;
    if (operation.multiple) {
      operationName = isFind ? getPluralName(def) : getPluralOperationName(def, operation.prefix);
      properties[operationName] = { type: 'array', items: def };
    // E.g. `updatePet` operation
    } else {
      operationName = isFind ? getSingularName(def) : getSingularOperationName(def, operation.prefix);
      properties[operationName] = def;
    }

    properties[operationName].operation = operation.prefix;

    return properties;
  }, {});
};

/* eslint-disable no-multi-spaces */
const operations = [
  { name: 'findOne',      prefix: 'find',     safe: true,   multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'findMany',     prefix: 'find',     safe: true,   multiple: true,   isBulkWrite: false, isBulkDelete: false },
  { name: 'createOne',    prefix: 'create',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'createMany',   prefix: 'create',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'replaceOne',   prefix: 'replace',  safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'replaceMany',  prefix: 'replace',  safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'updateOne',    prefix: 'update',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'updateMany',   prefix: 'update',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'upsertOne',    prefix: 'upsert',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'upsertMany',   prefix: 'upsert',   safe: false,  multiple: true,   isBulkWrite: true,  isBulkDelete: false },
  { name: 'deleteOne',    prefix: 'delete',   safe: false,  multiple: false,  isBulkWrite: false, isBulkDelete: false },
  { name: 'deleteMany',   prefix: 'delete',   safe: false,  multiple: true,   isBulkWrite: false, isBulkDelete: true  },
];
/* eslint-enable no-multi-spaces */

// Retrieves all root model definitions, so that recursive sub-models can point to them
const getModels = function (rootDef) {
  return Object.keys(rootDef).reduce((memo, name) => {
    const props = rootDef[name].properties;
    const subProps = Object.keys(props).reduce((memo,val) => {
      if (typeof props[val] !== 'object') { return memo; }
      return memo.concat(props[val]);
    }, []);
    return memo.concat(subProps);
  }, []);
};

// Returns GraphQL schema
const getSchema = function ({ definitions, bulkOptions }) {
  const key = `schema/${JSON.stringify({ definitions, bulkOptions })}/top`;
  if (cache.exists(key)) {
    return cache.get(key);
  }
  // Deep copy, so we do not modify input
  definitions = merge({}, definitions);

  const rootDef = getRootDefinition({ definitions, bulkOptions });
  const schemaId = uuidv4();
  const models = getModels(rootDef);

  // Apply `getType` to each top-level operation, i.e. Query and Mutation
  const topLevelSchema = Object.keys(rootDef).reduce((memo, name) => {
    const def = rootDef[name];
    memo[name] = getType(def, { schemaId, models });
    return memo;
  }, {});

  const rootSchema = new GraphQLSchema(topLevelSchema);
  cache.set(key, rootSchema);
  return rootSchema;
};

// Retrieves a GraphQL field info for a given IDL definition, i.e. an object that can be passed to new GraphQLObjectType({ fields })
// Includes return type, resolve function, arguments, etc.
const getField = function (def, opts) {
  // When top-level model enters this function, `def.operation` will be defined.
  // This passed the operation to all sub-schemas, so that resolvers know the current operation
  const operation = def.operation || opts.operation;
  opts = Object.assign({}, opts, { operation });

  // Done so that children can get a cached reference of parent type, while avoiding infinite recursion
  // Only cache schemas that have a model name, because they are the only one that can recurse
  // Namespace by operation, because operations can have slightly different types
  const modelName = def.modelName;
  const key = modelName && `field/${opts.schemaId}/${modelName}/${operation}`;
  if (key && cache.exists(key)) {
    return cache.get(key);
  }

  // Retrieves correct field
  const fieldInfo = graphQLFieldsInfo.find(possibleType => possibleType.condition(def));
  if (!fieldInfo) {
    throw new EngineError(`Could not parse property into a GraphQL type: ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }

  // Retrieves field information
  const field = fieldInfo.value(def, opts);

  if (key) {
    cache.set(key, field);
  }
  return field;
};

// Retrieves the GraphQL type for a given IDL definition
const getType = function (def, opts) {
  return getField(def, opts).type;
};

/**
 * Maps an IDL definition into a GraphQL field information, including type
 * The first matching one will be used, i.e. order matters: required modifier, then array modifier come first
 */
const graphQLFieldsInfo = [

  {
    condition: def => def.required,
    value(def, opts) {
      // Goal is to avoid infinite recursion, i.e. without modification the same graphQLFieldsInfo would be hit again
      const modifiedDef = Object.assign({}, def, { required: false });
      const subType = getType(modifiedDef, opts);
      const type = new GraphQLNonNull(subType);
      const description = def.description || (def.items && def.items.description);
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'array' && typeof def.items === 'object',
    value(def, opts) {
      const subDef = def.items;
      const subType = getType(subDef, opts);
      const type = new GraphQLList(subType);
      const description = subDef.description;
      const fieldInfo = { type, description };

      // If this is a top-level model, assign resolver
      if (subDef.modelName) {
        Object.assign(fieldInfo, {
          args: {
            id: {
              type: GraphQLInt,
              description: 'id to look at',
              defaultValue: 10
            },
          },
          async resolve(_, args, { callback }) {
            const operation = operations.find(op => op.prefix === opts.operation && op.multiple);
            return await executeOperation({ operation, args, callback });
          },
        });
      }

      return fieldInfo;
    },
  },

  {
    condition: def => def.type === 'object',
    value(initialDef, opts) {
      // If this definition points to a top-level model, use that model instead
      const topDef = opts.models.find(model => initialDef.modelName
        && model.modelName === initialDef.modelName
        && opts.operation === model.operation);
      const def = topDef || initialDef;

      let name = getTypeName(def, opts.operation);
      // Cannot create two GraphQL object types with the same name
      // Fix it by appending underscores to the name
      // TODO: either prefix with top-level model name, or throw exception, or some other better solution
      let key = `typename/${opts.schemaId}/${name}`;
      while (cache.exists(key)) {
        name += '_';
        key += '_';
      }
      cache.set(key, true);

      const description = def.description;

      const type = new GraphQLObjectType({
        name,
        description,

        // This needs to be function, otherwise we run in an infinite recursion,
        // if the children try to reference a parent type
        fields() {
          return Object.keys(def.properties).reduce((fields, attrName) => {
            const childDef = def.properties[attrName];
            // Recurse over children
            fields[attrName] = getField(childDef, opts);
            return fields;
          }, {});
        },
      });

      let fieldInfo = { type, description };

      // If this is a top-level model, assign resolver
      if (initialDef.modelName) {
        Object.assign(fieldInfo, {
          //description: `Fetches information about a ${getSingularName(def)}`,
          async resolve(_, args, { callback }) {
            const operation = operations.find(op => op.prefix === opts.operation && !op.multiple);
            return await executeOperation({ operation, args, callback });
          },
        });
      }

      return fieldInfo;
    },
  },

  {
    condition: def => def.type === 'integer' && def.format === 'id',
    value(def) {
      const type = GraphQLID;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'integer',
    value(def) {
      const type = GraphQLInt;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'number',
    value(def) {
      const type = GraphQLFloat;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'string',
    value(def) {
      const type = GraphQLString;
      const description = def.description;
      return { type, description };
    },
  },

  {
    condition: def => def.type === 'boolean',
    value(def) {
      const type = GraphQLBoolean;
      const description = def.description;
      return { type, description };
    },
  },

];

const executeOperation = async function ({ operation, args = {}, callback }) {
  const response = await callback({ operation, args });
  return response;
};

// Returns def.title
const getDefinitionName = function (def) {
  const name = def.title;
  if (!name) {
    throw new EngineError(`Missing "title" key in definition ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }
  if (typeof def.title !== 'string') {
    throw new EngineError(`"title" must be a string in definition ${JSON.stringify(def)}`, { reason: 'GRAPHQL_WRONG_DEFINITION' });
  }
  return name;
};

// Returns def.title, in plural form, lowercased, e.g. `pets`, for findMany queries
const getPluralName = function (def) {
  const name = getDefinitionName(def);
  return plural(name).toLowerCase();
};

// Returns def.title, in plural form, lowercased, e.g. `pet`, for findOne queries
const getSingularName = function (def) {
  const name = getDefinitionName(def);
  return singular(name).toLowerCase();
};

// Returns operation name, camelized, in plural form, e.g. `findPets` or `deletePets`
const getPluralOperationName = function (def, operation) {
  const name = getPluralName(def);
  return camelize(`${operation} ${name}`);
};

// Returns operation name, camelized, in singular form, e.g. `findPet` or `deletePet`
const getSingularOperationName = function (def, operation) {
  const name = getSingularName(def);
  return camelize(`${operation} ${name}`);
};

// Returns def.title, titleized, e.g. `Pet`, for schema type name
const getTypeName = function (def, operation) {
  const name = getDefinitionName(def);
  return titleize(operation) + titleize(singular(name));
};

const printSchema = function (schema) {
  return graphQLPrintSchema(schema);
};

// General key-value cache, specific for each root IDL definition
const cache = {

  _data: {},

  get(key) {
    return this._data[key];
  },

  exists(key) {
    return this.get(key) != null;
  },

  set(key, value) {
    this._data[key] = value;
  },

};

module.exports = {
  graphqlGetSchema: getSchema,
  graphqlPrintSchema: printSchema,
};<|endoftext|>


<commit_before>var Uber = {
	accessToken: localStorage.getItem('accessToken') || '',
	refreshToken: localStorage.getItem('refreshToken') || '',
	locations: JSON.parse(localStorage.getItem('locations')) || [],
	products: [],

	init: function() {
		Uber.refresh();
	},

	error: function(error) {
		appMessageQueue.clear();
		appMessageQueue.send({type:TYPE.ERROR, name:error});
	},

	sendLocations: function() {
		appMessageQueue.send({type:TYPE.LOCATION, method:METHOD.SIZE, index:Uber.locations.length});
		for (var i = 0; i < Uber.locations.length; i++) {
			var name = Uber.locations[i].name.substring(0,12);
			appMessageQueue.send({type:TYPE.LOCATION, method:METHOD.DATA, index:i, name:name, estimate:''});
		}
	},

	sendProducts: function() {
		appMessageQueue.send({type:TYPE.PRODUCT, method:METHOD.SIZE, index:Uber.products.length});
		for (var i = 0; i < Uber.products.length; i++) {
			var name = Uber.products[i].display_name.substring(0,12);
			var estimate = Math.ceil(Uber.products[i].estimate / 60) + ' min';
			var surge = (Uber.products[i].surge_multiplier > 1) ? 'Surge pricing: ' + Uber.products[i].surge_multiplier + 'x' : '';
			var resource = resources[name] || resources.uberX;
			appMessageQueue.send({type:TYPE.PRODUCT, method:METHOD.DATA, index:i, name:name, estimate:estimate, surge:surge, resource:resource-1});
		}
	},

	checkProducts: function(latitude, longitude) {
		Uber.error('Uber is not yet available at this location.');
		Uber.api.products(latitude, longitude, function(xhr) {
			var res = JSON.parse(xhr.responseText);
			console.log(JSON.stringify(res));
			if (res.products && res.products.length > 0) {
				Uber.error('All nearby cars are full but one should free up soon. Please try again in a few minutes.');
			}
		});
	},

	getPriceEstimates: function(productIndex) {
		navigator.geolocation.getCurrentPosition(function(pos) {
			var latitude = pos.coords.latitude || 0;
			var longitude = pos.coords.longitude || 0;
			var i = 0;
			if (!Uber.accessToken || true) {
				Uber.locations.forEach(function(location) {
					var index = i++;
					var url = 'https://ineal.me/pebble/ultra/api/estimates/price?start_latitude=' + latitude + '&start_longitude=' + longitude + '&end_latitude=' + location.lat + '&end_longitude=' + location.lng;
					var xhr = new XMLHttpRequest();
					xhr.open('GET', url, true);
					xhr.onload = function() {
						var res = JSON.parse(xhr.responseText);
						console.log(JSON.stringify(res));
						if (!res.prices || !res.prices.length) return;
						var name = location.name.substring(0,12);
						var estimate = '';
						res.prices.forEach(function(price) {
							if (Uber.products[productIndex].product_id == price.product_id) {
								estimate = price.estimate;
							}
						});
						appMessageQueue.send({type:TYPE.LOCATION, method:METHOD.DATA, index:index, name:name, estimate:estimate});
					};
					xhr.send(null);
				});
				return;
			}
			Uber.locations.forEach(function(location) {
				var index = i++;
				Uber.api.priceEstimates(latitude, longitude, location.lat, location.lng, function(xhr) {
					var res = JSON.parse(xhr.responseText);
					console.log(JSON.stringify(res));
					if (xhr.status === 401) {
						return Uber.refreshAccessToken(Uber.refresh);
					}
					if (res.code && res.code == 'distance_exceeded') {
						// do something special?
					}
					if (!res.prices || !res.prices.length) return;
					var name = location.name.substring(0,12);
					var estimate = '';
					res.prices.forEach(function(price) {
						if (Uber.products[productIndex].product_id == price.product_id) {
							estimate = price.estimate;
						}
					});
					appMessageQueue.send({type:TYPE.LOCATION, method:METHOD.DATA, index:index, name:name, estimate:estimate});
				});
			});
		// }, function(err) { Uber.error('Failed to get geolocation!'); }, { timeout: 10000 });
	},

	refresh: function() {
		Uber.error('Trying to get Geolocation...');
		// navigator.geolocation.getCurrentPosition(function(pos) {
			var pos = {};
			pos.coords = fakeLocations.sanfran;
			var latitude = pos.coords.latitude || 0;
			var longitude = pos.coords.longitude || 0;
			Uber.error('Requesting estimated pick up times...');
			if (!Uber.accessToken) {
				var url = 'https://ineal.me/pebble/ultra/api/estimates/time?latitude=' + latitude + '&longitude=' + longitude;
				var xhr = new XMLHttpRequest();
				xhr.open('GET', url, true);
				xhr.onload = function() {
					var res = JSON.parse(xhr.responseText);
					console.log(JSON.stringify(res));
					if (!res.times || !res.times.length) {
						return Uber.error('Uber is not yet available at this location or all cars are full at the moment. Please try again in a few minutes.');
					}
					Uber.products = res.times;
					Uber.sendProducts();
				};
				xhr.onerror = function() { Uber.error('Connection error!'); };
				xhr.ontimeout = function() { Uber.error('Connection to Uber API timed out!'); };
				xhr.timeout = 30000;
				xhr.send(null);
				return;
			}
			Uber.api.timeEstimates(latitude, longitude, function(xhr) {
				var res = JSON.parse(xhr.responseText);
				console.log(JSON.stringify(res));
				if (xhr.status === 401) {
					return Uber.refreshAccessToken(Uber.refresh);
				}
				if (!res.times) return;
				if (!res.times.length) {
					return Uber.checkProducts(latitude, longitude);
				}
				Uber.products = res.times;
				Uber.error('Checking for surge pricing...');
				Uber.api.priceEstimates(latitude, longitude, latitude, longitude, function(xhr2) {
					var res2 = JSON.parse(xhr2.responseText);
					console.log(JSON.stringify(res2));
					if (res2.prices && res2.prices.length > 0) {
						res2.prices.forEach(function(price) {
							for (var i = 0; i < Uber.products.length; i++) {
								if (Uber.products[i].product_id == price.product_id) {
									Uber.products[i].surge_multiplier = price.surge_multiplier;
								}
							}
						});
					}
					Uber.sendProducts();
				}, function(err) {
					Uber.sendProducts();
				});
			}, Uber.error);
		}, function(err) { Uber.error('Failed to get geolocation!'); }, { timeout: 10000 });
	},

	api: {
		products: function(latitude, longitude, cb, fb) {
			Uber.api.makeRequest('GET', '/v1/products', serialize({latitude:latitude, longitude:longitude}), cb, fb);
		},

		timeEstimates: function(latitude, longitude, cb, fb) {
			Uber.api.makeRequest('GET', '/v1/estimates/time', serialize({start_latitude:latitude, start_longitude:longitude}), cb, fb);
		},

		priceEstimates: function(start_lat, start_lng, end_lat, end_lng, cb, fb) {
			Uber.api.makeRequest('GET', '/v1/estimates/price', serialize({start_latitude:start_lat, start_longitude:start_lng, end_latitude:end_lat, end_longitude:end_lng}), cb, fb);
		},

		makeRequest: function(method, endpoint, data, cb, fb) {
			if (!Uber.accessToken) return Uber.error('Please log in to your Uber account on the Pebble mobile app.');
			var url = 'https://api.uber.com' + endpoint;
			if (method == 'GET' && data) {
				url += '?' + data;
				data = null;
			}
			console.log(method + ' ' + url + ' ' + data);
			var xhr = new XMLHttpRequest();
			xhr.setRequestHeader('Authorization', 'Bearer ' + Uber.accessToken);
			xhr.open(method, url, true);
			xhr.onload = function() { cb(xhr); };
			xhr.onerror = function() { fb('Connection error!'); };
			xhr.ontimeout = function() { fb('Connection to Uber API timed out!'); };
			xhr.timeout = 30000;
			xhr.send(data);
		}
	},

	handleAppMessage: function(e) {
		console.log('AppMessage received: ' + JSON.stringify(e.payload));
		if (!e.payload.method) return;
		switch (e.payload.method) {
			case METHOD.REFRESH:
				Uber.refresh();
				break;
			case METHOD.REQUESTLOCATIONS:
				Uber.sendLocations();
				Uber.getPriceEstimates(e.payload.index);
				break;
			case METHOD.REQUESTPRICE:
				Uber.getPriceEstimates(e.payload.index);
				break;
		}
	},

	refreshAccessToken: function(cb) {
		var xhr = new XMLHttpRequest();
		xhr.open('GET', 'https://ineal.me/pebble/ultra/api/refresh?token=' + Uber.refreshToken, true);
		xhr.onload = function() {
			var res = JSON.parse(xhr.responseText);
			console.log('new tokens: ' + JSON.stringify(res));
			if (res.access_token) {
				Uber.accessToken = res.access_token;
				localStorage.setItem('accessToken', Uber.accessToken);
			}
			if (res.refresh_token) {
				Uber.refreshToken = res.refresh_token;
				localStorage.setItem('refreshToken', Uber.refreshToken);
			}
			if (cb && typeof(cb) === 'function') {
				cb();
			}
		};
		xhr.send(null);
	},

	showConfiguration: function() {
		var data = {locations: Uber.locations};
		Pebble.openURL('https://ineal.me/pebble/ultra/configuration/?data=' + encodeURIComponent(JSON.stringify(data)));
	},

	handleConfiguration: function(e) {
		console.log('configuration received: ' + JSON.stringify(e));
		if (!e.response) return;
		var data = JSON.parse(decodeURIComponent(e.response));
		if (data.keys) {
			if (data.keys.access_token) {
				Uber.accessToken = data.keys.access_token;
				localStorage.setItem('accessToken', Uber.accessToken);
			}
			if (data.keys.refresh_token) {
				Uber.refreshToken = data.keys.refresh_token;
				localStorage.setItem('refreshToken', Uber.refreshToken);
			}
			Uber.refresh();
		}
		if (data.locations) {
			Uber.locations = data.locations;
			localStorage.setItem('locations', JSON.stringify(Uber.locations));
			Uber.sendLocations();
		}
	}
};

Pebble.addEventListener('ready', Uber.init);
Pebble.addEventListener('appmessage', Uber.handleAppMessage);
Pebble.addEventListener('showConfiguration', Uber.showConfiguration);
Pebble.addEventListener('webviewclosed', Uber.handleConfiguration);
<commit_msg>Accidentally commited testing stuff. Oops.<commit_after>var Uber = {
	accessToken: localStorage.getItem('accessToken') || '',
	refreshToken: localStorage.getItem('refreshToken') || '',
	locations: JSON.parse(localStorage.getItem('locations')) || [],
	products: [],

	init: function() {
		Uber.refresh();
	},

	error: function(error) {
		appMessageQueue.clear();
		appMessageQueue.send({type:TYPE.ERROR, name:error});
	},

	sendLocations: function() {
		appMessageQueue.send({type:TYPE.LOCATION, method:METHOD.SIZE, index:Uber.locations.length});
		for (var i = 0; i < Uber.locations.length; i++) {
			var name = Uber.locations[i].name.substring(0,12);
			appMessageQueue.send({type:TYPE.LOCATION, method:METHOD.DATA, index:i, name:name, estimate:''});
		}
	},

	sendProducts: function() {
		appMessageQueue.send({type:TYPE.PRODUCT, method:METHOD.SIZE, index:Uber.products.length});
		for (var i = 0; i < Uber.products.length; i++) {
			var name = Uber.products[i].display_name.substring(0,12);
			var estimate = Math.ceil(Uber.products[i].estimate / 60) + ' min';
			var surge = (Uber.products[i].surge_multiplier > 1) ? 'Surge pricing: ' + Uber.products[i].surge_multiplier + 'x' : '';
			var resource = resources[name] || resources.uberX;
			appMessageQueue.send({type:TYPE.PRODUCT, method:METHOD.DATA, index:i, name:name, estimate:estimate, surge:surge, resource:resource-1});
		}
	},

	checkProducts: function(latitude, longitude) {
		Uber.error('Uber is not yet available at this location.');
		Uber.api.products(latitude, longitude, function(xhr) {
			var res = JSON.parse(xhr.responseText);
			console.log(JSON.stringify(res));
			if (res.products && res.products.length > 0) {
				Uber.error('All nearby cars are full but one should free up soon. Please try again in a few minutes.');
			}
		});
	},

	getPriceEstimates: function(productIndex) {
		navigator.geolocation.getCurrentPosition(function(pos) {
			var latitude = pos.coords.latitude || 0;
			var longitude = pos.coords.longitude || 0;
			var i = 0;
			if (!Uber.accessToken || true) {
				Uber.locations.forEach(function(location) {
					var index = i++;
					var url = 'https://ineal.me/pebble/ultra/api/estimates/price?start_latitude=' + latitude + '&start_longitude=' + longitude + '&end_latitude=' + location.lat + '&end_longitude=' + location.lng;
					var xhr = new XMLHttpRequest();
					xhr.open('GET', url, true);
					xhr.onload = function() {
						var res = JSON.parse(xhr.responseText);
						console.log(JSON.stringify(res));
						if (!res.prices || !res.prices.length) return;
						var name = location.name.substring(0,12);
						var estimate = '';
						res.prices.forEach(function(price) {
							if (Uber.products[productIndex].product_id == price.product_id) {
								estimate = price.estimate;
							}
						});
						appMessageQueue.send({type:TYPE.LOCATION, method:METHOD.DATA, index:index, name:name, estimate:estimate});
					};
					xhr.send(null);
				});
				return;
			}
			Uber.locations.forEach(function(location) {
				var index = i++;
				Uber.api.priceEstimates(latitude, longitude, location.lat, location.lng, function(xhr) {
					var res = JSON.parse(xhr.responseText);
					console.log(JSON.stringify(res));
					if (xhr.status === 401) {
						return Uber.refreshAccessToken(Uber.refresh);
					}
					if (res.code && res.code == 'distance_exceeded') {
						// do something special?
					}
					if (!res.prices || !res.prices.length) return;
					var name = location.name.substring(0,12);
					var estimate = '';
					res.prices.forEach(function(price) {
						if (Uber.products[productIndex].product_id == price.product_id) {
							estimate = price.estimate;
						}
					});
					appMessageQueue.send({type:TYPE.LOCATION, method:METHOD.DATA, index:index, name:name, estimate:estimate});
				});
			});
		}, function(err) { Uber.error('Failed to get geolocation!'); }, { timeout: 10000 });
	},

	refresh: function() {
		Uber.error('Trying to get Geolocation...');
		navigator.geolocation.getCurrentPosition(function(pos) {
			var latitude = pos.coords.latitude || 0;
			var longitude = pos.coords.longitude || 0;
			Uber.error('Requesting estimated pick up times...');
			if (!Uber.accessToken) {
				var url = 'https://ineal.me/pebble/ultra/api/estimates/time?latitude=' + latitude + '&longitude=' + longitude;
				var xhr = new XMLHttpRequest();
				xhr.open('GET', url, true);
				xhr.onload = function() {
					var res = JSON.parse(xhr.responseText);
					console.log(JSON.stringify(res));
					if (!res.times || !res.times.length) {
						return Uber.error('Uber is not yet available at this location or all cars are full at the moment. Please try again in a few minutes.');
					}
					Uber.products = res.times;
					Uber.sendProducts();
				};
				xhr.onerror = function() { Uber.error('Connection error!'); };
				xhr.ontimeout = function() { Uber.error('Connection to Uber API timed out!'); };
				xhr.timeout = 30000;
				xhr.send(null);
				return;
			}
			Uber.api.timeEstimates(latitude, longitude, function(xhr) {
				var res = JSON.parse(xhr.responseText);
				console.log(JSON.stringify(res));
				if (xhr.status === 401) {
					return Uber.refreshAccessToken(Uber.refresh);
				}
				if (!res.times) return;
				if (!res.times.length) {
					return Uber.checkProducts(latitude, longitude);
				}
				Uber.products = res.times;
				Uber.error('Checking for surge pricing...');
				Uber.api.priceEstimates(latitude, longitude, latitude, longitude, function(xhr2) {
					var res2 = JSON.parse(xhr2.responseText);
					console.log(JSON.stringify(res2));
					if (res2.prices && res2.prices.length > 0) {
						res2.prices.forEach(function(price) {
							for (var i = 0; i < Uber.products.length; i++) {
								if (Uber.products[i].product_id == price.product_id) {
									Uber.products[i].surge_multiplier = price.surge_multiplier;
								}
							}
						});
					}
					Uber.sendProducts();
				}, function(err) {
					Uber.sendProducts();
				});
			}, Uber.error);
		}, function(err) { Uber.error('Failed to get geolocation!'); }, { timeout: 10000 });
	},

	api: {
		products: function(latitude, longitude, cb, fb) {
			Uber.api.makeRequest('GET', '/v1/products', serialize({latitude:latitude, longitude:longitude}), cb, fb);
		},

		timeEstimates: function(latitude, longitude, cb, fb) {
			Uber.api.makeRequest('GET', '/v1/estimates/time', serialize({start_latitude:latitude, start_longitude:longitude}), cb, fb);
		},

		priceEstimates: function(start_lat, start_lng, end_lat, end_lng, cb, fb) {
			Uber.api.makeRequest('GET', '/v1/estimates/price', serialize({start_latitude:start_lat, start_longitude:start_lng, end_latitude:end_lat, end_longitude:end_lng}), cb, fb);
		},

		makeRequest: function(method, endpoint, data, cb, fb) {
			if (!Uber.accessToken) return Uber.error('Please log in to your Uber account on the Pebble mobile app.');
			var url = 'https://api.uber.com' + endpoint;
			if (method == 'GET' && data) {
				url += '?' + data;
				data = null;
			}
			console.log(method + ' ' + url + ' ' + data);
			var xhr = new XMLHttpRequest();
			xhr.setRequestHeader('Authorization', 'Bearer ' + Uber.accessToken);
			xhr.open(method, url, true);
			xhr.onload = function() { cb(xhr); };
			xhr.onerror = function() { fb('Connection error!'); };
			xhr.ontimeout = function() { fb('Connection to Uber API timed out!'); };
			xhr.timeout = 30000;
			xhr.send(data);
		}
	},

	handleAppMessage: function(e) {
		console.log('AppMessage received: ' + JSON.stringify(e.payload));
		if (!e.payload.method) return;
		switch (e.payload.method) {
			case METHOD.REFRESH:
				Uber.refresh();
				break;
			case METHOD.REQUESTLOCATIONS:
				Uber.sendLocations();
				Uber.getPriceEstimates(e.payload.index);
				break;
			case METHOD.REQUESTPRICE:
				Uber.getPriceEstimates(e.payload.index);
				break;
		}
	},

	refreshAccessToken: function(cb) {
		var xhr = new XMLHttpRequest();
		xhr.open('GET', 'https://ineal.me/pebble/ultra/api/refresh?token=' + Uber.refreshToken, true);
		xhr.onload = function() {
			var res = JSON.parse(xhr.responseText);
			console.log('new tokens: ' + JSON.stringify(res));
			if (res.access_token) {
				Uber.accessToken = res.access_token;
				localStorage.setItem('accessToken', Uber.accessToken);
			}
			if (res.refresh_token) {
				Uber.refreshToken = res.refresh_token;
				localStorage.setItem('refreshToken', Uber.refreshToken);
			}
			if (cb && typeof(cb) === 'function') {
				cb();
			}
		};
		xhr.send(null);
	},

	showConfiguration: function() {
		var data = {locations: Uber.locations};
		Pebble.openURL('https://ineal.me/pebble/ultra/configuration/?data=' + encodeURIComponent(JSON.stringify(data)));
	},

	handleConfiguration: function(e) {
		console.log('configuration received: ' + JSON.stringify(e));
		if (!e.response) return;
		var data = JSON.parse(decodeURIComponent(e.response));
		if (data.keys) {
			if (data.keys.access_token) {
				Uber.accessToken = data.keys.access_token;
				localStorage.setItem('accessToken', Uber.accessToken);
			}
			if (data.keys.refresh_token) {
				Uber.refreshToken = data.keys.refresh_token;
				localStorage.setItem('refreshToken', Uber.refreshToken);
			}
			Uber.refresh();
		}
		if (data.locations) {
			Uber.locations = data.locations;
			localStorage.setItem('locations', JSON.stringify(Uber.locations));
			Uber.sendLocations();
		}
	}
};

Pebble.addEventListener('ready', Uber.init);
Pebble.addEventListener('appmessage', Uber.handleAppMessage);
Pebble.addEventListener('showConfiguration', Uber.showConfiguration);
Pebble.addEventListener('webviewclosed', Uber.handleConfiguration);
<|endoftext|>