<commit_before>/* This file is part of Indico.
 * Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
 *
 * Indico 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.
 *
 * Indico 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 Indico; if not, see <http://www.gnu.org/licenses/>.
 */

(function(global, $) {
    'use strict';

    // Ajaxifies a link to open the target page (which needs to be using jsonify_template or
    // WPJinjaMixin.render_template) in a  modal dialog. Any forms on the page are ajaxified and should be using
    // redirect_or_jsonify() (when used with WPJinjaMixin) or jsonify_data (when used with jsonify_template)
    // in case of success (or just return a JSON response containing success=true and possibly flashed messages).
    // The link target MUST point to a page which is also valid when loaded directly in the browser since the
    // link could still be opened in a new tab manually. If you don't have a non-AJAX version, place the url in
    // data-href.
    $.fn.ajaxDialog = function jqAjaxDialog(options) {
        return this.on('click', function(e) {
            e.preventDefault();
            if ($(this).hasClass('disabled')) {
                return;
            }
            var href = $(this).attr('href');
            if (href === undefined || href == '#') {
                var data_href = $(this).data('href');
                href = data_href ? data_href : href;
            }
            ajaxDialog($.extend({}, options, {
                url: href,
                trigger: this
            }));
        });
    };

    // See the documentation of $.fn.ajaxDialog - use this function if you want to open an ajax-based dialog
    // manually instead of triggering it from a link using its href.
    global.ajaxDialog = function ajaxDialog(options) {
        options = $.extend({
            trigger: null, // element that opened the dialog
            title: null, // title of the dialog
            url: null, // url to get the form/dialog from
            method: 'GET', // http method to get the form/dialog
            data: null, // object or callable to add data when loading the form/dialog
            backSelector: '[data-button-back]', // elements in the form which will close the form
            clearFlashes: true, // clear existing flashed messages before showing new ones
            onClose: null, // callback to invoke after closing the dialog. first argument is null if closed manually,
                           // otherwise the JSON returned by the server. if it returns false, the dialog will remain
                           // open; if it returns a Deferred object, the dialog remain open until the object is resolved
            onOpen: null,  // callback to invoke after opening the dialog.
            getExtraData: function() {},  // callback to add data to the form. receives the <form> element as `this`
            confirmCloseUnsaved: false,  // ask the user to confirm closing the dialog with unsaved changes
            dialogClasses: '',  // extra classes to add to the dialog canvas
            hidePageHeader: false  // if the default page header (title/subtitle/description) should be hidden
        }, options);

        var confirmCloseMessage = $T('You have unsaved changes. Do you really want to close the dialog without saving?');
        var popup = null;
        var customData = null;
        var oldOnBeforeUnload = null;
        var ignoreOnBeforeUnload = false;

        $.ajax({
            type: options.method,
            url: options.url,
            data: $.isFunction(options.data) ? options.data() : options.data,
            cache: false, // IE caches GET AJAX requests. WTF.
            complete: IndicoUI.Dialogs.Util.progress(),
            error: handleAjaxError,
            success: function(data) {
                if (handleAjaxError(data)) {
                    return;
                }
                showDialog(data);
            }
        });

        function hasChangedFields() {
            var forms = popup.contentContainer.find('form');
            return forms.length && !!forms.filter(function() {
                return $(this).data('fieldsChanged');
            }).length;
        }

        function confirmClose() {
            return hasChangedFields() ? confirmPrompt(confirmCloseMessage, $T('Unsaved changes')) : $.Deferred().resolve();
        }

        function showDialog(dialogData) {
            popup = new ExclusivePopup($.isFunction(options.title) ? options.title() : options.title, function() {
                closeDialog(null);
                return false;
            }, false, false);

            popup.draw = function() {
                this.ExclusivePopup.prototype.draw.call(this, dialogData.html);
            };

            popup.postDraw = function() {
                ajaxifyForms();
                popup.canvas.on('ajaxDialog:setData', function(e, data) {
                    customData = data;
                });
                injectJS(dialogData.js);

                if (options.onOpen) {
                    options.onOpen(popup);
                }

                _.defer(function() {
                    popup.canvas.data('ui-dialog')._focusTabbable();
                });
            };

            var dialogClasses = _.union(['dialog-window', options.dialogClasses]);
            if (options.hidePageHeader) {
                dialogClasses.push('no-page-header');
            }
            popup.canvas.addClass(dialogClasses.join(' '));

            if (options.confirmCloseUnsaved) {
                oldOnBeforeUnload = window.onbeforeunload;
                window.onbeforeunload = function() {
                    if (popup.isopen && !ignoreOnBeforeUnload && hasChangedFields()) {
                        return confirmCloseMessage;
                    }
                };
            }

            popup.open();
        }

        function closeDialog(callbackData, submitted) {
            var confirmDeferred = (submitted || !options.confirmCloseUnsaved) ? $.Deferred().resolve() : confirmClose();
            confirmDeferred.then(function() {
                ignoreOnBeforeUnload = true;
                var onCloseResult = !options.onClose ? $.Deferred().resolve() : options.onClose(callbackData, customData);
                if (onCloseResult === false) {
                    ignoreOnBeforeUnload = false;
                    return;
                }
                else if (!onCloseResult || onCloseResult === true) {
                    onCloseResult = $.Deferred().resolve();
                }
                onCloseResult.then(function() {
                    popup.close();
                    if (options.confirmCloseUnsaved) {
                        window.onbeforeunload = oldOnBeforeUnload;
                    }
                }, function() {
                    ignoreOnBeforeUnload = false;
                });
            });
        }

        function ajaxifyForms() {
            var killProgress = null;
            var forms = popup.contentContainer.find('form');
            showFormErrors(popup.resultContainer);
            initForms(forms);
            forms.on('click', options.backSelector, function(e) {
                e.preventDefault();
                closeDialog();
            }).each(function() {
                var $this = $(this);
                $this.on('ajaxDialog:beforeSubmit', function() {
                    killProgress = IndicoUI.Dialogs.Util.progress();
                }).on('ajaxDialog:error', function(e, xhr) {
                    if (killProgress) {
                        killProgress();
                    }
                    closeDialog();
                    handleAjaxError(xhr);
                }).on('ajaxDialog:success', function(e, data) {
                    if (killProgress) {
                        killProgress();
                    }
                    if (handleAjaxError(data)) {
                        closeDialog();
                        return;
                    }

                    handleFlashes(data, options.clearFlashes, options.trigger ? $(options.trigger) : null);

                    if (data.close_dialog || data.success) {
                        closeDialog(data, true);
                    } else if (data.html) {
                        popup.contentContainer.html(data.html);
                        ajaxifyForms();
                        injectJS(data.js);
                    }
                });
                // We often use forms with an empty action; those need to go to
                // their page and not the page that loaded the dialog!
                var action = $this.attr('action') || options.url;
                $this.ajaxForm({
                    url: action,
                    dataType: 'json',
                    data: options.getExtraData.call(this, options.trigger),
                    traditional: true,
                    beforeSubmit: function() {
                        $this.trigger('ajaxDialog:beforeSubmit');
                    },
                    error: function(xhr) {
                        $this.trigger('ajaxDialog:error', [xhr]);
                    },
                    success: function(data) {
                        $this.trigger('ajaxDialog:success', [data]);
                    }
                });
            });
        }

        function injectJS(js) {
            if (js) {
                $('body').append(js)
            }
        }
    };
})(window, jQuery);
<commit_msg>Add ajaxDialog:close event<commit_after>/* This file is part of Indico.
 * Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
 *
 * Indico 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.
 *
 * Indico 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 Indico; if not, see <http://www.gnu.org/licenses/>.
 */

