<commit_before>/**
 * A superclass for games where you need to guess the correct number.
 * This class will set the .doStartFunction and .doReturnFunction.
 * They will map to the functions that you should setup (see _setupFunctions).
 * See BeeFlightGame for inspiration of how you can use this class.
 *
 * SETUP THESE IN THE SUBCLASS:
 * "this" object should have a property used when setting agent start position:
 *    this.pos: { agent: { start: { x, y }, scale: z } } }.

 * startStop:  When round start without automatic "guessing". Used in count and incremental-steps method.
 * startBelow: When round start by guessing lower than target. Used in addition method.
 * startAbove: When round start by guessing higher than target. Used in subtraction method.
 * startThink: When round start by guessing something. Used in add/subt method.

 * runNumber:  The function to run when a number has been chosen.

 * returnToStart:            When returning to the start position on incorrect answer.
 * returnNone:               When the game stays at the incorrect answer position.
 * returnToPreviousIfHigher: When returning to previous value if the incorrect answer was too high.
 * returnToPreviousIfLower:  When returning to previous value if the incorrect answer was too low.
 *
 * VARIABLES THE SUBCLASS CAN USE:
 * Number amount:        this.amount
 * Representation:       this.representation
 * The method:           this.method
 * The number to answer: this.currentNumber (updates automatically)
 *
 * FUNCTIONS THE SUBCLASS CAN USE:
 * Try a number: this.tryNumber(number) - when testing a guess against this.currentNumber
 *
 *
 * Typical game flow:
 * this.startGame();    // the first mode, this.modeIntro, will be called
 * this.nextRound();    // start next round (will automatically start next mode)
 * this.disable(false); // make it possible to interact with anything
 * this.tryNumber(x);   // try a number against the current one
 * this.nextRound();    // do this regardless if right or wrong,
 *                      // it takes care of mode switching and function calls for you
 * // Repeat last two functions until game is over.
 */
NumberGame.prototype = Object.create(Subgame.prototype);
NumberGame.prototype.constructor = NumberGame;
function NumberGame () {
	Subgame.call(this); // Call parent constructor.
}

/*
 * Phaser state function.
 * Publishes subgameStarted event.
 */
NumberGame.prototype.init = function (options) {
	Subgame.prototype.init.call(this, options);
	GeneralButton.prototype.buttonColor = this.buttonColor || GLOBAL.BUTTON_COLOR;

	/* Public variables */
	this.method = parseInt(options.method || GLOBAL.METHOD.count);
	this.representation = options.representation;
	this.amount = GLOBAL.NUMBER_RANGE[options.range];
	/* The current number to answer */
	this.currentNumber = null;
	/* Stores the offset of the last try, can be used to judge last try */
	/* Ex: -1 means that last try was one less than currentNumber */
	this.lastTry = 0;
	/* This should be used to save the current position. */
	this.atValue = 0;
	/* This is used to add to the number of the button pushes. */
	/* This should be modified only when isRelative is set to true. */
	this.addToNumber = 0;

	// Setup gameplay differently depending on situation.
	this.isRelative = false;
	this._setupFunctions();

	/* Numbers for randomisation. */
	this._weighted = this.amount > 4 && this.method === GLOBAL.METHOD.count;
	this._numberMin = 1;
	this._numberMax = this.amount;
	if (this.method === GLOBAL.METHOD.addition) {
		this._numberMin++;
	}
	if (this.method === GLOBAL.METHOD.subtraction) {
		this._numberMax--;
	}

	// Agent is added to the game in the superclass, but we set it up here.
	this.agent.x = this.pos.agent.start.x;
	this.agent.y = this.pos.agent.start.y;
	this.agent.scale.set(this.pos.agent.scale);
	this.agent.visible = true;
	this.agent.addThought(this.representation[0], this.pos.agent.mirror || false);

	var _this = this;
	this.agent.moveTo = {
		start: function () {
			if (_this.agent.x === _this.pos.agent.stop.x &&
				_this.agent.y === _this.pos.agent.stop.y) {
				return new TweenMax(_this.agent);
			}
			return _this.agent.move({ x: _this.pos.agent.stop.x, y: _this.pos.agent.stop.y }, 3);
		}
	};
};

/*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM*/
/*                            Private functions                              */
/*WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW*/

/**
 * Setup start and return functions.
 * Will map to the functions the subclass should overshadow (see bottom of this class).
 * The .doStartFunction is an easy way to call the appropriate start function.
 * The .doReturnFunction is an wasy way to call the appropriate return function when answer is incorrect.
 */
NumberGame.prototype._setupFunctions = function () {
	if (this.method === GLOBAL.METHOD.count) {
		this.doStartFunction = this.startStop;
		this.doReturnFunction = this.returnToStart;
	} else if (this.method === GLOBAL.METHOD.incrementalSteps) {
		this.doStartFunction = this.startStop;
		this.doReturnFunction = this.returnNone;
	} else if (this.method === GLOBAL.METHOD.addition) {
		this.doStartFunction = this.startBelow;
		this.doReturnFunction = this.returnToPreviousIfHigher;
		this.isRelative = true;
	} else if (this.method === GLOBAL.METHOD.subtraction) {
		this.doStartFunction = this.startAbove;
		this.doReturnFunction = this.returnToPreviousIfLower;
		this.isRelative = true;
	} else {
		this.doStartFunction = this.startThink;
		this.doReturnFunction = this.returnNone;
		this.isRelative = true;
	}
};

/** Change this.currentNumber to a new one (resets the tries). */
NumberGame.prototype._nextNumber = function () {
	// Should we allow the same number again?
	this._totalTries += this._currentTries;
	this._currentTries = 0;

	// Weighted randomisation if applicable
	if (this._weighted && this.rnd.frac() < 0.2) {
		this.currentNumber = this.rnd.integerInRange(5, this._numberMax);
	} else {
		this.currentNumber = this.rnd.integerInRange(this._numberMin, this._numberMax);
	}
};

/*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM*/
/*                            Public functions                               */
/*WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW*/

/**
 * Try a number against this.currentNumber.
 * The offset of the last try is stored in this.lastTry.
 * Publishes tryNumber event.
 * @param {number} The number to try.
 * @param {number} The offset to the number (example if you start at 2).
 * @return {boolean} The offset of the last try (0 is correct, -x is too low, +x is too high).
 */
NumberGame.prototype.tryNumber = function (number, offset) {
	var sum = number + (offset || 0);
	EventSystem.publish(GLOBAL.EVENT.tryNumber, [sum, this.currentNumber, number, offset]);
	this._currentTries++;
	this.lastTry = sum - this.currentNumber;

	if (!this.lastTry) {
		this._counter.value++; // This will trigger next mode if we loop.
		this._nextNumber();
	}
	return this.lastTry;
};

/** Have the agent guess a number. */
NumberGame.prototype.agentGuess = function () {
	var t = new TimelineMax();
	t.addCallback(function () {
		this.updateButtons();
		this.agent.guessNumber(this.currentNumber - (this.isRelative ? this.addToNumber : 0),
			this.buttons ? this.buttons.min : 1,
			this.buttons ? this.buttons.max : this.amount);
	}, 0, null, this);
	t.add(this.agent.think());
	return t;
};

/**
 * Setup HUD buttons.
 * @param {Object} options - The options for the buttons
 *                           Supply like this: { buttons: {...}, yesnos: {...} }.
 *                           NOTE: .method and .onClick will be set in this function.
 */
NumberGame.prototype.setupButtons = function (options) {
	var _this = this;
	if (options.buttons) {
		options.buttons.method = this.method;
		options.buttons.onClick = function (number) { _this.pushNumber(number); };
		this.buttons = new ButtonPanel(this.amount, this.representation, options.buttons);
		this.buttons.visible = false;
		this.hudGroup.add(this.buttons);
	}

	if (options.yesnos) {
		options.yesnos.onClick = function (value) { _this.pushYesNo(value); };
		this.yesnos = new ButtonPanel(2, GLOBAL.NUMBER_REPRESENTATION.yesno, options.yesnos);
		this.yesnos.visible = false;
		this.hudGroup.add(this.yesnos);
	}
};

/* Function to trigger when a button in the number panel is pushed. */
NumberGame.prototype.pushNumber = function (number) {
	return this.runNumber(number)
		.addCallback(this.nextRound, null, null, this);
};

/* Function to trigger when a button in the yes-no panel is pushed. */
NumberGame.prototype.pushYesNo = function (value) {
	if (!value) {
		if (this.speech) {
			this.agent.say(this.speech).play('agentCorrected');
		}
		this.showNumbers();
	} else {
		this.pushNumber(this.agent.lastGuess);
	}
};

/* Show the number panel, hide the yes/no panel and enable input. */
NumberGame.prototype.showNumbers = function () {
	this.disable(true);
	if (this.buttons) {
		this.buttons.reset();
		if (this.isRelative) {
			this.updateButtons();
		}
		fade(this.buttons, true).eventCallback('onComplete', this.disable, false, this);
	}
	if (this.yesnos) {
		fade(this.yesnos, false);
	}

	if (this.agent.visible) {
		fade(this.agent.thought, false);
		this.agent.eyesFollowPointer();
	}
};

/* Show the yes/no panel, hide the number panel and enable input. */
NumberGame.prototype.showYesnos = function () {
	this.disable(true);
	if (this.buttons) {
		fade(this.buttons, false);
	}
	if (this.yesnos) {
		this.yesnos.reset();
		fade(this.yesnos, true).eventCallback('onComplete', this.disable, false, this);
	}

	if (this.agent.visible) { this.agent.eyesFollowPointer(); }
};