(function(global, $) {
    'use strict';

    // Ajaxifies a link to open the target page (which needs to be using jsonify_template or
    // WPJinjaMixin.render_template) in a  modal dialog. Any forms on the page are ajaxified and should be using
    // redirect_or_jsonify() (when used with WPJinjaMixin) or jsonify_data (when used with jsonify_template)
    // in case of success (or just return a JSON response containing success=true and possibly flashed messages).
    // The link target MUST point to a page which is also valid when loaded directly in the browser since the
    // link could still be opened in a new tab manually. If you don't have a non-AJAX version, place the url in
    // data-href.
    $.fn.ajaxDialog = function jqAjaxDialog(options) {
        return this.on('click', function(e) {
            e.preventDefault();
            if ($(this).hasClass('disabled')) {
                return;
            }
            var href = $(this).attr('href');
            if (href === undefined || href == '#') {
                var data_href = $(this).data('href');
                href = data_href ? data_href : href;
            }
            ajaxDialog($.extend({}, options, {
                url: href,
                trigger: this
            }));
        });
    };

    // See the documentation of $.fn.ajaxDialog - use this function if you want to open an ajax-based dialog
    // manually instead of triggering it from a link using its href.
    global.ajaxDialog = function ajaxDialog(options) {
        options = $.extend({
            trigger: null, // element that opened the dialog
            title: null, // title of the dialog
            url: null, // url to get the form/dialog from
            method: 'GET', // http method to get the form/dialog
            data: null, // object or callable to add data when loading the form/dialog
            backSelector: '[data-button-back]', // elements in the form which will close the form
            clearFlashes: true, // clear existing flashed messages before showing new ones
            onClose: null, // callback to invoke after closing the dialog. first argument is null if closed manually,
                           // otherwise the JSON returned by the server. if it returns false, the dialog will remain
                           // open; if it returns a Deferred object, the dialog remain open until the object is resolved
            onOpen: null,  // callback to invoke after opening the dialog.
            getExtraData: function() {},  // callback to add data to the form. receives the <form> element as `this`
            confirmCloseUnsaved: false,  // ask the user to confirm closing the dialog with unsaved changes
            dialogClasses: '',  // extra classes to add to the dialog canvas
            hidePageHeader: false  // if the default page header (title/subtitle/description) should be hidden
        }, options);

        var confirmCloseMessage = $T('You have unsaved changes. Do you really want to close the dialog without saving?');
        var popup = null;
        var customData = null;
        var oldOnBeforeUnload = null;
        var ignoreOnBeforeUnload = false;

        $.ajax({
            type: options.method,
            url: options.url,
            data: $.isFunction(options.data) ? options.data() : options.data,
            cache: false, // IE caches GET AJAX requests. WTF.
            complete: IndicoUI.Dialogs.Util.progress(),
            error: handleAjaxError,
            success: function(data) {
                if (handleAjaxError(data)) {
                    return;
                }
                showDialog(data);
            }
        });

        function hasChangedFields() {
            var forms = popup.contentContainer.find('form');
            return forms.length && !!forms.filter(function() {
                return $(this).data('fieldsChanged');
            }).length;
        }

        function confirmClose() {
            return hasChangedFields() ? confirmPrompt(confirmCloseMessage, $T('Unsaved changes')) : $.Deferred().resolve();
        }

        function showDialog(dialogData) {
            popup = new ExclusivePopup($.isFunction(options.title) ? options.title() : options.title, function() {
                closeDialog(null);
                return false;
            }, false, false);

            popup.draw = function() {
                this.ExclusivePopup.prototype.draw.call(this, dialogData.html);
            };

            popup.postDraw = function() {
                ajaxifyForms();
                popup.canvas.on('ajaxDialog:setData', function(e, data) {
                    customData = data;
                });
                popup.canvas.on('ajaxDialog:close', function(e, data, submitted) {
                    closeDialog(data, submitted);
                });
                injectJS(dialogData.js);

                if (options.onOpen) {
                    options.onOpen(popup);
                }

                _.defer(function() {
                    popup.canvas.data('ui-dialog')._focusTabbable();
                });
            };

            var dialogClasses = _.union(['dialog-window', options.dialogClasses]);
            if (options.hidePageHeader) {
                dialogClasses.push('no-page-header');
            }
            popup.canvas.addClass(dialogClasses.join(' '));

            if (options.confirmCloseUnsaved) {
                oldOnBeforeUnload = window.onbeforeunload;
                window.onbeforeunload = function() {
                    if (popup.isopen && !ignoreOnBeforeUnload && hasChangedFields()) {
                        return confirmCloseMessage;
                    }
                };
            }

            popup.open();
        }

        function closeDialog(callbackData, submitted) {
            var confirmDeferred = (submitted || !options.confirmCloseUnsaved) ? $.Deferred().resolve() : confirmClose();
            confirmDeferred.then(function() {
                ignoreOnBeforeUnload = true;
                var onCloseResult = !options.onClose ? $.Deferred().resolve() : options.onClose(callbackData, customData);
                if (onCloseResult === false) {
                    ignoreOnBeforeUnload = false;
                    return;
                }
                else if (!onCloseResult || onCloseResult === true) {
                    onCloseResult = $.Deferred().resolve();
                }
                onCloseResult.then(function() {
                    popup.close();
                    if (options.confirmCloseUnsaved) {
                        window.onbeforeunload = oldOnBeforeUnload;
                    }
                }, function() {
                    ignoreOnBeforeUnload = false;
                });
            });
        }

        function ajaxifyForms() {
            var killProgress = null;
            var forms = popup.contentContainer.find('form');
            showFormErrors(popup.resultContainer);
            initForms(forms);
            forms.on('click', options.backSelector, function(e) {
                e.preventDefault();
                closeDialog();
            }).each(function() {
                var $this = $(this);
                $this.on('ajaxDialog:beforeSubmit', function() {
                    killProgress = IndicoUI.Dialogs.Util.progress();
                }).on('ajaxDialog:error', function(e, xhr) {
                    if (killProgress) {
                        killProgress();
                    }
                    closeDialog();
                    handleAjaxError(xhr);
                }).on('ajaxDialog:success', function(e, data) {
                    if (killProgress) {
                        killProgress();
                    }
                    if (handleAjaxError(data)) {
                        closeDialog();
                        return;
                    }

                    handleFlashes(data, options.clearFlashes, options.trigger ? $(options.trigger) : null);

                    if (data.close_dialog || data.success) {
                        closeDialog(data, true);
                    } else if (data.html) {
                        popup.contentContainer.html(data.html);
                        ajaxifyForms();
                        injectJS(data.js);
                    }
                });
                // We often use forms with an empty action; those need to go to
                // their page and not the page that loaded the dialog!
                var action = $this.attr('action') || options.url;
                $this.ajaxForm({
                    url: action,
                    dataType: 'json',
                    data: options.getExtraData.call(this, options.trigger),
                    traditional: true,
                    beforeSubmit: function() {
                        $this.trigger('ajaxDialog:beforeSubmit');
                    },
                    error: function(xhr) {
                        $this.trigger('ajaxDialog:error', [xhr]);
                    },
                    success: function(data) {
                        $this.trigger('ajaxDialog:success', [data]);
                    }
                });
            });
        }

        function injectJS(js) {
            if (js) {
                $('body').append(js)
            }
        }
    };
})(window, jQuery);
<|endoftext|>


<commit_before>require('dotenv').config()

import { Router } from 'express'
import _ from 'lodash'
import logger from '../lib/logger'
import Service from '../lib/service'
import passport from 'passport'
import { Strategy } from 'passport-dropbox-oauth2'

passport.serializeUser(function (user, done) {
  done(null, user.id)
})
passport.deserializeUser(function (obj, done) {
  done(null, obj)
})
passport.use(new Strategy({
  clientID: process.env.DROPBOX_CLIENT_ID,
  clientSecret: process.env.DROPBOX_CLIENT_SECRET,
  callbackURL: process.env.DROPBOX_CLIENT_CALLBACK_URL,
  passReqToCallback: true
}, function (req, accessToken, refreshToken, profile, done) {
  process.nextTick(() => {
    let service = new Service()
    service.provider = 'dropbox'
    service.identity = profile.emails[0].value
    service.accountId = req.session.passport.user
    service.accessToken = accessToken
    //service.refreshToken = refreshToken // Doesn't seem to be defined by dropbox
    service.profile = profile
    service.save(err => {
      if(err) {
        logger.warn(err)
      }
      return done(null, profile)
    })
    return done(null, profile)
  })
}))

const router = Router()

router.get('/', passport.authenticate('dropbox-oauth2', {
  accessType: 'offline',
  approvalPrompt: 'force'
}))

router.get('/callback', (req, res, next) => {
  passport.authenticate('dropbox-oauth2', function(err, profile, info) {
    res.send('OK')
  })(req, res, next)
})

export default router

/* Sample ACCESSTOKEN FuZB-tXMAGAAAAAAAAAAWsUtLc_9Jouw96F2P0eQhVzNR5Dh-WM8vvdsMRazvy4i */

/* Sample REFRESHTOKEN undefined */

/* Sample PROFILE dataset
profile:  { provider: 'dropbox',
  id: 576540315,
  displayName: 'Adam Jones',
  name: { familyName: 'Jones', givenName: 'Adam', middleName: '' },
  emails: [ { value: 'a.jones@kollabria.com' } ],
  _raw: '{"referral_link": "https://db.tt/PXzZvSYD", "display_name": "Adam Jones", "uid": 576540315, "locale": "en", "email_verified": true, "team": null, "quota_info": {"datastores": 0, "shared": 0, "quota": 2147483648, "normal": 4498711}, "is_paired": false, "country": "US", "name_details": {"familiar_name": "Adam", "surname": "Jones", "given_name": "Adam"}, "email": "a.jones@kollabria.com"}',
  _json:
   { referral_link: 'https://db.tt/PXzZvSYD',
     display_name: 'Adam Jones',
     uid: 576540315,
     locale: 'en',
     email_verified: true,
     team: null,
     quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 4498711 },
     is_paired: false,
     country: 'US',
     name_details: { familiar_name: 'Adam', surname: 'Jones', given_name: 'Adam' },
     email: 'a.jones@kollabria.com' } }
 */
<commit_msg>Removed double callback<commit_after>require('dotenv').config()

import { Router } from 'express'
import _ from 'lodash'
import logger from '../lib/logger'
import Service from '../lib/service'
import passport from 'passport'
import { Strategy } from 'passport-dropbox-oauth2'

passport.serializeUser(function (user, done) {
  done(null, user.id)
})
passport.deserializeUser(function (obj, done) {
  done(null, obj)
})
passport.use(new Strategy({
  clientID: process.env.DROPBOX_CLIENT_ID,
  clientSecret: process.env.DROPBOX_CLIENT_SECRET,
  callbackURL: process.env.DROPBOX_CLIENT_CALLBACK_URL,
  passReqToCallback: true
}, function (req, accessToken, refreshToken, profile, done) {
  process.nextTick(() => {
    let service = new Service()
    service.provider = 'dropbox'
    service.identity = profile.emails[0].value
    service.accountId = req.session.passport.user
    service.accessToken = accessToken
    //service.refreshToken = refreshToken // Doesn't seem to be defined by dropbox
    service.profile = profile
    service.save(err => {
      if(err) {
        logger.warn(err)
      }
      return done(null, profile)
    })
  })
}))

const router = Router()

router.get('/', passport.authenticate('dropbox-oauth2', {
  accessType: 'offline',
  approvalPrompt: 'force'
}))

router.get('/callback', (req, res, next) => {
  passport.authenticate('dropbox-oauth2', function(err, profile, info) {
    res.send('OK')
  })(req, res, next)
})

export default router

/* Sample ACCESSTOKEN FuZB-tXMAGAAAAAAAAAAWsUtLc_9Jouw96F2P0eQhVzNR5Dh-WM8vvdsMRazvy4i */

/* Sample REFRESHTOKEN undefined */

/* Sample PROFILE dataset
profile:  { provider: 'dropbox',
  id: 576540315,
  displayName: 'Adam Jones',
  name: { familyName: 'Jones', givenName: 'Adam', middleName: '' },
  emails: [ { value: 'a.jones@kollabria.com' } ],
  _raw: '{"referral_link": "https://db.tt/PXzZvSYD", "display_name": "Adam Jones", "uid": 576540315, "locale": "en", "email_verified": true, "team": null, "quota_info": {"datastores": 0, "shared": 0, "quota": 2147483648, "normal": 4498711}, "is_paired": false, "country": "US", "name_details": {"familiar_name": "Adam", "surname": "Jones", "given_name": "Adam"}, "email": "a.jones@kollabria.com"}',
  _json:
   { referral_link: 'https://db.tt/PXzZvSYD',
     display_name: 'Adam Jones',
     uid: 576540315,
     locale: 'en',
     email_verified: true,
     team: null,
     quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 4498711 },
     is_paired: false,
     country: 'US',
     name_details: { familiar_name: 'Adam', surname: 'Jones', given_name: 'Adam' },
     email: 'a.jones@kollabria.com' } }
 */