/* Hide the number and yes/no panel. */
NumberGame.prototype.hideButtons = function () {
	this.disable(true);
	if (this.buttons) {
		fade(this.buttons, false);
	}
	if (this.yesnos) {
		fade(this.yesnos, false);
	}

	if (this.agent.visible) { this.agent.eyesStopFollow(); }
};

/* Update the values of the button panel. */
NumberGame.prototype.updateButtons = function () {
	if (this.buttons) {
		if (this.method === GLOBAL.METHOD.addition) {
			this.buttons.setRange(1, this.amount - this.addToNumber);
		} else if (this.method === GLOBAL.METHOD.subtraction) {
			this.buttons.setRange(1 - this.addToNumber, -1);
		} else {
			this.buttons.setRange(1 - this.addToNumber, this.amount - this.addToNumber);
		}
	}
};

/* Update the relative value. */
NumberGame.prototype.updateRelative = function () {
	if (this.isRelative) {
		this.addToNumber = this.atValue;
	}
};

/* Start the game. */
NumberGame.prototype.startGame = function () {
	this._nextNumber();
	Subgame.prototype.startGame.call(this);
};

/* The following functions should be overshadowed in the game object. */
NumberGame.prototype.pos = { agent: { start: { x: 0, y: 0 }, scale: 1 } };
NumberGame.prototype.startStop = function () {};
NumberGame.prototype.startBelow = function () {};
NumberGame.prototype.startAbove = function () {};
NumberGame.prototype.startThink = function () {};
NumberGame.prototype.returnToStart = function () {};
NumberGame.prototype.returnNone = function () {};
NumberGame.prototype.returnToPreviousIfHigher = function () {};
NumberGame.prototype.returnToPreviousIfLower = function () {};<commit_msg>NumberGame: Do not update button panel too early<commit_after>/**
 * A superclass for games where you need to guess the correct number.
 * This class will set the .doStartFunction and .doReturnFunction.
 * They will map to the functions that you should setup (see _setupFunctions).
 * See BeeFlightGame for inspiration of how you can use this class.
 *
 * SETUP THESE IN THE SUBCLASS:
 * "this" object should have a property used when setting agent start position:
 *    this.pos: { agent: { start: { x, y }, scale: z } } }.

 * startStop:  When round start without automatic "guessing". Used in count and incremental-steps method.
 * startBelow: When round start by guessing lower than target. Used in addition method.
 * startAbove: When round start by guessing higher than target. Used in subtraction method.
 * startThink: When round start by guessing something. Used in add/subt method.

 * runNumber:  The function to run when a number has been chosen.

 * returnToStart:            When returning to the start position on incorrect answer.
 * returnNone:               When the game stays at the incorrect answer position.
 * returnToPreviousIfHigher: When returning to previous value if the incorrect answer was too high.
 * returnToPreviousIfLower:  When returning to previous value if the incorrect answer was too low.
 *
 * VARIABLES THE SUBCLASS CAN USE:
 * Number amount:        this.amount
 * Representation:       this.representation
 * The method:           this.method
 * The number to answer: this.currentNumber (updates automatically)
 *
 * FUNCTIONS THE SUBCLASS CAN USE:
 * Try a number: this.tryNumber(number) - when testing a guess against this.currentNumber
 *
 *
 * Typical game flow:
 * this.startGame();    // the first mode, this.modeIntro, will be called
 * this.nextRound();    // start next round (will automatically start next mode)
 * this.disable(false); // make it possible to interact with anything
 * this.tryNumber(x);   // try a number against the current one
 * this.nextRound();    // do this regardless if right or wrong,
 *                      // it takes care of mode switching and function calls for you
 * // Repeat last two functions until game is over.
 */
NumberGame.prototype = Object.create(Subgame.prototype);
NumberGame.prototype.constructor = NumberGame;
function NumberGame () {
	Subgame.call(this); // Call parent constructor.
}

/*
 * Phaser state function.
 * Publishes subgameStarted event.
 */
NumberGame.prototype.init = function (options) {
	Subgame.prototype.init.call(this, options);
	GeneralButton.prototype.buttonColor = this.buttonColor || GLOBAL.BUTTON_COLOR;

	/* Public variables */
	this.method = parseInt(options.method || GLOBAL.METHOD.count);
	this.representation = options.representation;
	this.amount = GLOBAL.NUMBER_RANGE[options.range];
	/* The current number to answer */
	this.currentNumber = null;
	/* Stores the offset of the last try, can be used to judge last try */
	/* Ex: -1 means that last try was one less than currentNumber */
	this.lastTry = 0;
	/* This should be used to save the current position. */
	this.atValue = 0;
	/* This is used to add to the number of the button pushes. */
	/* This should be modified only when isRelative is set to true. */
	this.addToNumber = 0;

	// Setup gameplay differently depending on situation.
	this.isRelative = false;
	this._setupFunctions();

	/* Numbers for randomisation. */
	this._weighted = this.amount > 4 && this.method === GLOBAL.METHOD.count;
	this._numberMin = 1;
	this._numberMax = this.amount;
	if (this.method === GLOBAL.METHOD.addition) {
		this._numberMin++;
	}
	if (this.method === GLOBAL.METHOD.subtraction) {
		this._numberMax--;
	}

	// Agent is added to the game in the superclass, but we set it up here.
	this.agent.x = this.pos.agent.start.x;
	this.agent.y = this.pos.agent.start.y;
	this.agent.scale.set(this.pos.agent.scale);
	this.agent.visible = true;
	this.agent.addThought(this.representation[0], this.pos.agent.mirror || false);

	var _this = this;
	this.agent.moveTo = {
		start: function () {
			if (_this.agent.x === _this.pos.agent.stop.x &&
				_this.agent.y === _this.pos.agent.stop.y) {
				return new TweenMax(_this.agent);
			}
			return _this.agent.move({ x: _this.pos.agent.stop.x, y: _this.pos.agent.stop.y }, 3);
		}
	};
};

/*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM*/
/*                            Private functions                              */
/*WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW*/

/**
 * Setup start and return functions.
 * Will map to the functions the subclass should overshadow (see bottom of this class).
 * The .doStartFunction is an easy way to call the appropriate start function.
 * The .doReturnFunction is an wasy way to call the appropriate return function when answer is incorrect.
 */
NumberGame.prototype._setupFunctions = function () {
	if (this.method === GLOBAL.METHOD.count) {
		this.doStartFunction = this.startStop;
		this.doReturnFunction = this.returnToStart;
	} else if (this.method === GLOBAL.METHOD.incrementalSteps) {
		this.doStartFunction = this.startStop;
		this.doReturnFunction = this.returnNone;
	} else if (this.method === GLOBAL.METHOD.addition) {
		this.doStartFunction = this.startBelow;
		this.doReturnFunction = this.returnToPreviousIfHigher;
		this.isRelative = true;
	} else if (this.method === GLOBAL.METHOD.subtraction) {
		this.doStartFunction = this.startAbove;
		this.doReturnFunction = this.returnToPreviousIfLower;
		this.isRelative = true;
	} else {
		this.doStartFunction = this.startThink;
		this.doReturnFunction = this.returnNone;
		this.isRelative = true;
	}
};

/** Change this.currentNumber to a new one (resets the tries). */
NumberGame.prototype._nextNumber = function () {
	// Should we allow the same number again?
	this._totalTries += this._currentTries;
	this._currentTries = 0;

	// Weighted randomisation if applicable
	if (this._weighted && this.rnd.frac() < 0.2) {
		this.currentNumber = this.rnd.integerInRange(5, this._numberMax);
	} else {
		this.currentNumber = this.rnd.integerInRange(this._numberMin, this._numberMax);
	}
};

NumberGame.prototype._getRange = function () {
	if (this.method === GLOBAL.METHOD.addition) {
		return { min: 1, max: this.amount - this.addToNumber };
	} else if (this.method === GLOBAL.METHOD.subtraction) {
		return { min: 1 - this.addToNumber, max: -1 };
	} else if (this.method === GLOBAL.METHOD.additionSubtraction) {
		return { min: 1 - this.addToNumber, max: this.amount - this.addToNumber };
	} else {
		return { min: 1, max: this.amount };
	}
};

/*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM*/
/*                            Public functions                               */
/*WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW*/

/**
 * Try a number against this.currentNumber.
 * The offset of the last try is stored in this.lastTry.
 * Publishes tryNumber event.
 * @param {number} The number to try.
 * @param {number} The offset to the number (example if you start at 2).
 * @return {boolean} The offset of the last try (0 is correct, -x is too low, +x is too high).
 */
NumberGame.prototype.tryNumber = function (number, offset) {
	var sum = number + (offset || 0);
	EventSystem.publish(GLOBAL.EVENT.tryNumber, [sum, this.currentNumber, number, offset]);
	this._currentTries++;
	this.lastTry = sum - this.currentNumber;

	if (!this.lastTry) {
		this._counter.value++; // This will trigger next mode if we loop.
		this._nextNumber();
	}
	return this.lastTry;
};

/** Have the agent guess a number. */
NumberGame.prototype.agentGuess = function () {
	var t = new TimelineMax();
	t.addCallback(function () {
		var range = this._getRange();
		this.agent.guessNumber(this.currentNumber - (this.isRelative ? this.addToNumber : 0), range.min, range.max);
	}, 0, null, this);
	t.add(this.agent.think());
	return t;
};

/**
 * Setup HUD buttons.
 * @param {Object} options - The options for the buttons
 *                           Supply like this: { buttons: {...}, yesnos: {...} }.
 *                           NOTE: .method and .onClick will be set in this function.
 */
NumberGame.prototype.setupButtons = function (options) {
	var _this = this;
	if (options.buttons) {
		options.buttons.method = this.method;
		options.buttons.onClick = function (number) { _this.pushNumber(number); };
		this.buttons = new ButtonPanel(this.amount, this.representation, options.buttons);
		this.buttons.visible = false;
		this.hudGroup.add(this.buttons);
	}

	if (options.yesnos) {
		options.yesnos.onClick = function (value) { _this.pushYesNo(value); };
		this.yesnos = new ButtonPanel(2, GLOBAL.NUMBER_REPRESENTATION.yesno, options.yesnos);
		this.yesnos.visible = false;
		this.hudGroup.add(this.yesnos);
	}
};

/* Function to trigger when a button in the number panel is pushed. */
NumberGame.prototype.pushNumber = function (number) {
	return this.runNumber(number)
		.addCallback(this.nextRound, null, null, this);
};

/* Function to trigger when a button in the yes-no panel is pushed. */
NumberGame.prototype.pushYesNo = function (value) {
	if (!value) {
		if (this.speech) {
			this.agent.say(this.speech).play('agentCorrected');
		}
		this.showNumbers();
	} else {
		this.pushNumber(this.agent.lastGuess);
	}
};

/* Show the number panel, hide the yes/no panel and enable input. */
NumberGame.prototype.showNumbers = function () {
	this.disable(true);
	if (this.buttons) {
		this.buttons.reset();
		if (this.isRelative) {
			this.updateButtons();
		}
		fade(this.buttons, true).eventCallback('onComplete', this.disable, false, this);
	}
	if (this.yesnos) {
		fade(this.yesnos, false);
	}

	if (this.agent.visible) {
		fade(this.agent.thought, false);
		this.agent.eyesFollowPointer();
	}
};

/* Show the yes/no panel, hide the number panel and enable input. */
NumberGame.prototype.showYesnos = function () {
	this.disable(true);
	if (this.buttons) {
		fade(this.buttons, false);
	}
	if (this.yesnos) {
		this.yesnos.reset();
		fade(this.yesnos, true).eventCallback('onComplete', this.disable, false, this);
	}

	if (this.agent.visible) { this.agent.eyesFollowPointer(); }
};

/* Hide the number and yes/no panel. */
NumberGame.prototype.hideButtons = function () {
	this.disable(true);
	if (this.buttons) {
		fade(this.buttons, false);
	}
	if (this.yesnos) {
		fade(this.yesnos, false);
	}

	if (this.agent.visible) { this.agent.eyesStopFollow(); }
};

/* Update the values of the button panel. */
NumberGame.prototype.updateButtons = function () {
	if (this.buttons) {
		var range = this._getRange();
		this.buttons.setRange(range.min, range.max);
	}
};

/* Update the relative value. */
NumberGame.prototype.updateRelative = function () {
	if (this.isRelative) {
		this.addToNumber = this.atValue;
	}
};

/* Start the game. */
NumberGame.prototype.startGame = function () {
	this._nextNumber();
	Subgame.prototype.startGame.call(this);
};

/* The following functions should be overshadowed in the game object. */
NumberGame.prototype.pos = { agent: { start: { x: 0, y: 0 }, scale: 1 } };
NumberGame.prototype.startStop = function () {};
NumberGame.prototype.startBelow = function () {};
NumberGame.prototype.startAbove = function () {};
NumberGame.prototype.startThink = function () {};
NumberGame.prototype.returnToStart = function () {};
NumberGame.prototype.returnNone = function () {};
NumberGame.prototype.returnToPreviousIfHigher = function () {};
NumberGame.prototype.returnToPreviousIfLower = function () {};<|endoftext|>


<commit_before>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { BadSymbol, ExchangeError, NotSupported } = require ('./base/errors');
const Precise = require ('./base/Precise');

//  ---------------------------------------------------------------------------