<|endoftext|>


<commit_before>(function($){
  function escapeString (value) {
    return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  }

  $.declare('tokens',{
    defaults : {
      formatSuggestion : function(suggestion, value){
        var pattern = '(' + escapeString(value) + ')';
        return suggestion.replace(new RegExp(pattern, 'gi'), '$1<\/strong>');
      },
      search : function(suggestion, queryOriginal, queryLowerCase){
        return suggestion.toLowerCase().indexOf(queryLowerCase.toLowerCase()) !== -1;
      },
      query : function(query, callback){
        var queryLower = query.toLowerCase(), self = this;

        var suggestions = $.grep(this.source, function(suggestion){
          return self.search(suggestion, query, queryLower);
        });

        callback.apply(this,[suggestions]);
      },
      validate : function(query) {
        return true;
      },
      keyCode : {
        UP : 38,
        DOWN : 40,
        BACKSPACE : 8,
        TAB: 9,
        ENTER : 13,
        ESC : 27,
        COMMA : 188,
        SPACE: 32
      },
      texts : {
        'close-text' : '×',
        'type-suggestions' : 'Type to search values',
        'no-results' : 'There are no results matching',
        'add-result' : 'Add "%s" to the list',
        'invalid-format' : '%s is not the correct format',
        'existing-item' : '%s is already selected'
      },
      cssClasses : {
        'token-list' : 'tokens-token-list',
        'list-input-holder' : 'tokens-list-input-holder',
        'list-token-holder' : 'tokens-list-token-holder',
        'input-text' : 'tokens-input-text',
        'delete-anchor' : 'tokens-delete-token',
        'suggestion-selector' : 'tokens-suggestion-selector',
        'suggestions-list-element' : 'tokens-suggestions-list-element',
        'highlighted-suggestion' : 'tokens-highlighted-suggestion',
        'add-new-result' : 'tokens-add-new-result',
        'existing-item' : 'tokens-existing-item'
      },
      maxSelected : 0,
      showSuggestionOnFocus : true,
      showMessageOnNoResults : true,
      allowAddingNoSuggestion : true,
      cleanInputOnHide : true,
      suggestionsZindex : 999,
      source : [],
      initValue : [],
      minChars : 0
    },
    _getTarget : function(e){
      return $(e.currentTarget || e.toElement);
    },
    _setAttributes: function () {
      this.el.hide();
    },
    _createStructure: function () {
      this.list = $('
').addClass(this.cssClasses['token-list']);

      this.listInputHolder = $('
')
          .addClass(this.cssClasses['list-input-holder'])
          .appendTo(this.list);

      this.inputText = $('<input type="text">')
          .attr('autocomplete','off')
          .attr('autocapitalize', 'off')
          .addClass(this.cssClasses['input-text'])
          .appendTo(this.listInputHolder);

      this.list.insertBefore(this.el);
      this._createTester();
      this._createSuggestionsStructure();
    },
    _createSuggestionsStructure : function(){
      this.suggestionsHolder = $('
')
          .addClass(this.cssClasses['suggestion-selector'])
          .css({
            position : 'absolute',
            'z-index' : this.suggestionsZindex
          })
          .hide()
          .appendTo($('body'));
    },
    _createTester : function(){
      this.inputResizer = $('<tester>').css({
        position: 'absolute',
        top : -9999,
        left : -9999,
        width : 'auto',
        'font-size' : this.inputText.css('font-size'),
        'font-family' : this.inputText.css('font-family'),
        'font-weight' : this.inputText.css('font-weight'),
        'letter-spacing' : this.inputText.css('letter-spacing'),
        whitespace : 'nowrap'
      }).insertAfter(this.inputText);
    },
    _focusInput: function () {
      var self = this;
      setTimeout(function(){
        self.inputText.focus();
      },10);
    },
    _onDeleteClick: function (e) {
      e.stopImmediatePropagation();
      this._removeNode(this._getTarget(e).parent());
    },
    _onListClick : function(){
      this._focusInput();
      this._showTypeSuggestion();
    },
    _onKeyDown : function(event){
      switch(event.keyCode){
        case this.keyCode.UP :
          this._prevSuggestion();
          break;
        case this.keyCode.DOWN :
          this._nextSuggestion();
          break;
        case this.keyCode.ESC :
          this.el.val(this.currentValue);
          this._hideSuggestions();
          break;
        case this.keyCode.TAB:
        case this.keyCode.ENTER:
        case this.keyCode.COMMA:
        case this.keyCode.SPACE:
          this._selectSuggestion();
          break;
        case this.keyCode.BACKSPACE:
          this._deleteLastIfEmpty();
          return;
        default:
          return;
      }

      event.stopImmediatePropagation();
      event.preventDefault();
    },
    _onKeyUp : function(event){
      switch(event.keyCode){
        case this.keyCode.UP :
        case this.keyCode.DOWN :
          return;
      }
      this._suggestionChanged();
    },
    _onMouseOver : function (event) {
      var target = this._getTarget(event);
      this._activateSuggestion(target.data('index'));
    },
    _suggestionChanged : function(){
      var value = this.inputText.val();

      if (value !== this.suggestionValue){
        this.inputText.val(value);
        this.suggestionValue = value;
        this.selectedSuggestion = -1;

        if (value.length > this.minChars){
          this._updateSuggestions();
        }
      }
    },
    _deleteLastIfEmpty : function(){
      if (this.inputText.val() === ''){
        this._deleteLastToken();
      }
    },
    _deleteLastToken : function(){
      var tokens = this.currentValue.slice(0),
          lastToken = tokens.pop();

      this.removeValue(lastToken);
    },
    _nextSuggestion : function(){
      if (this.selectedSuggestion !== (this.suggestions.length -1)){
        this._adjustPosition(this.selectedSuggestion + 1);
      }
    },
    _prevSuggestion : function(){
      if (this.selectedSuggestion !== -1){
        this._adjustPosition(this.selectedSuggestion -1);
      }
    },
    _updateSuggestions : function(){
      var self = this;

      this.query.call(self, self.suggestionValue, function(suggestions){
        var len = suggestions.length;

        if (suggestions && $.isArray(suggestions) && len){
          var html = $('
');
          this.suggestions = suggestions;

          for (var i = 0; i < len; i++){
            html.append(
              $('
').
              addClass(this.cssClasses['suggestions-list-element']).
              data('index',i).
              html(this.formatSuggestion(suggestions[i], this.suggestionValue))
            );
          }

          this.suggestionsHolder.empty().append(html);
          this._showSuggestions();
        }
        else if (this.showMessageOnNoResults) {
          if (!this.allowAddingNoSuggestion){
            if (this.source.length){
              this._addTextToSuggestions(this.texts['no-results']);
            }
          }
          else {
            this._addTextToSuggestions(this.texts['add-result'].replace('%s',this.suggestionValue), this.cssClasses['add-new-result']);
          }
        }
      });
    },
    _adjustPosition : function(index){
      var selectedSuggestion = this._activateSuggestion(index),
          selTop, upperLimit, lowerLimit, elementHeight;

      if (selectedSuggestion){
        selTop = selectedSuggestion.offset().top;
        upperLimit = this.suggestionsHolder.scrollTop();
        elementHeight = selectedSuggestion.outerHeight();
        lowerLimit = upperLimit - elementHeight;

        if (selTop < upperLimit){
          this.suggestionsHolder.scrollTop(selTop);
        }
        else if (selTop > lowerLimit) {
          this.suggestionsHolder.scrollTop(selTop - elementHeight);
        }

        this.inputText.val(this.suggestions[index]);
      }
    },
    _deactivateSuggestion : function(event){
      this._getTarget(event).removeClass(this.cssClasses['']);
      this.selectedSuggestion = -1;
    },
    _selectSuggestion : function(){
      if (this.suggestions.length && this.selectedSuggestion !== -1){
        if(this.addValue(this.suggestions[this.selectedSuggestion])) {
          this.cancelBlur = false;
          this._hideSuggestions();
          this.inputText.val('');
        } else {
          this._addTextToSuggestions(this.texts['existing-item'].replace('%s',this.suggestions[this.selectedSuggestion]), this.cssClasses['existing-item']);
          this.cancelBlur = true;
        }
      }
      else if (this.allowAddingNoSuggestion){
        var val = $.trim(this.inputText.val());
        var isValid = this.validate(val);

        if(typeof isValid === 'string') {
          val = isValid;
          isValid = true;
        }

        if (val.length >= this.minChars && isValid){
          if(this.addValue(val)) {
            this.cancelBlur = false;
            this._hideSuggestions();
            this.inputText.val('');
          } else {
            this._addTextToSuggestions(this.texts['existing-item'].replace('%s',this.suggestionValue), this.cssClasses['existing-item']);
            this.cancelBlur = true;
          }
        } else {
          this._addTextToSuggestions(this.texts['invalid-format'].replace('%s',this.suggestionValue), this.cssClasses['invalid-format']);
          this.cancelBlur = true;
        }
      }
    },
    _activateSuggestion : function(index){
      var cssClass = this.cssClasses['highlighted-suggestion'],
          list = this.suggestionsHolder.find('ul'),
          element = null;

      list.children('.' + cssClass).removeClass(cssClass);
      this.selectedSuggestion = index;

      if (index !== -1 && list.children().length > index){
        element =  $(list.children().get(index)).addClass(cssClass);
      }

      return element;
    },
    _resizeInput : function() {
      if (this.inputResizer.text() !== this.inputText.val()){
        this.inputResizer.html(escapeString(this.inputText.val()));
        this.inputText.width(this.inputResizer.width() + 30);
      }
    },
    _bindEvents: function () {
      this.list.on('click',this.proxy(this._onListClick,this));
      this.list.on('click', '.' + this.cssClasses['delete-anchor'], this.proxy(this._onDeleteClick,this));

      this.inputText.on('blur', this.proxy(this._hideSuggestions,this));
      this.inputText.on('keydown', this.proxy(this._onKeyDown,this));
      this.inputText.on('keyup', this.proxy(this._onKeyUp,this));
      this.inputText.on('blur keyup keydown', this.proxy(this._resizeInput,this));

      var listClass = '.' + this.cssClasses['suggestions-list-element'] + ', .' + this.cssClasses['add-new-result'];

      this.suggestionsHolder.on('mouseover', listClass, this.proxy(this._onMouseOver,this));
      this.suggestionsHolder.on('mouseout', listClass, this.proxy(this._deactivateSuggestion,this));
      this.suggestionsHolder.on('mousedown', listClass, this.proxy(this._selectSuggestion,this));
    },
    _getCloseAnchor: function () {
      return $('').text(this.texts['close-text']).addClass(this.cssClasses['delete-anchor']);
    },
    _updateValue : function() {
      this.el.val(this.currentValue.join(', '));
    },
    _getTextFromNode : function($node){
      return $node.find('p').text();
    },
    _getNodeFromText : function(text){
      var $node = null,
          self = this;

      this.list.find('.' + this.cssClasses['list-token-holder']).each(function(){
        if (self._getTextFromNode($(this)) === text){
          $node = $(this);
        }

        return $node === null;
      });

      return $node;
    },
    _removeNode : function($node, text){
      text = text || this._getTextFromNode($node);
      var index = this.currentValue.indexOf(text);
      if (index !== -1){
        $node.remove();
        this.currentValue.splice(index,1);
        this._updateValue();

        this.emit('remove', text);
      }
    },
    _addInitialValues : function(){
      for (var i = 0, len = this.initValue.length; i < len; i++) {
        this.addValue(this.initValue[i]);
      }
    },
    _isWithinMax : function(){
      return this.maxSelected === 0 || (this.currentValue.length < this.maxSelected);
    },
    _hasReachedMax : function() {
      return this.maxSelected !== 0 || (this.currentValue.length === this.maxSelected);
    },
    _getSuggestionPosition : function(){
      return {
        top : this.list.offset().top + this.list.outerHeight(),
        left : this.list.offset().left,
        width : this.list.width()
      };
    },
    _showTypeSuggestion : function(){
      if (this.showSuggestionOnFocus && !this.suggestions.length && this.source.length){
        this._addTextToSuggestions(this.texts['type-suggestions']);
      }
    },
    _addTextToSuggestions : function(text, classes){
      classes = classes || "";
      this.suggestionsHolder.html('
' + $('
').text(text).html() + '
');
      this._showSuggestions();
    },
    _showSuggestions : function(){
      this.suggestionsHolder.css(this._getSuggestionPosition()).show();
    },
    _hideSuggestions : function() {
      if(this.cancelBlur) {
        this.cancelBlur = false;
        return false;
      }

      this.suggestionValue = '';
      this.suggestions = [];
      this.suggestionsHolder.hide();

      if (this.cleanInputOnHide){
        this.inputText.val('');
      }
    },
    getValue : function() {
      return this.currentValue;
    },
    addValue: function (value) {
      var tmp = this.currentValue.join(',').toLowerCase().split(',');
      if (tmp.indexOf(value.toLowerCase()) === -1 && this._isWithinMax()){
        this.currentValue.push(value);
        var list = $('
').addClass(this.cssClasses['list-token-holder']),
            paragraph = $('
').text(value);

        paragraph.appendTo(list);
        this._getCloseAnchor().appendTo(list);
        list.insertBefore(this.listInputHolder);
        this._updateValue();

        this.emit('add', value);

        if (this._hasReachedMax()){
          this.emit('max', value);
        }
        return true;
      } else {
        return false;
      }
     },
    removeValue: function (value) {
      this._removeNode(this._getNodeFromText(value),value);

      return this.el;
    },
    init : function(){
      this.currentValue = [];
      this.suggestions = [];
      this.suggestionValue = '';
      this.selectedSuggestion = -1;

      this._setAttributes();
      this._createStructure();
      this._bindEvents();
      this._addInitialValues();
    }
  });
})(jQuery);
<commit_msg>Small fix for html being rendered in tester element<commit_after>(function($){
  function escapeString (value) {
    return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  }

  $.declare('tokens',{
    defaults : {
      formatSuggestion : function(suggestion, value){
        var pattern = '(' + escapeString(value) + ')';
        return suggestion.replace(new RegExp(pattern, 'gi'), '$1<\/strong>');
      },
      search : function(suggestion, queryOriginal, queryLowerCase){
        return suggestion.toLowerCase().indexOf(queryLowerCase.toLowerCase()) !== -1;
      },
      query : function(query, callback){
        var queryLower = query.toLowerCase(), self = this;

        var suggestions = $.grep(this.source, function(suggestion){
          return self.search(suggestion, query, queryLower);
        });

        callback.apply(this,[suggestions]);
      },
      validate : function(query) {
        return true;
      },
      keyCode : {
        UP : 38,
        DOWN : 40,
        BACKSPACE : 8,
        TAB: 9,
        ENTER : 13,
        ESC : 27,
        COMMA : 188,
        SPACE: 32
      },
      texts : {
        'close-text' : '×',
        'type-suggestions' : 'Type to search values',
        'no-results' : 'There are no results matching',
        'add-result' : 'Add "%s" to the list',
        'invalid-format' : '%s is not the correct format',
        'existing-item' : '%s is already selected'
      },
      cssClasses : {
        'token-list' : 'tokens-token-list',
        'list-input-holder' : 'tokens-list-input-holder',
        'list-token-holder' : 'tokens-list-token-holder',
        'input-text' : 'tokens-input-text',
        'delete-anchor' : 'tokens-delete-token',
        'suggestion-selector' : 'tokens-suggestion-selector',
        'suggestions-list-element' : 'tokens-suggestions-list-element',
        'highlighted-suggestion' : 'tokens-highlighted-suggestion',
        'add-new-result' : 'tokens-add-new-result',
        'existing-item' : 'tokens-existing-item'
      },
      maxSelected : 0,
      showSuggestionOnFocus : true,
      showMessageOnNoResults : true,
      allowAddingNoSuggestion : true,
      cleanInputOnHide : true,
      suggestionsZindex : 999,
      source : [],
      initValue : [],
      minChars : 0
    },
    _getTarget : function(e){
      return $(e.currentTarget || e.toElement);
    },
    _setAttributes: function () {
      this.el.hide();
    },
    _createStructure: function () {
      this.list = $('
').addClass(this.cssClasses['token-list']);

      this.listInputHolder = $('
')
          .addClass(this.cssClasses['list-input-holder'])
          .appendTo(this.list);

      this.inputText = $('<input type="text">')
          .attr('autocomplete','off')
          .attr('autocapitalize', 'off')
          .addClass(this.cssClasses['input-text'])
          .appendTo(this.listInputHolder);

      this.list.insertBefore(this.el);
      this._createTester();
      this._createSuggestionsStructure();
    },
    _createSuggestionsStructure : function(){
      this.suggestionsHolder = $('
')
          .addClass(this.cssClasses['suggestion-selector'])
          .css({
            position : 'absolute',
            'z-index' : this.suggestionsZindex
          })
          .hide()
          .appendTo($('body'));
    },
    _createTester : function(){
      this.inputResizer = $('<tester>').css({
        position: 'absolute',
        top : -9999,
        left : -9999,
        width : 'auto',
        'font-size' : this.inputText.css('font-size'),
        'font-family' : this.inputText.css('font-family'),
        'font-weight' : this.inputText.css('font-weight'),
        'letter-spacing' : this.inputText.css('letter-spacing'),
        whitespace : 'nowrap'
      }).insertAfter(this.inputText);
    },
    _focusInput: function () {
      var self = this;
      setTimeout(function(){
        self.inputText.focus();
      },10);
    },
    _onDeleteClick: function (e) {
      e.stopImmediatePropagation();
      this._removeNode(this._getTarget(e).parent());
    },
    _onListClick : function(){
      this._focusInput();
      this._showTypeSuggestion();
    },
    _onKeyDown : function(event){
      switch(event.keyCode){
        case this.keyCode.UP :
          this._prevSuggestion();
          break;
        case this.keyCode.DOWN :
          this._nextSuggestion();
          break;
        case this.keyCode.ESC :
          this.el.val(this.currentValue);
          this._hideSuggestions();
          break;
        case this.keyCode.TAB:
        case this.keyCode.ENTER:
        case this.keyCode.COMMA:
        case this.keyCode.SPACE:
          this._selectSuggestion();
          break;
        case this.keyCode.BACKSPACE:
          this._deleteLastIfEmpty();
          return;
        default:
          return;
      }

      event.stopImmediatePropagation();
      event.preventDefault();
    },
    _onKeyUp : function(event){
      switch(event.keyCode){
        case this.keyCode.UP :
        case this.keyCode.DOWN :
          return;
      }
      this._suggestionChanged();
    },
    _onMouseOver : function (event) {
      var target = this._getTarget(event);
      this._activateSuggestion(target.data('index'));
    },
    _suggestionChanged : function(){
      var value = this.inputText.val();

      if (value !== this.suggestionValue){
        this.inputText.val(value);
        this.suggestionValue = value;
        this.selectedSuggestion = -1;

        if (value.length > this.minChars){
          this._updateSuggestions();
        }
      }
    },
    _deleteLastIfEmpty : function(){
      if (this.inputText.val() === ''){
        this._deleteLastToken();
      }
    },
    _deleteLastToken : function(){
      var tokens = this.currentValue.slice(0),
          lastToken = tokens.pop();

      this.removeValue(lastToken);
    },
    _nextSuggestion : function(){
      if (this.selectedSuggestion !== (this.suggestions.length -1)){
        this._adjustPosition(this.selectedSuggestion + 1);
      }
    },
    _prevSuggestion : function(){
      if (this.selectedSuggestion !== -1){
        this._adjustPosition(this.selectedSuggestion -1);
      }
    },
    _updateSuggestions : function(){
      var self = this;

      this.query.call(self, self.suggestionValue, function(suggestions){
        var len = suggestions.length;

        if (suggestions && $.isArray(suggestions) && len){
          var html = $('
');
          this.suggestions = suggestions;

          for (var i = 0; i < len; i++){
            html.append(
              $('
').
              addClass(this.cssClasses['suggestions-list-element']).
              data('index',i).
              html(this.formatSuggestion(suggestions[i], this.suggestionValue))
            );
          }

          this.suggestionsHolder.empty().append(html);
          this._showSuggestions();
        }
        else if (this.showMessageOnNoResults) {
          if (!this.allowAddingNoSuggestion){
            if (this.source.length){
              this._addTextToSuggestions(this.texts['no-results']);
            }
          }
          else {
            this._addTextToSuggestions(this.texts['add-result'].replace('%s',this.suggestionValue), this.cssClasses['add-new-result']);
          }
        }
      });
    },
    _adjustPosition : function(index){
      var selectedSuggestion = this._activateSuggestion(index),
          selTop, upperLimit, lowerLimit, elementHeight;

      if (selectedSuggestion){
        selTop = selectedSuggestion.offset().top;
        upperLimit = this.suggestionsHolder.scrollTop();
        elementHeight = selectedSuggestion.outerHeight();
        lowerLimit = upperLimit - elementHeight;

        if (selTop < upperLimit){
          this.suggestionsHolder.scrollTop(selTop);
        }
        else if (selTop > lowerLimit) {
          this.suggestionsHolder.scrollTop(selTop - elementHeight);
        }

        this.inputText.val(this.suggestions[index]);
      }
    },
    _deactivateSuggestion : function(event){
      this._getTarget(event).removeClass(this.cssClasses['']);
      this.selectedSuggestion = -1;
    },
    _selectSuggestion : function(){
      if (this.suggestions.length && this.selectedSuggestion !== -1){
        if(this.addValue(this.suggestions[this.selectedSuggestion])) {
          this.cancelBlur = false;
          this._hideSuggestions();
          this.inputText.val('');
        } else {
          this._addTextToSuggestions(this.texts['existing-item'].replace('%s',this.suggestions[this.selectedSuggestion]), this.cssClasses['existing-item']);
          this.cancelBlur = true;
        }
      }
      else if (this.allowAddingNoSuggestion){
        var val = $.trim(this.inputText.val());
        var isValid = this.validate(val);

        if(typeof isValid === 'string') {
          val = isValid;
          isValid = true;
        }

        if (val.length >= this.minChars && isValid){
          if(this.addValue(val)) {
            this.cancelBlur = false;
            this._hideSuggestions();
            this.inputText.val('');
          } else {
            this._addTextToSuggestions(this.texts['existing-item'].replace('%s',this.suggestionValue), this.cssClasses['existing-item']);
            this.cancelBlur = true;
          }
        } else {
          this._addTextToSuggestions(this.texts['invalid-format'].replace('%s',this.suggestionValue), this.cssClasses['invalid-format']);
          this.cancelBlur = true;
        }
      }
    },
    _activateSuggestion : function(index){
      var cssClass = this.cssClasses['highlighted-suggestion'],
          list = this.suggestionsHolder.find('ul'),
          element = null;

      list.children('.' + cssClass).removeClass(cssClass);
      this.selectedSuggestion = index;

      if (index !== -1 && list.children().length > index){
        element =  $(list.children().get(index)).addClass(cssClass);
      }

      return element;
    },
    _resizeInput : function() {
      if (this.inputResizer.text() !== this.inputText.val()){
        this.inputResizer.html($('
').text(this.inputText.val()).html());
        this.inputText.width(this.inputResizer.width() + 30);
      }
    },
    _bindEvents: function () {
      this.list.on('click',this.proxy(this._onListClick,this));
      this.list.on('click', '.' + this.cssClasses['delete-anchor'], this.proxy(this._onDeleteClick,this));

      this.inputText.on('blur', this.proxy(this._hideSuggestions,this));
      this.inputText.on('keydown', this.proxy(this._onKeyDown,this));
      this.inputText.on('keyup', this.proxy(this._onKeyUp,this));
      this.inputText.on('blur keyup keydown', this.proxy(this._resizeInput,this));

      var listClass = '.' + this.cssClasses['suggestions-list-element'] + ', .' + this.cssClasses['add-new-result'];

      this.suggestionsHolder.on('mouseover', listClass, this.proxy(this._onMouseOver,this));
      this.suggestionsHolder.on('mouseout', listClass, this.proxy(this._deactivateSuggestion,this));
      this.suggestionsHolder.on('mousedown', listClass, this.proxy(this._selectSuggestion,this));
    },
    _getCloseAnchor: function () {
      return $('').text(this.texts['close-text']).addClass(this.cssClasses['delete-anchor']);
    },
    _updateValue : function() {
      this.el.val(this.currentValue.join(', '));
    },
    _getTextFromNode : function($node){
      return $node.find('p').text();
    },
    _getNodeFromText : function(text){
      var $node = null,
          self = this;

      this.list.find('.' + this.cssClasses['list-token-holder']).each(function(){
        if (self._getTextFromNode($(this)) === text){
          $node = $(this);
        }

        return $node === null;
      });

      return $node;
    },
    _removeNode : function($node, text){
      text = text || this._getTextFromNode($node);
      var index = this.currentValue.indexOf(text);
      if (index !== -1){
        $node.remove();
        this.currentValue.splice(index,1);
        this._updateValue();

        this.emit('remove', text);
      }
    },
    _addInitialValues : function(){
      for (var i = 0, len = this.initValue.length; i < len; i++) {
        this.addValue(this.initValue[i]);
      }
    },
    _isWithinMax : function(){
      return this.maxSelected === 0 || (this.currentValue.length < this.maxSelected);
    },
    _hasReachedMax : function() {
      return this.maxSelected !== 0 || (this.currentValue.length === this.maxSelected);
    },
    _getSuggestionPosition : function(){
      return {
        top : this.list.offset().top + this.list.outerHeight(),
        left : this.list.offset().left,
        width : this.list.width()
      };
    },
    _showTypeSuggestion : function(){
      if (this.showSuggestionOnFocus && !this.suggestions.length && this.source.length){
        this._addTextToSuggestions(this.texts['type-suggestions']);
      }
    },
    _addTextToSuggestions : function(text, classes){
      classes = classes || "";
      this.suggestionsHolder.html('
' + $('
').text(text).html() + '
');
      this._showSuggestions();
    },
    _showSuggestions : function(){
      this.suggestionsHolder.css(this._getSuggestionPosition()).show();
    },
    _hideSuggestions : function() {
      if(this.cancelBlur) {
        this.cancelBlur = false;
        return false;
      }

      this.suggestionValue = '';
      this.suggestions = [];
      this.suggestionsHolder.hide();

      if (this.cleanInputOnHide){
        this.inputText.val('');
      }
    },
    getValue : function() {
      return this.currentValue;
    },
    addValue: function (value) {
      var tmp = this.currentValue.join(',').toLowerCase().split(',');
      if (tmp.indexOf(value.toLowerCase()) === -1 && this._isWithinMax()){
        this.currentValue.push(value);
        var list = $('
').addClass(this.cssClasses['list-token-holder']),
            paragraph = $('
').text(value);

        paragraph.appendTo(list);
        this._getCloseAnchor().appendTo(list);
        list.insertBefore(this.listInputHolder);
        this._updateValue();

        this.emit('add', value);

        if (this._hasReachedMax()){
          this.emit('max', value);
        }
        return true;
      } else {
        return false;
      }
     },
    removeValue: function (value) {
      this._removeNode(this._getNodeFromText(value),value);

      return this.el;
    },
    init : function(){
      this.currentValue = [];
      this.suggestions = [];
      this.suggestionValue = '';
      this.selectedSuggestion = -1;

      this._setAttributes();
      this._createStructure();
      this._bindEvents();
      this._addInitialValues();
    }
  });
})(jQuery);
<|endoftext|>


<commit_before>'use strict';

var DEBUG = true;  // when true, orders are just validated by Kraken, not fulfilled
var SATOSHI_FACTOR = Math.pow(10,8);

// Translating currency code for Kraken
var currencies = {'USD' : 'ZUSD',
		  'EUR' : 'ZEUR',
		  'BTC' : 'XXBT'
		 };

var KrakenClient = require('kraken-api');

var KrakenTrade = function(config) {
    this.client = new KrakenClient(config.key, config.secret);
    if (config.currency != 'EUR' || config.currency != 'USD') {
	// need to raise error, can trade only EUR
    }
    this._currency = config.currency;
};

KrakenTrade.factory = function factory(config) {
    return new KrakenTrade(config);
};

// Public functions

KrakenTrade.prototype.balance = function balance(callback) {
    var kcurrency = currencies[this._currency];
    this.client.api('Balance', null, function(error, response) {
	if (error) {
	    return callback(error);
	}
	else {
	    var balances = response.result;
	    var eur_balance = 0.0;
	    if (balances[kcurrency]) {
		try {
		    eur_balance = parseFloat(balances[kcurrency], 10)
		} catch(e) {
		    return callback("Can't parse balance information!");
		}
	    }
	    return callback(null, eur_balance);
	}
    });
};

KrakenTrade.prototype.currency = function currency() {
    return this._currency;
};

KrakenTrade.prototype.purchase = function purchase(satoshis, currentPrice, callback) {
    var bitcoins = satoshis / SATOSHI_FACTOR;
    var amountStr = bitcoins.toFixed(8);

    // Buy at market price, when validate is true only check but do not fulfill
    var orderInfo = { 'pair' : 'XXBT'+currencies[this._currency],
		      'type' : 'buy',
		      'ordertype' : 'market',
		      'volume': bitcoins,
		      'validate' : DEBUG
		    };
    this.client.api('AddOrder', orderInfo, function(error, response) {
	if (error) {
	    return callback(error);
	}
	else {
	    if (DEBUG) {
		console.log(response.result);
	    }
	    return callback();
	}
    });
};

module.exports = KrakenTrade;
<commit_msg>left in a debug flag, take it out<commit_after>'use strict';

var DEBUG = false;  // when true, orders are just validated by Kraken, not fulfilled
var SATOSHI_FACTOR = Math.pow(10,8);

// Translating currency code for Kraken
var currencies = {'USD' : 'ZUSD',
		  'EUR' : 'ZEUR',
		  'BTC' : 'XXBT'
		 };

var KrakenClient = require('kraken-api');

var KrakenTrade = function(config) {
    this.client = new KrakenClient(config.key, config.secret);
    if (config.currency != 'EUR' || config.currency != 'USD') {
	// need to raise error, can trade only EUR
    }
    this._currency = config.currency;
};

KrakenTrade.factory = function factory(config) {
    return new KrakenTrade(config);
};

// Public functions

KrakenTrade.prototype.balance = function balance(callback) {
    var kcurrency = currencies[this._currency];
    this.client.api('Balance', null, function(error, response) {
	if (error) {
	    return callback(error);
	}
	else {
	    var balances = response.result;
	    var eur_balance = 0.0;
	    if (balances[kcurrency]) {
		try {
		    eur_balance = parseFloat(balances[kcurrency], 10)
		} catch(e) {
		    return callback("Can't parse balance information!");
		}
	    }
	    return callback(null, eur_balance);
	}
    });
};

KrakenTrade.prototype.currency = function currency() {
    return this._currency;
};

KrakenTrade.prototype.purchase = function purchase(satoshis, currentPrice, callback) {
    var bitcoins = satoshis / SATOSHI_FACTOR;
    var amountStr = bitcoins.toFixed(8);

    // Buy at market price, when validate is true only check but do not fulfill
    var orderInfo = { 'pair' : 'XXBT'+currencies[this._currency],
		      'type' : 'buy',
		      'ordertype' : 'market',
		      'volume': bitcoins,
		      'validate' : DEBUG
		    };
    this.client.api('AddOrder', orderInfo, function(error, response) {
	if (error) {
	    return callback(error);
	}
	else {
	    if (DEBUG) {
		console.log(response.result);
	    }
	    return callback();
	}
    });
};

module.exports = KrakenTrade;
<|endoftext|>


<commit_before>import { emailAddressRegExpTest } from '../helpers/email';

export class ResponsibleExtractor {

    constructor(string, acceptOnlyValidEmailAsFreeText = false) {
        this.string = string;
        this.extractedResponsible = [];
        this.acceptOnlyValidEmailAsFreeText = acceptOnlyValidEmailAsFreeText;

        this._extractResponsible();
    }

    getExtractedResponsible() {
        return this.extractedResponsible;
    }

    getCleanedString() {
        return this.string;
    }

    _extractResponsible() {
        const regEx = new RegExp(/(^|[\s.,;])@([a-zA-z]+[^\s,;]*)/g);
        const subjectString = this.string;
        let match;

        while ((match = regEx.exec(subjectString)) !== null) {
            const possibleResponsibleName = match[2];
            const added = this._addResponsible(possibleResponsibleName);
            if (added) {
                this._removeResponsibleFromString(possibleResponsibleName);
            }
        }
    }

    _addResponsible(responsibleName) {
        let user = Meteor.users.findOne({username: responsibleName});
        if (user) {
            this.extractedResponsible.push(user.username);
            return true;
        }
        if (this._isValidResonsible(responsibleName)) {
            this.extractedResponsible.push(responsibleName);
            return true;
        }
        return false;
    }

    _isValidResonsible(possibleResponsibleName) {
        return (!this.acceptOnlyValidEmailAsFreeText || emailAddressRegExpTest.test(possibleResponsibleName));
    }

    _removeResponsibleFromString(responsibleName) {
        this.string = this.string.replace('@' + responsibleName + ' ', '');
        this.string = this.string.replace(' @' + responsibleName, '');
        this.string = this.string.replace('@' + responsibleName, '');
    }

}<commit_msg>Convert username to userId when extracting responsible from item subject<commit_after>import { emailAddressRegExpTest } from '../helpers/email';

export class ResponsibleExtractor {

    constructor(string, acceptOnlyValidEmailAsFreeText = false) {
        this.string = string;
        this.extractedResponsible = [];
        this.acceptOnlyValidEmailAsFreeText = acceptOnlyValidEmailAsFreeText;

        this._extractResponsible();
    }

    getExtractedResponsible() {
        return this.extractedResponsible;
    }

    getCleanedString() {
        return this.string;
    }

    _extractResponsible() {
        const regEx = new RegExp(/(^|[\s.,;])@([a-zA-z]+[^\s,;]*)/g);
        const subjectString = this.string;
        let match;

        while ((match = regEx.exec(subjectString)) !== null) {
            const possibleResponsibleName = match[2];
            const added = this._addResponsible(possibleResponsibleName);
            if (added) {
                this._removeResponsibleFromString(possibleResponsibleName);
            }
        }
    }

    _addResponsible(responsibleName) {
        let user = Meteor.users.findOne({username: responsibleName});
        if (user) {
            this.extractedResponsible.push(user._id);
            return true;
        }
        if (this._isValidResonsible(responsibleName)) {
            this.extractedResponsible.push(responsibleName);
            return true;
        }
        return false;
    }

    _isValidResonsible(possibleResponsibleName) {
        return (!this.acceptOnlyValidEmailAsFreeText || emailAddressRegExpTest.test(possibleResponsibleName));
    }

    _removeResponsibleFromString(responsibleName) {
        this.string = this.string.replace('@' + responsibleName + ' ', '');
        this.string = this.string.replace(' @' + responsibleName, '');
        this.string = this.string.replace('@' + responsibleName, '');
    }

}<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var async = require('async');
var jsonquest = require('./jsonquest');

var _t0 = null;
var sequenceNumber = 0;

var Trader = function(config) {
  if (!(this instanceof Trader)) return new Trader(config);
  EventEmitter.call(this);

  this.connectionInfo = null;
  this.protocol = config.protocol || 'https';
  this.rejectUnauthorized = typeof config.rejectUnauthorized === 'undefined' ?
    true :
    !!config.rejectUnauthorized;

  this.config = config;
  this.exchangeRate = null;
  this.balance = null;
  this.locale = null;

  this.exchange = null;
  this.balanceTimer = null;
  this.balanceRetries = 0;
  this.balanceTriggers = null;
  this.tickerExchange = null;
  this.transferExchange = null;
  this.pollTimer = null;
  this.pollRetries = 0;
  this.txLimit = null;
  this.idVerificationLimit = null;
  this.idVerificationEnabled = false;
};
util.inherits(Trader, EventEmitter);

Trader.prototype.init = function init(connectionInfo) {
  if (this.protocol === 'https') {
    this.cert = fs.readFileSync(this.config.certs.certFile);
    this.key = fs.readFileSync(this.config.certs.keyFile);
  }
  this.connectionInfo = connectionInfo;

  jsonquest.setEmitter(this.emit);
};

Trader.prototype.pair = function pair(connectionInfo) {
  this.connectionInfo = connectionInfo;
};

Trader.prototype._request = function _request(options, cb) {
  var protocol = this.protocol || 'https';
  var connectionInfo = this.connectionInfo;
  var self = this;

  jsonquest({
    protocol: protocol,
    host: connectionInfo.host,
    port: connectionInfo.port,
    cert: this.cert,
    key: this.key,
    rejectUnauthorized: this.rejectUnauthorized,
    method: options.method,
    path: options.path,
    body: options.body,
    repeatUntilSuccess: options.repeatUntilSuccess
  }, function (err, res, body) {
    if (err) return cb(_richError(err.message, 'networkDown'));

    if (protocol === 'https') {
      var fingerprint = res.socket.getPeerCertificate().fingerprint;
      if (fingerprint !== connectionInfo.fingerprint)
        return cb(_richError('Security Error: Unauthorized server certificate!'));
    }

    if (res.statusCode === 404) {
      self.connectionInfo = null;
      return cb(_richError('Server has unpaired', 'unpair'));
    }

    // All 2xx codes are OK
    if (res.statusCode < 200 || res.statusCode >= 300)
      return cb(_richError('Server returned ' + res.statusCode + ': ' + body.err));

    var errRec = body.err ? {name: body.errType, message: body.err} : null;
    var result = body;

    if (errRec) return cb(_richError(errRec.message, errRec.name));
    cb(null, result);
  });
};

Trader.prototype.run = function run() {
  var self = this;

  self.trigger();
  self.triggerInterval = setInterval(function() {
    self.trigger();
  }, this.config.settings.pollInterval);
};

Trader.prototype.stop = function stop() {
  if (this.triggerInterval) clearInterval(this.triggerInterval);
};

Trader.prototype.verifyUser = function verifyUser(idRec, cb) {
  console.log(idRec);
  this._request({
    path: '/verify_user',
    method: 'POST',
    body: idRec
  }, cb);
};

Trader.prototype.verifyTransaction = function verifyTransaction(idRec) {
  console.log(idRec);
  this._request({
    path: '/verify_transaction',
    method: 'POST',
    body: idRec
  }, function(err) {
    if (err) console.log(err);
  });
};

Trader.prototype.reportEvent = function reportEvent(eventType, note) {
  var rec = {
    eventType: eventType,
    note: note,
    deviceTime: Date.now()
  };
  this._request({
    path: '/event',
    method: 'POST',
    body: rec,
    repeatUntilSuccess: true
  }, function (err) {
    if (err) console.log(err);
  });
};

Trader.prototype.sendBitcoins = function sendBitcoins(tx, cb) {
  tx.sequenceNumber = ++sequenceNumber;
  this._request({
    path: '/send',
    method: 'POST',
    body: tx,
    repeatUntilSuccess: true
  }, function (err, result) {
    if (!err) cb(null, result.txId);
    else {
      if (err.name === 'InsufficientFunds') return cb(err);
      if (err.name === 'networkDown') return cb(new Error('sendBitcoins timeout'));
    }
  });

  sequenceNumber = 0;
};

Trader.prototype.trigger = function trigger() {
  var self = this;

  // Not paired yet
  if (this.connectionInfo === null) return;

  self._request({
    path: '/poll',
    method: 'GET'
  }, function (err, res) {
    self._pollHandler(err, res);
  });
};

// TODO: Keep trying, like sendBitcoins
Trader.prototype.trade = function trade(rec, cb) {
  rec.sequenceNumber = ++sequenceNumber;
  this._request({
    path: '/trade',
    method: 'POST',
    body: rec,
    repeatUntilSuccess: true
  }, cb);
};

// Private functions

Trader.prototype._pollHandler = function _pollHandler(err, res) {
  if (err && err.name === 'networkDown') {
    if (_t0 === null) {
      _t0 = Date.now();
      return;
    }

    if (Date.now() - _t0 > this.config.settings.pollTimeout) {
      _t0 = null;
      this.emit('networkDown');
      return;
    }
  }

  if (err && err.name === 'unpair') {
    this.emit('unpair');
    return;
  }

  _t0 = null;

  // Not a network error, so no need to keep trying
  if (err) {
    this.emit('networkDown');
    return;
  }

  this.txLimit = res.txLimit;
  this.idVerificationLimit = res.idVerificationLimit;
  this.idVerificationEnabled = res.idVerificationEnabled;
  this.exchangeRate = res.rate;
  this.balance = res.fiat;
  this.locale = res.locale;
  this.emit('pollUpdate');
  this.emit('networkUp');
};

function _richError(errMessage, name) {
  var err = new Error(errMessage);
  err.name = name;
  return err;
}

module.exports = Trader;
<commit_msg>fix(offline): /poll requests behave properly during offline tx<commit_after>'use strict';

var fs = require('fs');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var async = require('async');
var jsonquest = require('./jsonquest');

var _t0 = null;
var sequenceNumber = 0;

var Trader = function(config) {
  if (!(this instanceof Trader)) return new Trader(config);
  EventEmitter.call(this);

  this.connectionInfo = null;
  this.protocol = config.protocol || 'https';
  this.rejectUnauthorized = typeof config.rejectUnauthorized === 'undefined' ?
    true :
    !!config.rejectUnauthorized;

  this.config = config;
  this.exchangeRate = null;
  this.balance = null;
  this.locale = null;

  this.exchange = null;
  this.balanceTimer = null;
  this.balanceRetries = 0;
  this.balanceTriggers = null;
  this.tickerExchange = null;
  this.transferExchange = null;
  this.pollTimer = null;
  this.pollRetries = 0;
  this.txLimit = null;
  this.idVerificationLimit = null;
  this.idVerificationEnabled = false;
};
util.inherits(Trader, EventEmitter);

Trader.prototype.init = function init(connectionInfo) {
  if (this.protocol === 'https') {
    this.cert = fs.readFileSync(this.config.certs.certFile);
    this.key = fs.readFileSync(this.config.certs.keyFile);
  }
  this.connectionInfo = connectionInfo;

  jsonquest.setEmitter(this.emit);
};

Trader.prototype.pair = function pair(connectionInfo) {
  this.connectionInfo = connectionInfo;
};

Trader.prototype._request = function _request(options, cb) {
  var protocol = this.protocol || 'https';
  var connectionInfo = this.connectionInfo;
  var self = this;

  jsonquest({
    protocol: protocol,
    host: connectionInfo.host,
    port: connectionInfo.port,
    cert: this.cert,
    key: this.key,
    rejectUnauthorized: this.rejectUnauthorized,
    method: options.method,
    path: options.path,
    body: options.body,
    repeatUntilSuccess: options.repeatUntilSuccess
  }, function (err, res, body) {
    if (err) return cb(_richError(err.message, 'networkDown'));

    if (protocol === 'https') {
      var fingerprint = res.socket.getPeerCertificate().fingerprint;
      if (fingerprint !== connectionInfo.fingerprint)
        return cb(_richError('Security Error: Unauthorized server certificate!'));
    }

    if (res.statusCode === 404) {
      self.connectionInfo = null;
      return cb(_richError('Server has unpaired', 'unpair'));
    }

    // All 2xx codes are OK
    if (res.statusCode < 200 || res.statusCode >= 300)
      return cb(_richError('Server returned ' + res.statusCode + ': ' + body.err));

    var errRec = body.err ? {name: body.errType, message: body.err} : null;
    var result = body;

    if (errRec) return cb(_richError(errRec.message, errRec.name));
    cb(null, result);
  });
};

Trader.prototype.run = function run() {
  var self = this;

  self.trigger();
  self.triggerInterval = setInterval(function() {
    self.trigger();
  }, this.config.settings.pollInterval);
};

Trader.prototype.stop = function stop() {
  if (this.triggerInterval) clearInterval(this.triggerInterval);
};

Trader.prototype.verifyUser = function verifyUser(idRec, cb) {
  console.log(idRec);
  this._request({
    path: '/verify_user',
    method: 'POST',
    body: idRec
  }, cb);
};

Trader.prototype.verifyTransaction = function verifyTransaction(idRec) {
  console.log(idRec);
  this._request({
    path: '/verify_transaction',
    method: 'POST',
    body: idRec
  }, function(err) {
    if (err) console.log(err);
  });
};

Trader.prototype.reportEvent = function reportEvent(eventType, note) {
  var rec = {
    eventType: eventType,
    note: note,
    deviceTime: Date.now()
  };
  this._request({
    path: '/event',
    method: 'POST',
    body: rec,
    repeatUntilSuccess: true
  }, function (err) {
    if (err) console.log(err);
  });
};

Trader.prototype.sendBitcoins = function sendBitcoins(tx, cb) {
  tx.sequenceNumber = ++sequenceNumber;
  this._request({
    path: '/send',
    method: 'POST',
    body: tx,
    repeatUntilSuccess: true
  }, function (err, result) {
    if (!err) cb(null, result.txId);
    else {
      if (err.name === 'InsufficientFunds') return cb(err);
      if (err.name === 'networkDown') return cb(new Error('sendBitcoins timeout'));
    }
  });

  sequenceNumber = 0;
};

Trader.prototype.trigger = function trigger() {
  var self = this;

  // Not paired yet
  if (this.connectionInfo === null) return;

  self._request({
    path: '/poll',
    method: 'GET'
  }, function (err, res) {
    self._pollHandler(err, res);
  });
};

Trader.prototype.trade = function trade(rec, cb) {
  rec.sequenceNumber = ++sequenceNumber;
  this._request({
    path: '/trade',
    method: 'POST',
    body: rec,
    repeatUntilSuccess: true
  }, cb);
};

// Private functions

Trader.prototype._pollHandler = function _pollHandler(err, res) {
  if (err && err.name === 'networkDown') {
    if (_t0 === null) {
      _t0 = Date.now();
      return;
    }

    if (Date.now() - _t0 > this.config.settings.pollTimeout) {
      _t0 = null;
      this.emit('networkDown');
      return;
    }
  }

  if (err && err.name === 'unpair') {
    this.emit('unpair');
    return;
  }

  _t0 = null;

  // Not a network error, so no need to keep trying
  if (err) {
    if (sequenceNumber === 0)
      this.emit('networkDown');

    return;
  }

  this.txLimit = res.txLimit;
  this.idVerificationLimit = res.idVerificationLimit;
  this.idVerificationEnabled = res.idVerificationEnabled;
  this.exchangeRate = res.rate;
  this.balance = res.fiat;
  this.locale = res.locale;
  this.emit('pollUpdate');
  this.emit('networkUp');
};

function _richError(errMessage, name) {
  var err = new Error(errMessage);
  err.name = name;
  return err;
}

module.exports = Trader;
<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TAPi18n } from 'meteor/tap:i18n';

import { displayModal } from '/imports/ui/modules/modal';
import { transact } from '/imports/api/transactions/transaction';
import { displayNotice } from '/imports/ui/modules/notice';

const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');

let web3;

const modal = {
  icon: 'images/metamask.png',
  title: TAPi18n.__('metamask'),
  cancel: TAPi18n.__('close'),
  alertMode: true,
};

/**
* @summary given a token code, returns unit of conversion for web3 prompt
* @param {string} ticker token ticker
*/
const _convertToEther = (ticker) => {
  switch (ticker) {
    case 'ETH':
    case 'WEI':
    default:
      return 'ether';
  }
};

/**
* @summary check web3 plugin and connects to code obejct
*/
const _web3 = () => {
  if (!window.web3) {
    modal.message = TAPi18n.__('metamask-install');
    displayModal(true, modal);
    return false;
  }
  if (!web3) {
    // We don't know window.web3 version, so we use our own instance of web3
    // with provider given by window.web3
    web3 = new Web3(window.web3.currentProvider);
  }
  if (!web3.eth.coinbase) {
    modal.message = TAPi18n.__('metamask-activate');
    displayModal(true, modal);
    return false;
  }
  return web3;
};

/**
* @summary send crypto with mask;
* @param {string} from blockchain address
* @param {string} to blockchain destination
* @param {string} quantity amount transacted
* @param {string} token currency
* @param {object} sourceId sender in sovereign
* @param {object} targetId receiver in sovereign
*/
const _transactWithMetamask = (from, to, quantity, token, sourceId, targetId) => {
  if (_web3()) {
    const tx = {
      from,
      to,
      value: web3.toHex(web3.toWei(quantity, _convertToEther(token))),
      gas: 200000,
      chainId: 3,
    };
    web3.eth.sendTransaction(tx, (error, receipt) => {
      if (error) {
        if (error.message.includes('User denied transaction signature') || error.code === -32603) {
          modal.message = TAPi18n.__('metamask-denied-signature');
          displayModal(true, modal);
          return;
        }
      }
      web3.eth.getTransaction(receipt, (err, res) => {
        if (!err) {
          const ticket = transact(
            sourceId,
            targetId,
            0,
            {
              currency: token,
              status: 'PENDING',
              kind: 'CRYPTO',
              contractId: targetId,
              blockchain: {
                tickets: [{
                  hash: res.hash,
                  status: 'PENDING',
                  value: quantity,
                }],
                coin: {
                  code: token,
                },
              },
            },
            () => {
              displayNotice(`${TAPi18n.__('transaction-broadcast').replace('{{token}}', token)}`, true);
            }
          );
          console.log(ticket);
        }
      });
    });
  }
  return undefined;
};

if (Meteor.isClient) {
  const handleSignMessage = (publicAddress) => {
    return new Promise((resolve, reject) => {
      web3.personal.sign(
        web3.fromUtf8(`${TAPi18n.__('metamask-sign-nonce').replace('{{collectiveName}}', Meteor.settings.public.Collective.name)}`),
        publicAddress,
        function (err, signature) {
          if (err) return reject(err);
          return resolve({ signature });
        }
      );
    });
  };

  const verifySignature = (signature, publicAddress) => {
    const msg = `${TAPi18n.__('metamask-sign-nonce').replace('{{collectiveName}}', Meteor.settings.public.Collective.name)}`;
    let res;

    // Perform an elliptic curve signature verification with ecrecover
    const msgBuffer = ethUtil.toBuffer(msg);
    const msgHash = ethUtil.hashPersonalMessage(msgBuffer);
    const signatureBuffer = ethUtil.toBuffer(signature.signature);
    const signatureParams = ethUtil.fromRpcSig(signatureBuffer);
    const publicKey = ethUtil.ecrecover(
      msgHash,
      signatureParams.v,
      signatureParams.r,
      signatureParams.s
    );
    const addressBuffer = ethUtil.publicToAddress(publicKey);
    const address = ethUtil.bufferToHex(addressBuffer);

    // The signature verification is successful if the address found with
    // ecrecover matches the initial publicAddress
    if (address.toLowerCase() === publicAddress.toLowerCase()) {
      return 'success';
    }
    return res
      .status(401)
      .send({ error: TAPi18n.__('metamask-sign-fail') });
  };

  /**
  * @summary log in signing public blockchain address with private key
  */
  const loginWithMetamask = () => {
    if (_web3()) {
      const nonce = Math.floor(Math.random() * 10000);
      const publicAddress = web3.eth.coinbase.toLowerCase();

      handleSignMessage(publicAddress, nonce).then(function (signature) {
        const verification = verifySignature(signature, publicAddress, nonce);

        if (verification === 'success') {
          const methodName = 'login';
          const methodArguments = [{ publicAddress }];
          Accounts.callLoginMethod({
            methodArguments,
            userCallback: (err) => {
              Accounts._pageLoadLogin({
                type: 'metamask',
                allowed: !err,
                error: err,
                methodName,
                methodArguments,
              });
            },
          });
        } else {
          console.log(TAPi18n.__('metamask-login-error'));
        }
      });
    }
  };

  Accounts.registerClientLoginFunction('metamask', loginWithMetamask);

  Meteor.loginWithMetamask = function () {
    return Accounts.applyLoginFunction('metamask', arguments);
  };
}

if (Meteor.isServer) {
  Accounts.registerLoginHandler('metamask', function (opts) {
    const publicAddress = opts.publicAddress;
    let user = null;
    const userQuery = Meteor.users.find({ username: publicAddress }).fetch();
    let serviceUserId = {};

    // Check if user with current publicAddress already exists
    if (userQuery.length === 0) {
      // If not, create it
      user = Accounts.updateOrCreateUserFromExternalService('metamask', {
        id: publicAddress,
        publicAddress,
      });
      serviceUserId = { userId: user.userId };
    } else {
      // Otherwise, retrieve it
      user = userQuery;
      serviceUserId = { userId: user[0]._id };
    }
    return serviceUserId;
  });

  Accounts.onLogin(function (loginObject) {
    if (loginObject.type !== 'resume') {
      Meteor.call('loadUserTokenBalance', loginObject.user._id, (subsidyError) => {
        if (subsidyError) {
          console.log(subsidyError, 'error on Accounts.onLogin');
        }
      });
    }
  });
}

export const transactWithMetamask = _transactWithMetamask;
export const setupWeb3 = _web3;
<commit_msg>refresh page on login<commit_after>import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TAPi18n } from 'meteor/tap:i18n';
import { Router } from 'meteor/iron:router';
import { displayModal } from '/imports/ui/modules/modal';
import { transact } from '/imports/api/transactions/transaction';
import { displayNotice } from '/imports/ui/modules/notice';

const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');

let web3;

const modal = {
  icon: 'images/metamask.png',
  title: TAPi18n.__('metamask'),
  cancel: TAPi18n.__('close'),
  alertMode: true,
};

/**
* @summary given a token code, returns unit of conversion for web3 prompt
* @param {string} ticker token ticker
*/
const _convertToEther = (ticker) => {
  switch (ticker) {
    case 'ETH':
    case 'WEI':
    default:
      return 'ether';
  }
};

/**
* @summary check web3 plugin and connects to code obejct
*/
const _web3 = () => {
  if (!window.web3) {
    modal.message = TAPi18n.__('metamask-install');
    displayModal(true, modal);
    return false;
  }
  if (!web3) {
    // We don't know window.web3 version, so we use our own instance of web3
    // with provider given by window.web3
    web3 = new Web3(window.web3.currentProvider);
  }
  if (!web3.eth.coinbase) {
    modal.message = TAPi18n.__('metamask-activate');
    displayModal(true, modal);
    return false;
  }
  return web3;
};

/**
* @summary send crypto with mask;
* @param {string} from blockchain address
* @param {string} to blockchain destination
* @param {string} quantity amount transacted
* @param {string} token currency
* @param {object} sourceId sender in sovereign
* @param {object} targetId receiver in sovereign
*/
const _transactWithMetamask = (from, to, quantity, token, sourceId, targetId) => {
  if (_web3()) {
    const tx = {
      from,
      to,
      value: web3.toHex(web3.toWei(quantity, _convertToEther(token))),
      gas: 200000,
      chainId: 3,
    };
    web3.eth.sendTransaction(tx, (error, receipt) => {
      if (error) {
        if (error.message.includes('User denied transaction signature') || error.code === -32603) {
          modal.message = TAPi18n.__('metamask-denied-signature');
          displayModal(true, modal);
          return;
        }
      }
      web3.eth.getTransaction(receipt, (err, res) => {
        if (!err) {
          const ticket = transact(
            sourceId,
            targetId,
            0,
            {
              currency: token,
              status: 'PENDING',
              kind: 'CRYPTO',
              contractId: targetId,
              blockchain: {
                tickets: [{
                  hash: res.hash,
                  status: 'PENDING',
                  value: quantity,
                }],
                coin: {
                  code: token,
                },
              },
            },
            () => {
              displayNotice(`${TAPi18n.__('transaction-broadcast').replace('{{token}}', token)}`, true);
            }
          );
          console.log(ticket);
        }
      });
    });
  }
  return undefined;
};

if (Meteor.isClient) {
  const handleSignMessage = (publicAddress) => {
    return new Promise((resolve, reject) => {
      web3.personal.sign(
        web3.fromUtf8(`${TAPi18n.__('metamask-sign-nonce').replace('{{collectiveName}}', Meteor.settings.public.Collective.name)}`),
        publicAddress,
        function (err, signature) {
          if (err) return reject(err);
          return resolve({ signature });
        }
      );
    });
  };

  const verifySignature = (signature, publicAddress) => {
    const msg = `${TAPi18n.__('metamask-sign-nonce').replace('{{collectiveName}}', Meteor.settings.public.Collective.name)}`;
    let res;

    // Perform an elliptic curve signature verification with ecrecover
    const msgBuffer = ethUtil.toBuffer(msg);
    const msgHash = ethUtil.hashPersonalMessage(msgBuffer);
    const signatureBuffer = ethUtil.toBuffer(signature.signature);
    const signatureParams = ethUtil.fromRpcSig(signatureBuffer);
    const publicKey = ethUtil.ecrecover(
      msgHash,
      signatureParams.v,
      signatureParams.r,
      signatureParams.s
    );
    const addressBuffer = ethUtil.publicToAddress(publicKey);
    const address = ethUtil.bufferToHex(addressBuffer);

    // The signature verification is successful if the address found with
    // ecrecover matches the initial publicAddress
    if (address.toLowerCase() === publicAddress.toLowerCase()) {
      return 'success';
    }
    return res
      .status(401)
      .send({ error: TAPi18n.__('metamask-sign-fail') });
  };

  /**
  * @summary log in signing public blockchain address with private key
  */
  const loginWithMetamask = () => {
    if (_web3()) {
      const nonce = Math.floor(Math.random() * 10000);
      const publicAddress = web3.eth.coinbase.toLowerCase();

      handleSignMessage(publicAddress, nonce).then(function (signature) {
        const verification = verifySignature(signature, publicAddress, nonce);

        if (verification === 'success') {
          const methodName = 'login';
          const methodArguments = [{ publicAddress }];
          Accounts.callLoginMethod({
            methodArguments,
            userCallback: (err) => {
              Accounts._pageLoadLogin({
                type: 'metamask',
                allowed: !err,
                error: err,
                methodName,
                methodArguments,
              });
              Router.go('/');
            },
          });
        } else {
          console.log(TAPi18n.__('metamask-login-error'));
        }
      });
    }
  };

  Accounts.registerClientLoginFunction('metamask', loginWithMetamask);

  Meteor.loginWithMetamask = function () {
    return Accounts.applyLoginFunction('metamask', arguments);
  };
}

if (Meteor.isServer) {
  Accounts.registerLoginHandler('metamask', function (opts) {
    const publicAddress = opts.publicAddress;
    let user = null;
    const userQuery = Meteor.users.find({ username: publicAddress }).fetch();
    let serviceUserId = {};

    // Check if user with current publicAddress already exists
    if (userQuery.length === 0) {
      // If not, create it
      user = Accounts.updateOrCreateUserFromExternalService('metamask', {
        id: publicAddress,
        publicAddress,
      });
      serviceUserId = { userId: user.userId };
    } else {
      // Otherwise, retrieve it
      user = userQuery;
      serviceUserId = { userId: user[0]._id };
    }
    return serviceUserId;
  });

  Accounts.onLogin(function (loginObject) {
    if (loginObject.type !== 'resume') {
      Meteor.call('loadUserTokenBalance', loginObject.user._id, (subsidyError) => {
        if (subsidyError) {
          console.log(subsidyError, 'error on Accounts.onLogin');
        }
      });
    }
  });
}

export const transactWithMetamask = _transactWithMetamask;
export const setupWeb3 = _web3;
<|endoftext|>