module.exports = class bitstamp1 extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bitstamp1',
            'name': 'Bitstamp',
            'countries': [ 'GB' ],
            'rateLimit': 1000,
            'version': 'v1',
            'has': {
                'CORS': true,
                'spot': true,
                'margin': undefined,
                'swap': undefined,
                'future': undefined,
                'option': undefined,
                'cancelOrder': true,
                'createOrder': true,
                'fetchBalance': true,
                'fetchMyTrades': true,
                'fetchOrder': true,
                'fetchOrderBook': true,
                'fetchTicker': true,
                'fetchTrades': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg',
                'api': 'https://www.bitstamp.net/api',
                'www': 'https://www.bitstamp.net',
                'doc': 'https://www.bitstamp.net/api',
            },
            'requiredCredentials': {
                'apiKey': true,
                'secret': true,
                'uid': true,
            },
            'api': {
                'public': {
                    'get': [
                        'ticker',
                        'ticker_hour',
                        'order_book',
                        'transactions',
                        'eur_usd',
                    ],
                },
                'private': {
                    'post': [
                        'balance',
                        'user_transactions',
                        'open_orders',
                        'order_status',
                        'cancel_order',
                        'cancel_all_orders',
                        'buy',
                        'sell',
                        'bitcoin_deposit_address',
                        'unconfirmed_btc',
                        'ripple_withdrawal',
                        'ripple_address',
                        'withdrawal_requests',
                        'bitcoin_withdrawal',
                    ],
                },
            },
            'markets': {
                'BTC/USD': { 'id': 'btcusd', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'btc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'BTC/EUR': { 'id': 'btceur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'btc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'EUR/USD': { 'id': 'eurusd', 'symbol': 'EUR/USD', 'base': 'EUR', 'quote': 'USD', 'baseId': 'eur', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'XRP/USD': { 'id': 'xrpusd', 'symbol': 'XRP/USD', 'base': 'XRP', 'quote': 'USD', 'baseId': 'xrp', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'XRP/EUR': { 'id': 'xrpeur', 'symbol': 'XRP/EUR', 'base': 'XRP', 'quote': 'EUR', 'baseId': 'xrp', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'XRP/BTC': { 'id': 'xrpbtc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'LTC/USD': { 'id': 'ltcusd', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD', 'baseId': 'ltc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'LTC/EUR': { 'id': 'ltceur', 'symbol': 'LTC/EUR', 'base': 'LTC', 'quote': 'EUR', 'baseId': 'ltc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'LTC/BTC': { 'id': 'ltcbtc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'ETH/USD': { 'id': 'ethusd', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD', 'baseId': 'eth', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'ETH/EUR': { 'id': 'etheur', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR', 'baseId': 'eth', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'ETH/BTC': { 'id': 'ethbtc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
            },
        });
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        if (symbol !== 'BTC/USD') {
            throw new ExchangeError (this.id + ' ' + this.version + " fetchOrderBook doesn't support " + symbol + ', use it for BTC/USD only');
        }
        await this.loadMarkets ();
        const orderbook = await this.publicGetOrderBook (params);
        const timestamp = this.safeTimestamp (orderbook, 'timestamp');
        return this.parseOrderBook (orderbook, symbol, timestamp);
    }

    parseTicker (ticker, market = undefined) {
        //
        // {
        //     "volume": "2836.47827985",
        //     "last": "36544.93",
        //     "timestamp": "1643372072",
        //     "bid": "36535.79",
        //     "vwap":"36594.20",
        //     "high": "37534.15",
        //     "low": "35511.32",
        //     "ask": "36548.47",
        //     "open": 37179.62
        // }
        //
        const symbol = this.safeSymbol (undefined, market);
        const timestamp = this.safeTimestamp (ticker, 'timestamp');
        const vwap = this.safeString (ticker, 'vwap');
        const baseVolume = this.safeString (ticker, 'volume');
        const quoteVolume = Precise.stringMul (baseVolume, vwap);
        const last = this.safeString (ticker, 'last');
        return this.safeTicker ({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeString (ticker, 'high'),
            'low': this.safeString (ticker, 'low'),
            'bid': this.safeString (ticker, 'bid'),
            'bidVolume': undefined,
            'ask': this.safeString (ticker, 'ask'),
            'askVolume': undefined,
            'vwap': vwap,
            'open': this.safeString (ticker, 'open'),
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market, false);
    }

    async fetchTicker (symbol, params = {}) {
        if (symbol !== 'BTC/USD') {
            throw new ExchangeError (this.id + ' ' + this.version + " fetchTicker doesn't support " + symbol + ', use it for BTC/USD only');
        }
        await this.loadMarkets ();
        const market = this.market (symbol);
        const ticker = await this.publicGetTicker (params);
        //
        // {
        //     "volume": "2836.47827985",
        //     "last": "36544.93",
        //     "timestamp": "1643372072",
        //     "bid": "36535.79",
        //     "vwap":"36594.20",
        //     "high": "37534.15",
        //     "low": "35511.32",
        //     "ask": "36548.47",
        //     "open": 37179.62
        // }
        //
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market = undefined) {
        const timestamp = this.safeTimestamp2 (trade, 'date', 'datetime');
        const side = (trade['type'] === 0) ? 'buy' : 'sell';
        const orderId = this.safeString (trade, 'order_id');
        if ('currency_pair' in trade) {
            if (trade['currency_pair'] in this.markets_by_id) {
                market = this.markets_by_id[trade['currency_pair']];
            }
        }
        const id = this.safeString (trade, 'tid');
        const priceString = this.safeString (trade, 'price');
        const amountString = this.safeString (trade, 'amount');
        const price = this.parseNumber (priceString);
        const amount = this.parseNumber (amountString);
        const cost = this.parseNumber (Precise.stringMul (priceString, amountString));
        let symbol = undefined;
        if (market !== undefined) {
            symbol = market['symbol'];
        }
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': undefined,
            'side': side,
            'takerOrMaker': undefined,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': undefined,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        if (symbol !== 'BTC/USD') {
            throw new BadSymbol (this.id + ' ' + this.version + " fetchTrades doesn't support " + symbol + ', use it for BTC/USD only');
        }
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'time': 'minute',
        };
        const response = await this.publicGetTransactions (this.extend (request, params));
        return this.parseTrades (response, market, since, limit);
    }

    parseBalance (response) {
        const result = { 'info': response };
        const codes = Object.keys (this.currencies);
        for (let i = 0; i < codes.length; i++) {
            const code = codes[i];
            const currency = this.currency (code);
            const currencyId = currency['id'];
            const account = this.account ();
            account['free'] = this.safeString (response, currencyId + '_available');
            account['used'] = this.safeString (response, currencyId + '_reserved');
            account['total'] = this.safeString (response, currencyId + '_balance');
            result[code] = account;
        }
        return this.safeBalance (result);
    }

    async fetchBalance (params = {}) {
        const response = await this.privatePostBalance (params);
        return this.parseBalance (response);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        if (type !== 'limit') {
            throw new ExchangeError (this.id + ' ' + this.version + ' accepts limit orders only');
        }
        if (symbol !== 'BTC/USD') {
            throw new ExchangeError (this.id + ' v1 supports BTC/USD orders only');
        }
        await this.loadMarkets ();
        const method = 'privatePost' + this.capitalize (side);
        const request = {
            'amount': amount,
            'price': price,
        };
        const response = await this[method] (this.extend (request, params));
        const id = this.safeString (response, 'id');
        return {
            'info': response,
            'id': id,
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        return await this.privatePostCancelOrder ({ 'id': id });
    }

    parseOrderStatus (status) {
        const statuses = {
            'In Queue': 'open',
            'Open': 'open',
            'Finished': 'closed',
            'Canceled': 'canceled',
        };
        return this.safeString (statuses, status, status);
    }

    async fetchOrderStatus (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {
            'id': id,
        };
        const response = await this.privatePostOrderStatus (this.extend (request, params));
        return this.parseOrderStatus (response);
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = undefined;
        if (symbol !== undefined) {
            market = this.market (symbol);
        }
        const pair = market ? market['id'] : 'all';
        const request = {
            'id': pair,
        };
        const response = await this.privatePostOpenOrdersId (this.extend (request, params));
        return this.parseTrades (response, market, since, limit);
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        throw new NotSupported (this.id + ' fetchOrder is not implemented yet');
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'public') {
            if (Object.keys (query).length) {
                url += '?' + this.urlencode (query);
            }
        } else {
            this.checkRequiredCredentials ();
            const nonce = this.nonce ().toString ();
            const auth = nonce + this.uid + this.apiKey;
            const signature = this.encode (this.hmac (this.encode (auth), this.encode (this.secret)));
            query = this.extend ({
                'key': this.apiKey,
                'signature': signature.toUpperCase (),
                'nonce': nonce,
            }, query);
            body = this.urlencode (query);
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
        if (response === undefined) {
            return;
        }
        const status = this.safeString (response, 'status');
        if (status === 'error') {
            throw new ExchangeError (this.id + ' ' + this.json (response));
        }
    }
};
<commit_msg>bitstamp1.has leverage methods<commit_after>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { BadSymbol, ExchangeError, NotSupported } = require ('./base/errors');
const Precise = require ('./base/Precise');

//  ---------------------------------------------------------------------------

module.exports = class bitstamp1 extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bitstamp1',
            'name': 'Bitstamp',
            'countries': [ 'GB' ],
            'rateLimit': 1000,
            'version': 'v1',
            'has': {
                'CORS': true,
                'spot': true,
                'margin': false,
                'swap': false,
                'future': false,
                'option': false,
                'addMargin': false,
                'cancelOrder': true,
                'createOrder': true,
                'createReduceOnlyOrder': false,
                'fetchBalance': true,
                'fetchBorrowRate': false,
                'fetchBorrowRateHistories': false,
                'fetchBorrowRateHistory': false,
                'fetchBorrowRates': false,
                'fetchBorrowRatesPerSymbol': false,
                'fetchFundingHistory': false,
                'fetchFundingRate': false,
                'fetchFundingRateHistory': false,
                'fetchFundingRates': false,
                'fetchIndexOHLCV': false,
                'fetchIsolatedPositions': false,
                'fetchLeverage': false,
                'fetchMarkOHLCV': false,
                'fetchMyTrades': true,
                'fetchOrder': true,
                'fetchOrderBook': true,
                'fetchPosition': false,
                'fetchPositions': false,
                'fetchPositionsRisk': false,
                'fetchPremiumIndexOHLCV': false,
                'fetchTicker': true,
                'fetchTrades': true,
                'reduceMargin': false,
                'setLeverage': false,
                'setMarginMode': false,
                'setPositionMode': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg',
                'api': 'https://www.bitstamp.net/api',
                'www': 'https://www.bitstamp.net',
                'doc': 'https://www.bitstamp.net/api',
            },
            'requiredCredentials': {
                'apiKey': true,
                'secret': true,
                'uid': true,
            },
            'api': {
                'public': {
                    'get': [
                        'ticker',
                        'ticker_hour',
                        'order_book',
                        'transactions',
                        'eur_usd',
                    ],
                },
                'private': {
                    'post': [
                        'balance',
                        'user_transactions',
                        'open_orders',
                        'order_status',
                        'cancel_order',
                        'cancel_all_orders',
                        'buy',
                        'sell',
                        'bitcoin_deposit_address',
                        'unconfirmed_btc',
                        'ripple_withdrawal',
                        'ripple_address',
                        'withdrawal_requests',
                        'bitcoin_withdrawal',
                    ],
                },
            },
            'markets': {
                'BTC/USD': { 'id': 'btcusd', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'btc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'BTC/EUR': { 'id': 'btceur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'btc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'EUR/USD': { 'id': 'eurusd', 'symbol': 'EUR/USD', 'base': 'EUR', 'quote': 'USD', 'baseId': 'eur', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'XRP/USD': { 'id': 'xrpusd', 'symbol': 'XRP/USD', 'base': 'XRP', 'quote': 'USD', 'baseId': 'xrp', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'XRP/EUR': { 'id': 'xrpeur', 'symbol': 'XRP/EUR', 'base': 'XRP', 'quote': 'EUR', 'baseId': 'xrp', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'XRP/BTC': { 'id': 'xrpbtc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'LTC/USD': { 'id': 'ltcusd', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD', 'baseId': 'ltc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'LTC/EUR': { 'id': 'ltceur', 'symbol': 'LTC/EUR', 'base': 'LTC', 'quote': 'EUR', 'baseId': 'ltc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'LTC/BTC': { 'id': 'ltcbtc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'ETH/USD': { 'id': 'ethusd', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD', 'baseId': 'eth', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'ETH/EUR': { 'id': 'etheur', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR', 'baseId': 'eth', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
                'ETH/BTC': { 'id': 'ethbtc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005, 'type': 'spot', 'spot': true },
            },
        });
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        if (symbol !== 'BTC/USD') {
            throw new ExchangeError (this.id + ' ' + this.version + " fetchOrderBook doesn't support " + symbol + ', use it for BTC/USD only');
        }
        await this.loadMarkets ();
        const orderbook = await this.publicGetOrderBook (params);
        const timestamp = this.safeTimestamp (orderbook, 'timestamp');
        return this.parseOrderBook (orderbook, symbol, timestamp);
    }

    parseTicker (ticker, market = undefined) {
        //
        // {
        //     "volume": "2836.47827985",
        //     "last": "36544.93",
        //     "timestamp": "1643372072",
        //     "bid": "36535.79",
        //     "vwap":"36594.20",
        //     "high": "37534.15",
        //     "low": "35511.32",
        //     "ask": "36548.47",
        //     "open": 37179.62
        // }
        //
        const symbol = this.safeSymbol (undefined, market);
        const timestamp = this.safeTimestamp (ticker, 'timestamp');
        const vwap = this.safeString (ticker, 'vwap');
        const baseVolume = this.safeString (ticker, 'volume');
        const quoteVolume = Precise.stringMul (baseVolume, vwap);
        const last = this.safeString (ticker, 'last');
        return this.safeTicker ({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeString (ticker, 'high'),
            'low': this.safeString (ticker, 'low'),
            'bid': this.safeString (ticker, 'bid'),
            'bidVolume': undefined,
            'ask': this.safeString (ticker, 'ask'),
            'askVolume': undefined,
            'vwap': vwap,
            'open': this.safeString (ticker, 'open'),
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market, false);
    }

    async fetchTicker (symbol, params = {}) {
        if (symbol !== 'BTC/USD') {
            throw new ExchangeError (this.id + ' ' + this.version + " fetchTicker doesn't support " + symbol + ', use it for BTC/USD only');
        }
        await this.loadMarkets ();
        const market = this.market (symbol);
        const ticker = await this.publicGetTicker (params);
        //
        // {
        //     "volume": "2836.47827985",
        //     "last": "36544.93",
        //     "timestamp": "1643372072",
        //     "bid": "36535.79",
        //     "vwap":"36594.20",
        //     "high": "37534.15",
        //     "low": "35511.32",
        //     "ask": "36548.47",
        //     "open": 37179.62
        // }
        //
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market = undefined) {
        const timestamp = this.safeTimestamp2 (trade, 'date', 'datetime');
        const side = (trade['type'] === 0) ? 'buy' : 'sell';
        const orderId = this.safeString (trade, 'order_id');
        if ('currency_pair' in trade) {
            if (trade['currency_pair'] in this.markets_by_id) {
                market = this.markets_by_id[trade['currency_pair']];
            }
        }
        const id = this.safeString (trade, 'tid');
        const priceString = this.safeString (trade, 'price');
        const amountString = this.safeString (trade, 'amount');
        const price = this.parseNumber (priceString);
        const amount = this.parseNumber (amountString);
        const cost = this.parseNumber (Precise.stringMul (priceString, amountString));
        let symbol = undefined;
        if (market !== undefined) {
            symbol = market['symbol'];
        }
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': undefined,
            'side': side,
            'takerOrMaker': undefined,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': undefined,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        if (symbol !== 'BTC/USD') {
            throw new BadSymbol (this.id + ' ' + this.version + " fetchTrades doesn't support " + symbol + ', use it for BTC/USD only');
        }
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'time': 'minute',
        };
        const response = await this.publicGetTransactions (this.extend (request, params));
        return this.parseTrades (response, market, since, limit);
    }

    parseBalance (response) {
        const result = { 'info': response };
        const codes = Object.keys (this.currencies);
        for (let i = 0; i < codes.length; i++) {
            const code = codes[i];
            const currency = this.currency (code);
            const currencyId = currency['id'];
            const account = this.account ();
            account['free'] = this.safeString (response, currencyId + '_available');
            account['used'] = this.safeString (response, currencyId + '_reserved');
            account['total'] = this.safeString (response, currencyId + '_balance');
            result[code] = account;
        }
        return this.safeBalance (result);
    }

    async fetchBalance (params = {}) {
        const response = await this.privatePostBalance (params);
        return this.parseBalance (response);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        if (type !== 'limit') {
            throw new ExchangeError (this.id + ' ' + this.version + ' accepts limit orders only');
        }
        if (symbol !== 'BTC/USD') {
            throw new ExchangeError (this.id + ' v1 supports BTC/USD orders only');
        }
        await this.loadMarkets ();
        const method = 'privatePost' + this.capitalize (side);
        const request = {
            'amount': amount,
            'price': price,
        };
        const response = await this[method] (this.extend (request, params));
        const id = this.safeString (response, 'id');
        return {
            'info': response,
            'id': id,
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        return await this.privatePostCancelOrder ({ 'id': id });
    }

    parseOrderStatus (status) {
        const statuses = {
            'In Queue': 'open',
            'Open': 'open',
            'Finished': 'closed',
            'Canceled': 'canceled',
        };
        return this.safeString (statuses, status, status);
    }

    async fetchOrderStatus (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {
            'id': id,
        };
        const response = await this.privatePostOrderStatus (this.extend (request, params));
        return this.parseOrderStatus (response);
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = undefined;
        if (symbol !== undefined) {
            market = this.market (symbol);
        }
        const pair = market ? market['id'] : 'all';
        const request = {
            'id': pair,
        };
        const response = await this.privatePostOpenOrdersId (this.extend (request, params));
        return this.parseTrades (response, market, since, limit);
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        throw new NotSupported (this.id + ' fetchOrder is not implemented yet');
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'public') {
            if (Object.keys (query).length) {
                url += '?' + this.urlencode (query);
            }
        } else {
            this.checkRequiredCredentials ();
            const nonce = this.nonce ().toString ();
            const auth = nonce + this.uid + this.apiKey;
            const signature = this.encode (this.hmac (this.encode (auth), this.encode (this.secret)));
            query = this.extend ({
                'key': this.apiKey,
                'signature': signature.toUpperCase (),
                'nonce': nonce,
            }, query);
            body = this.urlencode (query);
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
        if (response === undefined) {
            return;
        }
        const status = this.safeString (response, 'status');
        if (status === 'error') {
            throw new ExchangeError (this.id + ' ' + this.json (response));
        }
    }
};
<|endoftext|>


<commit_before>var Bookmark = (function($, Handlebars) {
    'use strict';

    var _onToggle = function($sidebar, isActive) {
            if (isActive)
                $sidebar.removeAttribute(_removeBrackets(s.sidebar.open));
            else
                $sidebar.setAttribute(_removeBrackets(s.sidebar.open), "true");

            return;
        },

        _addToggleListenerCallback = function() {
            if (typeof s.sidebar.onToggle == "function") {
                var $sidebar = document.querySelector(s.sidebar.container);
                var isActive = _isSidebarActive($sidebar);
                s.sidebar.onToggle($sidebar, isActive);
            }
        };

    var s = {
            element: "[bookmark]",
            storage: "bookmarks",
            activeAttr: "bookmark-active",
            sidebar: {
                template: 'views/sidebar.hbs',
                container: '[bookmarks-sidebar]',
                open: '[bookmarks-open]',
                toggle: '[bookmarks-toggle]',
                remove: '[bookmark-remove]',
                onToggle: _onToggle
            }
        },

        _add = function(item, group, id) {

            var bookmarks = _getStorage(s.storage);
            bookmarks = !bookmarks ? {
                [group]: []
            } : bookmarks;

            if (!bookmarks[group])
                bookmarks[group] = [];

            if (_exists(bookmarks[group], id) === -1)
                bookmarks[group].push(id);

            _setStorage(s.storage, bookmarks);
            item.setAttribute(s.activeAttr, "true");

            return bookmarks;
        },

        _remove = function(item, group, id) {
            var bookmarks = _getStorage(s.storage);
            var index = _exists(bookmarks[group], id);

            if (index !== -1) {
                bookmarks[group].splice(index, 1);
                item.removeAttribute(s.activeAttr);
                _setStorage(s.storage, bookmarks);
            }

            return bookmarks;
        },

        _getStorage = function(storage) {
            var data = localStorage.getItem(storage);
            return data ? JSON.parse(data) : false;
        },

        _setStorage = function(storage, data) {
            var json = JSON.stringify(data);
            return data ? localStorage.setItem(storage, json) : false;
        },

        _exists = function(bookmarks, id) {
            if (Array.isArray(bookmarks) && id) {
                var index = bookmarks.indexOf(id);
                return index;
            }

            return -1;
        },

        _addClickListenerList = function(list, callback) {
            var bookmarks = _getStorage(s.storage);

            for (var i = 0; i < list.length; i++) {
                var item = list[i];

                /**
                 * @dica
                 * usando closure ("fechamento") para gerar uma função anonima
                 * que cria uma nova referencia para item
                 */

                (function(item) {
                    var id = item.getAttribute("data-id");
                    var group = item.getAttribute("data-group");

                    if (/^[0-9]+$/.test(id) && bookmarks[group] && _exists(bookmarks[group], id) !== -1)
                        item.setAttribute(_removeBrackets(s.activeAttr), "true");

                    item.addEventListener('click', function() { callback(item, group, id); });
                })(item);
            }
            return;
        },

        _addToggleListenerList = function(callback) {

            var toggleElements = document.querySelectorAll(s.sidebar.toggle);

            for (var i = 0; i < toggleElements.length; i++) {
                var $element = toggleElements[i];

                (function($element) {
                    $element.addEventListener('click', function() { callback($element); });
                })($element);
            }
        },

        _filter = function(list) {
            list = list || undefined;
            var filtered = false;

            if (Array.isArray(list)) {
                var filtered = list.filter(function(item) {
                    return item.hasAttribute("data-group") && item.hasAttribute("data-id");
                });
            }
            return filtered;
        },

        _isActiveBookmark = function($item) {
            return ($item.hasAttribute(s.activeAttr) ? true : false);
        },

        _getAsyncRequest = function(url, callback) {
            var xmlhttp = new XMLHttpRequest();

            var done = function() {
                if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                    if (typeof callback == "function")
                        callback(xmlhttp.responseText);
                }
            };

            xmlhttp.onreadystatechange = done;
            xmlhttp.open("GET", url, true);
            xmlhttp.send(null);
        },

        _loadSidebar = function(bookmarks) {
            _getAsyncRequest(s.sidebar.template, function(template) {

                if (!template)
                    console.log("Template não encontrado :(");

                var $el = document.querySelector(s.sidebar.container);

                if ($el) {

                    $el.innerHTML = Handlebars.compile(template)(bookmarks);

                    _addToggleListenerList(_addToggleListenerCallback);
                }

                return;
            });
        },

        _isSidebarActive = function($sidebar) {
            return $sidebar.hasAttribute(_removeBrackets(s.sidebar.open)) ? true : false;
        },

        _removeBrackets = function(string) {
            return string.replace(/^[\[.#]+|[\].#]+$/g, "");
        },

        _init = function(options, afterLoad, itemClick) {

            // o unico motivo de usar jquery é a linha abaixo.
            s = $.extend(true, {}, s, options);

            var elements = document.querySelectorAll(s.element),
                filtered;

            //fix to convert a nodeList to Array
            elements = Array.prototype.slice.call(elements);

            filtered = _filter(elements);

            //adiciona o listner de click para cada objeto reconhecido como favorito
            _addClickListenerList(filtered, function($item, group, id) {
                var bookmarks = _isActiveBookmark($item) ? _remove(item, group, id) : _add(item, group, id);

                _loadSidebar(bookmarks);
                itemClick(item, _isActiveBookmark(item), bookmarks);

                return;
            });

            var bookmarks = _getStorage(s.storage);

            if (s.sidebar) {

                _loadSidebar(bookmarks);

                _addToggleListenerList(_addToggleListenerCallback);
            }

            afterLoad(bookmarks);

        };

    return {
        init: _init
    }

})(jQuery, Handlebars);
<commit_msg>alterando variável item para $item<commit_after>var Bookmark = (function($, Handlebars) {
    'use strict';

    var _onToggle = function($sidebar, isActive) {
            if (isActive)
                $sidebar.removeAttribute(_removeBrackets(s.sidebar.open));
            else
                $sidebar.setAttribute(_removeBrackets(s.sidebar.open), "true");

            return;
        },

        _addToggleListenerCallback = function() {
            if (typeof s.sidebar.onToggle == "function") {
                var $sidebar = document.querySelector(s.sidebar.container);
                var isActive = _isSidebarActive($sidebar);
                s.sidebar.onToggle($sidebar, isActive);
            }
        };

    var s = {
            element: "[bookmark]",
            storage: "bookmarks",
            activeAttr: "bookmark-active",
            sidebar: {
                template: 'views/sidebar.hbs',
                container: '[bookmarks-sidebar]',
                open: '[bookmarks-open]',
                toggle: '[bookmarks-toggle]',
                remove: '[bookmark-remove]',
                onToggle: _onToggle
            }
        },

        _add = function(item, group, id) {

            var bookmarks = _getStorage(s.storage);
            bookmarks = !bookmarks ? {
                [group]: []
            } : bookmarks;

            if (!bookmarks[group])
                bookmarks[group] = [];

            if (_exists(bookmarks[group], id) === -1)
                bookmarks[group].push(id);

            _setStorage(s.storage, bookmarks);
            item.setAttribute(s.activeAttr, "true");

            return bookmarks;
        },

        _remove = function(item, group, id) {
            var bookmarks = _getStorage(s.storage);
            var index = _exists(bookmarks[group], id);

            if (index !== -1) {
                bookmarks[group].splice(index, 1);
                item.removeAttribute(s.activeAttr);
                _setStorage(s.storage, bookmarks);
            }

            return bookmarks;
        },

        _getStorage = function(storage) {
            var data = localStorage.getItem(storage);
            return data ? JSON.parse(data) : false;
        },

        _setStorage = function(storage, data) {
            var json = JSON.stringify(data);
            return data ? localStorage.setItem(storage, json) : false;
        },

        _exists = function(bookmarks, id) {
            if (Array.isArray(bookmarks) && id) {
                var index = bookmarks.indexOf(id);
                return index;
            }

            return -1;
        },

        _addClickListenerList = function(list, callback) {
            var bookmarks = _getStorage(s.storage);

            for (var i = 0; i < list.length; i++) {
                var $item = list[i];

                /**
                 * @dica
                 * usando closure ("fechamento") para gerar uma função anonima
                 * que cria uma nova referencia para $item
                 */

                (function($item) {
                    var id = $item.getAttribute("data-id");
                    var group = $item.getAttribute("data-group");

                    if (/^[0-9]+$/.test(id) && bookmarks[group] && _exists(bookmarks[group], id) !== -1)
                        $item.setAttribute(_removeBrackets(s.activeAttr), "true");

                    $item.addEventListener('click', function() { callback($item, group, id); });
                })($item);
            }
            return;
        },

        _addToggleListenerList = function(callback) {

            var toggleElements = document.querySelectorAll(s.sidebar.toggle);

            for (var i = 0; i < toggleElements.length; i++) {
                var $element = toggleElements[i];

                (function($element) {
                    $element.addEventListener('click', function() { callback($element); });
                })($element);
            }
        },

        _filter = function(list) {
            list = list || undefined;
            var filtered = false;

            if (Array.isArray(list)) {
                var filtered = list.filter(function($item) {
                    return $item.hasAttribute("data-group") && $item.hasAttribute("data-id");
                });
            }
            return filtered;
        },

        _isActiveBookmark = function($item) {
            return ($item.hasAttribute(s.activeAttr) ? true : false);
        },

        _getAsyncRequest = function(url, callback) {
            var xmlhttp = new XMLHttpRequest();

            var done = function() {
                if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                    if (typeof callback == "function")
                        callback(xmlhttp.responseText);
                }
            };

            xmlhttp.onreadystatechange = done;
            xmlhttp.open("GET", url, true);
            xmlhttp.send(null);
        },

        _loadSidebar = function(bookmarks) {
            _getAsyncRequest(s.sidebar.template, function(template) {

                if (!template)
                    console.log("Template não encontrado :(");

                var $el = document.querySelector(s.sidebar.container);

                if ($el) {

                    $el.innerHTML = Handlebars.compile(template)(bookmarks);

                    _addToggleListenerList(_addToggleListenerCallback);
                }

                return;
            });
        },

        _isSidebarActive = function($sidebar) {
            return $sidebar.hasAttribute(_removeBrackets(s.sidebar.open)) ? true : false;
        },

        _removeBrackets = function(string) {
            return string.replace(/^[\[.#]+|[\].#]+$/g, "");
        },

        _init = function(options, afterLoad, itemClick) {

            // o unico motivo de usar jquery é a linha abaixo.
            s = $.extend(true, {}, s, options);

            var elements = document.querySelectorAll(s.element),
                filtered;

            //fix to convert a nodeList to Array
            elements = Array.prototype.slice.call(elements);

            filtered = _filter(elements);

            //adiciona o listner de click para cada objeto reconhecido como favorito
            _addClickListenerList(filtered, function($item, group, id) {
                var bookmarks = _isActiveBookmark($item) ? _remove($item, group, id) : _add($item, group, id);

                _loadSidebar(bookmarks);
                itemClick($item, _isActiveBookmark($item), bookmarks);

                return;
            });

            var bookmarks = _getStorage(s.storage);

            if (s.sidebar) {

                _loadSidebar(bookmarks);

                _addToggleListenerList(_addToggleListenerCallback);
            }

            afterLoad(bookmarks);

        };

    return {
        init: _init
    }

})(jQuery, Handlebars);
<|endoftext|>


<commit_before>import {Hooks} from 'PluginSDK';
import Service from '../structs/Service';
import ValidatorUtil from '../utils/ValidatorUtil';
import VolumeConstants from '../constants/VolumeConstants';

const getFindPropertiesRecursive = function (service, item) {

  return Object.keys(item).reduce(function (memo, subItem) {

    if (item[subItem].type === 'group') {
      Object.keys(item[subItem].properties).forEach(function (key) {
        memo[key] = item[subItem].properties[key].default;

        if (item[subItem].properties[key].getter &&
          !!item[subItem].properties[key].getter(service)) {
          memo[key] = item[subItem].properties[key].getter(service);
        }
      });

      return memo;
    }

    if (item[subItem].type === 'object') {
      memo[subItem] = getFindPropertiesRecursive(service, item[subItem].properties);

      return memo;
    }

    memo[subItem] = item[subItem].default;

    if (item[subItem].getter) {
      memo[subItem] = item[subItem].getter(service);
    }

    return memo;
  }, {});
};

// Removes redundant attributes
const pruneHealthCheckAttributes = function (healthCheckSchema, healthCheck) {
  let properties = healthCheckSchema
    .properties
    .healthChecks
    .itemShape
    .properties;

  return Object.keys(properties).reduce(function (memo, prop) {
    if (!properties[prop].shouldShow
      || properties[prop].shouldShow(healthCheck)) {

      if (prop === 'command') {
        memo[prop] = {value: healthCheck[prop]};

        return memo;
      }

      memo[prop] = healthCheck[prop];
    }

    return memo;
  }, {});
};

const ServiceUtil = {
  createServiceFromFormModel: function (formModel, schema, isEdit = false, definition = {}) {

    if (formModel != null) {
      let {
        general,
        optional,
        containerSettings,
        environmentVariables,
        labels,
        volumes,
        networking,
        healthChecks
      } = formModel;

      if (general != null) {
        definition.id = general.id;
        definition.cmd = general.cmd;
        definition.cpus = general.cpus;
        definition.mem = general.mem;
        definition.disk = general.disk;
        definition.instances = general.instances;
      }

      if (optional != null) {
        definition.executor = optional.executor;
        definition.fetch = optional.uris &&
          optional.uris.split(',')
            .map(function (uri) {
              return {uri: uri.trim()};
            });
        definition.constraints = optional.constraints &&
          optional.constraints.split(',')
            .map(function (item) {
              return item.split(':');
            });
        definition.acceptedResourceRoles =
          optional.acceptedResourceRoles &&
            optional.acceptedResourceRoles.split(',')
              .map(function (item) {
                return item.trim();
              });
        definition.user = optional.user;
      }

      if (containerSettings != null && containerSettings.image != null) {
        definition.container = {
          docker: {
            image: containerSettings.image
          }
        };
        if (containerSettings.forcePullImage != null) {
          definition.container.docker.forcePullImage =
            containerSettings.forcePullImage;
        }
        if (containerSettings.privileged != null) {
          definition.container.docker.privileged =
            containerSettings.privileged;
        }
        if (containerSettings.parameters != null) {
          definition.container.docker.parameters =
            containerSettings.parameters;
        }
      }

      if (volumes != null) {
        let type = VolumeConstants.type.MESOS;
        let volumesList = [];

        if (definition.container == null) {
          definition.container = {};
        }

        if (definition.container.docker &&
          definition.container.docker.image) {

          type = VolumeConstants.type.DOCKER;

          if (volumes.dockerVolumes) {
            volumesList = volumesList.concat(
              volumes.dockerVolumes
              .filter(function ({containerPath, hostPath}) {
                return containerPath != null && hostPath != null;
              })
              .map(function ({containerPath, hostPath, mode}) {
                return {
                  containerPath,
                  hostPath,
                  mode: VolumeConstants.mode[mode]
                };
              })
            );
          }
        }

        if (volumes.externalVolumes) {
          let externalVolumes = volumes.externalVolumes
            .map(function ({containerPath, externalName}) {
              return {
                containerPath: containerPath,
                external: {
                  name: externalName,
                  provider: 'dvdi',
                  options: {
                    'dvdi/driver': 'rexray'
                  }
                },
                mode: 'RW'
              };
            });

          if (externalVolumes.length) {
            volumesList = volumesList.concat(externalVolumes);
          }
          if (!isEdit && volumesList.length) {
            definition.updateStrategy = {
              maximumOverCapacity: 0,
              minimumHealthCapacity: 0
            };
          }
        }

        if (volumes.localVolumes) {
          let localVolumes = volumes.localVolumes
            .map(function ({containerPath, size}) {
              return {
                containerPath,
                persistent: {size},
                mode: VolumeConstants.mode.rw
              };
            });

          if (localVolumes.length) {
            volumesList = volumesList.concat(localVolumes);
            if (!isEdit && volumesList.length) {
              definition.updateStrategy = {
                maximumOverCapacity: 0,
                minimumHealthCapacity: 0
              };
              definition.residency = {
                relaunchEscalationTimeoutSeconds: 10,
                taskLostBehavior: 'WAIT_FOREVER'
              };
            }
          }
        }

        if (volumesList.length) {
          definition.container.type = type;
          definition.container.volumes = volumesList;
        }
      }

      if (labels != null && labels.labels != null) {
        definition.labels = labels.labels.reduce(function (memo, item) {
          memo[item.key] = item.value;

          return memo;
        }, {});
      }

      if (healthChecks != null && healthChecks.healthChecks != null) {
        definition.healthChecks = healthChecks.healthChecks
          .reduce(function (memo, healthCheck) {
            // Only set defaults if user has changed a value in the form.
            // I.e. user has intent to create a healthCheck.
            let hasSetValue = Object.values(healthCheck).some(function (value) {
              return value != null && value !== false;
            });

            if (hasSetValue) {
              if (healthCheck.portType == null) {
                healthCheck.portType = 'PORT_INDEX';
              }
              if (healthCheck.protocol == null) {
                healthCheck.protocol = 'HTTP';
              }

              memo.push(
                pruneHealthCheckAttributes(
                  schema.properties.healthChecks, healthCheck
                )
              );
            }

            return memo;
          }, []);
      }

      if (environmentVariables != null && environmentVariables.environmentVariables != null) {
        definition.env = environmentVariables.environmentVariables
          .reduce(function (variableMap, variable) {

            // The 'undefined' value is not rendered by the JSON.stringify,
            // so make sure empty environment variables are not left unrendered
            let value = variable.value;
            if (value == null) {
              value = '';
            }

            // Pass it through the registered plugins
            variableMap[variable.key] = Hooks.applyFilter(
              'serviceVariableValue',
              value,
              variable,
              definition
            );

            return variableMap;
          }, {});
      }

      if (networking != null) {
        let isContainerApp = containerSettings != null && containerSettings.image != null;

        let networkType = networking.networkType || 'host';

        if (networking.ports != null) {
          networking.ports = networking.ports.filter(function (port) {
            return port.name != null || port.lbPort != null || port.discovery;
          });
        }

        if (networking.ports != null && networking.ports.length) {
          if (networkType === 'host' || !isContainerApp) {
            // Avoid specifying an empty portDefinitions by default
            if (networking.ports.length) {
              definition.portDefinitions = networking.ports.map(function (port, index) {
                let portMapping = {protocol: 'tcp'};
                // Ensure that lbPort is an int
                let lbPort = parseInt(port.lbPort || 0, 10);

                if (networkType === 'host') {
                  portMapping.port = 0;
                }
                if (port.discovery === true) {
                  if (networkType === 'host') {
                    portMapping.port = lbPort;
                  }
                  if (general != null) {
                    portMapping.labels = {};
                    portMapping.labels[`VIP_${index}`] = `${general.id}:${lbPort}`;
                  }
                } else if (lbPort != null && /^\d*$/.test(lbPort) ) {
                  portMapping.port = lbPort;
                }
                if (port.protocol != null) {
                  portMapping.protocol = port.protocol;
                }
                if (port.name != null) {
                  portMapping.name = port.name;
                }

                return portMapping;
              });
            }
          } else {
            definition.container.docker.portMappings = [];
            networking.ports.forEach(function (port, index) {
              let portMapping = {containerPort: 0, protocol: 'tcp'};

              if (port.protocol != null) {
                portMapping.protocol = port.protocol;
              }
              if (port.name != null) {
                portMapping.name = port.name;
              }
              let lbPort = parseInt(port.lbPort || 0, 10);
              portMapping.containerPort = lbPort;

              // Needs to be removed?
              // if (networkType === 'bridge') {
              //   portMapping.hostPort = lbPort;
              // }
              if (port.discovery === true) {

                if (networkType !== 'bridge') {
                  portMapping.servicePort = lbPort;
                }
                portMapping.labels = {};
                if (general != null) {
                  portMapping.labels[`VIP_${index}`] = `${general.id}:${lbPort}`;
                }
              }

              if (['host', 'bridge'].includes(networkType)) {
                definition.container.docker.portMappings.push(portMapping);
              }

              if (!['host', 'bridge'].includes(networkType)) {

                if (port.expose) {
                  portMapping.hostPort = 0;
                }
                definition.container.docker.portMappings.push(portMapping);
                // TODO - Add portDefinition to discovery field
              }
            });
          }
        }

        if (isContainerApp) {
          if (networkType === 'host') {
            definition.container.docker.network = 'HOST';
          } else if (networkType === 'bridge') {
            definition.container.docker.network = 'BRIDGE';
          } else {
            definition.container.docker.network = 'USER';
            definition.ipAddress = {networkName: networkType};
          }
        }
      }
    }

    definition = Object.keys(definition).reduce(function (memo, key) {
      if (!ValidatorUtil.isEmpty(definition[key])) {
        memo[key] = definition[key];
      } else {
        memo[key] = null;
      }

      return memo;
    }, {});

    return new Service(definition);
  },

  createFormModelFromSchema: function (schema, service = new Service()) {

    return getFindPropertiesRecursive(service, schema.properties);
  },

  getAppDefinitionFromService: function (service) {

    let appDefinition = JSON.parse(service.toJSON());

    // General
    appDefinition.id = service.getId();
    appDefinition.cpus = service.getCpus();
    appDefinition.mem = service.getMem();
    appDefinition.disk = service.getDisk();
    appDefinition.instances = service.getInstancesCount();
    appDefinition.cmd = service.getCommand();

    // Optional
    appDefinition.executor = service.getExecutor();
    appDefinition.fetch = service.getFetch();
    appDefinition.constraints = service.getConstraints();
    appDefinition.acceptedResourceRoles = service.getAcceptedResourceRoles();
    appDefinition.user = service.getUser();
    appDefinition.labels = service.getLabels();
    appDefinition.healthChecks = service.getHealthChecks();

    let containerSettings = service.getContainerSettings();
    if (containerSettings &&
      (containerSettings.docker &&
      containerSettings.docker.image ||
      containerSettings.type === VolumeConstants.type.MESOS)
    ) {
      appDefinition.container = containerSettings;

      if (appDefinition.container.type === VolumeConstants.type.MESOS) {
        delete(appDefinition.container.docker);
      }
    }

    appDefinition.updateStrategy = service.getUpdateStrategy();
    appDefinition.residency = service.getResidency();
    appDefinition.ipAddress = service.getIpAddress();

    // Environment Variables
    appDefinition.env = service.getEnvironmentVariables();

    appDefinition.portDefinitions = service.getPortDefinitions();

    Hooks.applyFilter(
      'serviceToAppDefinition',
      appDefinition,
      service
    );

    return appDefinition;
  },

  convertServiceLabelsToArray: function (service) {
    if (!(service instanceof Service)) {
      return [];
    }

    let labels = service.getLabels();
    if (labels == null) {
      return [];
    }

    return Object.keys(labels).map(key => ({key, value: labels[key]}));
  }
};

module.exports = ServiceUtil;
<commit_msg>Fix env var undefined bug<commit_after>import {Hooks} from 'PluginSDK';
import Service from '../structs/Service';
import ValidatorUtil from '../utils/ValidatorUtil';
import VolumeConstants from '../constants/VolumeConstants';

const getFindPropertiesRecursive = function (service, item) {

  return Object.keys(item).reduce(function (memo, subItem) {

    if (item[subItem].type === 'group') {
      Object.keys(item[subItem].properties).forEach(function (key) {
        memo[key] = item[subItem].properties[key].default;

        if (item[subItem].properties[key].getter &&
          !!item[subItem].properties[key].getter(service)) {
          memo[key] = item[subItem].properties[key].getter(service);
        }
      });

      return memo;
    }

    if (item[subItem].type === 'object') {
      memo[subItem] = getFindPropertiesRecursive(service, item[subItem].properties);

      return memo;
    }

    memo[subItem] = item[subItem].default;

    if (item[subItem].getter) {
      memo[subItem] = item[subItem].getter(service);
    }

    return memo;
  }, {});
};

// Removes redundant attributes
const pruneHealthCheckAttributes = function (healthCheckSchema, healthCheck) {
  let properties = healthCheckSchema
    .properties
    .healthChecks
    .itemShape
    .properties;

  return Object.keys(properties).reduce(function (memo, prop) {
    if (!properties[prop].shouldShow
      || properties[prop].shouldShow(healthCheck)) {

      if (prop === 'command') {
        memo[prop] = {value: healthCheck[prop]};

        return memo;
      }

      memo[prop] = healthCheck[prop];
    }

    return memo;
  }, {});
};

const ServiceUtil = {
  createServiceFromFormModel: function (formModel, schema, isEdit = false, definition = {}) {

    if (formModel != null) {
      let {
        general,
        optional,
        containerSettings,
        environmentVariables,
        labels,
        volumes,
        networking,
        healthChecks
      } = formModel;

      if (general != null) {
        definition.id = general.id;
        definition.cmd = general.cmd;
        definition.cpus = general.cpus;
        definition.mem = general.mem;
        definition.disk = general.disk;
        definition.instances = general.instances;
      }

      if (optional != null) {
        definition.executor = optional.executor;
        definition.fetch = optional.uris &&
          optional.uris.split(',')
            .map(function (uri) {
              return {uri: uri.trim()};
            });
        definition.constraints = optional.constraints &&
          optional.constraints.split(',')
            .map(function (item) {
              return item.split(':');
            });
        definition.acceptedResourceRoles =
          optional.acceptedResourceRoles &&
            optional.acceptedResourceRoles.split(',')
              .map(function (item) {
                return item.trim();
              });
        definition.user = optional.user;
      }

      if (containerSettings != null && containerSettings.image != null) {
        definition.container = {
          docker: {
            image: containerSettings.image
          }
        };
        if (containerSettings.forcePullImage != null) {
          definition.container.docker.forcePullImage =
            containerSettings.forcePullImage;
        }
        if (containerSettings.privileged != null) {
          definition.container.docker.privileged =
            containerSettings.privileged;
        }
        if (containerSettings.parameters != null) {
          definition.container.docker.parameters =
            containerSettings.parameters;
        }
      }

      if (volumes != null) {
        let type = VolumeConstants.type.MESOS;
        let volumesList = [];

        if (definition.container == null) {
          definition.container = {};
        }

        if (definition.container.docker &&
          definition.container.docker.image) {

          type = VolumeConstants.type.DOCKER;

          if (volumes.dockerVolumes) {
            volumesList = volumesList.concat(
              volumes.dockerVolumes
              .filter(function ({containerPath, hostPath}) {
                return containerPath != null && hostPath != null;
              })
              .map(function ({containerPath, hostPath, mode}) {
                return {
                  containerPath,
                  hostPath,
                  mode: VolumeConstants.mode[mode]
                };
              })
            );
          }
        }

        if (volumes.externalVolumes) {
          let externalVolumes = volumes.externalVolumes
            .map(function ({containerPath, externalName}) {
              return {
                containerPath: containerPath,
                external: {
                  name: externalName,
                  provider: 'dvdi',
                  options: {
                    'dvdi/driver': 'rexray'
                  }
                },
                mode: 'RW'
              };
            });

          if (externalVolumes.length) {
            volumesList = volumesList.concat(externalVolumes);
          }
          if (!isEdit && volumesList.length) {
            definition.updateStrategy = {
              maximumOverCapacity: 0,
              minimumHealthCapacity: 0
            };
          }
        }

        if (volumes.localVolumes) {
          let localVolumes = volumes.localVolumes
            .map(function ({containerPath, size}) {
              return {
                containerPath,
                persistent: {size},
                mode: VolumeConstants.mode.rw
              };
            });

          if (localVolumes.length) {
            volumesList = volumesList.concat(localVolumes);
            if (!isEdit && volumesList.length) {
              definition.updateStrategy = {
                maximumOverCapacity: 0,
                minimumHealthCapacity: 0
              };
              definition.residency = {
                relaunchEscalationTimeoutSeconds: 10,
                taskLostBehavior: 'WAIT_FOREVER'
              };
            }
          }
        }

        if (volumesList.length) {
          definition.container.type = type;
          definition.container.volumes = volumesList;
        }
      }

      if (labels != null && labels.labels != null) {
        definition.labels = labels.labels.reduce(function (memo, item) {
          memo[item.key] = item.value;

          return memo;
        }, {});
      }

      if (healthChecks != null && healthChecks.healthChecks != null) {
        definition.healthChecks = healthChecks.healthChecks
          .reduce(function (memo, healthCheck) {
            // Only set defaults if user has changed a value in the form.
            // I.e. user has intent to create a healthCheck.
            let hasSetValue = Object.values(healthCheck).some(function (value) {
              return value != null && value !== false;
            });

            if (hasSetValue) {
              if (healthCheck.portType == null) {
                healthCheck.portType = 'PORT_INDEX';
              }
              if (healthCheck.protocol == null) {
                healthCheck.protocol = 'HTTP';
              }

              memo.push(
                pruneHealthCheckAttributes(
                  schema.properties.healthChecks, healthCheck
                )
              );
            }

            return memo;
          }, []);
      }

      if (environmentVariables != null && environmentVariables.environmentVariables != null) {
        definition.env = environmentVariables.environmentVariables
          .reduce(function (variableMap, variable) {

            if (variable.key == null) {
              return variableMap;
            }

            // The 'undefined' value is not rendered by the JSON.stringify,
            // so make sure empty environment variables are not left unrendered
            let value = variable.value;
            if (value == null) {
              value = '';
            }

            // Pass it through the registered plugins
            variableMap[variable.key] = Hooks.applyFilter(
              'serviceVariableValue',
              value,
              variable,
              definition
            );

            return variableMap;
          }, {});
      }

      if (networking != null) {
        let isContainerApp = containerSettings != null && containerSettings.image != null;

        let networkType = networking.networkType || 'host';

        if (networking.ports != null) {
          networking.ports = networking.ports.filter(function (port) {
            return port.name != null || port.lbPort != null || port.discovery;
          });
        }

        if (networking.ports != null && networking.ports.length) {
          if (networkType === 'host' || !isContainerApp) {
            // Avoid specifying an empty portDefinitions by default
            if (networking.ports.length) {
              definition.portDefinitions = networking.ports.map(function (port, index) {
                let portMapping = {protocol: 'tcp'};
                // Ensure that lbPort is an int
                let lbPort = parseInt(port.lbPort || 0, 10);

                if (networkType === 'host') {
                  portMapping.port = 0;
                }
                if (port.discovery === true) {
                  if (networkType === 'host') {
                    portMapping.port = lbPort;
                  }
                  if (general != null) {
                    portMapping.labels = {};
                    portMapping.labels[`VIP_${index}`] = `${general.id}:${lbPort}`;
                  }
                } else if (lbPort != null && /^\d*$/.test(lbPort) ) {
                  portMapping.port = lbPort;
                }
                if (port.protocol != null) {
                  portMapping.protocol = port.protocol;
                }
                if (port.name != null) {
                  portMapping.name = port.name;
                }

                return portMapping;
              });
            }
          } else {
            definition.container.docker.portMappings = [];
            networking.ports.forEach(function (port, index) {
              let portMapping = {containerPort: 0, protocol: 'tcp'};

              if (port.protocol != null) {
                portMapping.protocol = port.protocol;
              }
              if (port.name != null) {
                portMapping.name = port.name;
              }
              let lbPort = parseInt(port.lbPort || 0, 10);
              portMapping.containerPort = lbPort;

              // Needs to be removed?
              // if (networkType === 'bridge') {
              //   portMapping.hostPort = lbPort;
              // }
              if (port.discovery === true) {

                if (networkType !== 'bridge') {
                  portMapping.servicePort = lbPort;
                }
                portMapping.labels = {};
                if (general != null) {
                  portMapping.labels[`VIP_${index}`] = `${general.id}:${lbPort}`;
                }
              }

              if (['host', 'bridge'].includes(networkType)) {
                definition.container.docker.portMappings.push(portMapping);
              }

              if (!['host', 'bridge'].includes(networkType)) {

                if (port.expose) {
                  portMapping.hostPort = 0;
                }
                definition.container.docker.portMappings.push(portMapping);
                // TODO - Add portDefinition to discovery field
              }
            });
          }
        }

        if (isContainerApp) {
          if (networkType === 'host') {
            definition.container.docker.network = 'HOST';
          } else if (networkType === 'bridge') {
            definition.container.docker.network = 'BRIDGE';
          } else {
            definition.container.docker.network = 'USER';
            definition.ipAddress = {networkName: networkType};
          }
        }
      }
    }

    definition = Object.keys(definition).reduce(function (memo, key) {
      if (!ValidatorUtil.isEmpty(definition[key])) {
        memo[key] = definition[key];
      } else {
        memo[key] = null;
      }

      return memo;
    }, {});

    return new Service(definition);
  },

  createFormModelFromSchema: function (schema, service = new Service()) {

    return getFindPropertiesRecursive(service, schema.properties);
  },

  getAppDefinitionFromService: function (service) {

    let appDefinition = JSON.parse(service.toJSON());

    // General
    appDefinition.id = service.getId();
    appDefinition.cpus = service.getCpus();
    appDefinition.mem = service.getMem();
    appDefinition.disk = service.getDisk();
    appDefinition.instances = service.getInstancesCount();
    appDefinition.cmd = service.getCommand();

    // Optional
    appDefinition.executor = service.getExecutor();
    appDefinition.fetch = service.getFetch();
    appDefinition.constraints = service.getConstraints();
    appDefinition.acceptedResourceRoles = service.getAcceptedResourceRoles();
    appDefinition.user = service.getUser();
    appDefinition.labels = service.getLabels();
    appDefinition.healthChecks = service.getHealthChecks();

    let containerSettings = service.getContainerSettings();
    if (containerSettings &&
      (containerSettings.docker &&
      containerSettings.docker.image ||
      containerSettings.type === VolumeConstants.type.MESOS)
    ) {
      appDefinition.container = containerSettings;

      if (appDefinition.container.type === VolumeConstants.type.MESOS) {
        delete(appDefinition.container.docker);
      }
    }

    appDefinition.updateStrategy = service.getUpdateStrategy();
    appDefinition.residency = service.getResidency();
    appDefinition.ipAddress = service.getIpAddress();

    // Environment Variables
    appDefinition.env = service.getEnvironmentVariables();

    appDefinition.portDefinitions = service.getPortDefinitions();

    Hooks.applyFilter(
      'serviceToAppDefinition',
      appDefinition,
      service
    );

    return appDefinition;
  },

  convertServiceLabelsToArray: function (service) {
    if (!(service instanceof Service)) {
      return [];
    }

    let labels = service.getLabels();
    if (labels == null) {
      return [];
    }

    return Object.keys(labels).map(key => ({key, value: labels[key]}));
  }
};

module.exports = ServiceUtil;
<|endoftext|>