<commit_before>'use babel';
'use strict';

import EventEmitter from 'events';
import stringArgv from 'string-argv';
import { stripIndents } from 'common-tags';
import escapeRegex from 'escape-string-regexp';
import FriendlyError from '../errors/friendly';

export default class CommandDispatcher extends EventEmitter {
	constructor(bot) {
		super();
		if(!bot) throw new Error('A bot must be specified.');
		this.bot = bot;
		this._serverCommandPatterns = {};
		this._results = {};
	}

	async handleMessage(message, oldMessage = null) {
		if(message.author.equals(this.bot.client.user)) return;

		// Make sure the bot is allowed to run in the channel, or the user is an admin
		if(message.server && !this.bot.storage.allowedChannels.isEmpty(message.server)
			&& !this.bot.storage.allowedChannels.exists(message.server, message.channel)
			&& !this.bot.permissions.isAdmin(message.server, message.author)) return;

		// Parse the message, and get the old result if it exists
		const [command, args, fromPattern, isCommandMessage] = this.parseMessage(message);
		const oldResult = oldMessage ? this._results[oldMessage.id] : null;

		// Run the command, or make an error message result
		let result;
		if(command) {
			if(!oldMessage || oldResult) result = this.constructor.makeResultObject(await this.run(command, args, fromPattern, message));
		} else if(isCommandMessage) {
			result = { reply: [`Unknown command. Use ${this.bot.util.usage('help', message.server)} to view the list of all commands.`], editable: true };
		} else if(this.bot.config.values.nonCommandEdit) {
			result = {};
		}

		if(result) {
			// Change a plain or reply response into direct if there isn't a server
			if(!message.server) {
				if(!result.direct) result.direct = result.plain || result.reply;
				delete result.plain;
				delete result.reply;
			}

			// Update old messages or send new ones
			if(oldResult && (oldResult.plain || oldResult.reply || oldResult.direct)) {
				await this.updateMessagesForResult(message, result, oldResult);
			} else {
				await this.sendMessagesForResult(message, result);
			}

			// Cache the result
			if(this.bot.config.values.commandEditable > 0) {
				if(result.editable) {
					result.timeout = oldResult && oldResult.timeout ? oldResult.timeout : setTimeout(() => { delete this._results[message.id]; }, this.bot.config.values.commandEditable * 1000);
					this._results[message.id] = result;
				} else {
					delete this._results[message.id];
				}
			}
		}
	}

	async run(command, args, fromPattern, message) {
		const logInfo = {
			args: String(args),
			user: `${message.author.username}#${message.author.discriminator}`,
			userID: message.author.id,
			server: message.server ? message.server.name : null,
			serverID: message.server ? message.server.id : null
		};

		// Make sure the command is usable
		if(command.serverOnly && !message.server) {
			this.bot.logger.info(`Not running ${command.group}:${command.groupName}; server only.`, logInfo);
			return `The \`${command.name}\` command must be used in a server channel.`;
		}
		if(command.isRunnable && !command.isRunnable(message)) {
			this.bot.logger.info(`Not running ${command.group}:${command.groupName}; not runnable.`, logInfo);
			return `You do not have permission to use the \`${command.name}\` command.`;
		}

		// Run the command
		this.bot.logger.info(`Running ${command.group}:${command.groupName}.`, logInfo);
		try {
			const result = await command.run(message, args, fromPattern);
			this.emit('commandRun', command, message, args, fromPattern, this.bot);
			return result;
		} catch(err) {
			if(err instanceof FriendlyError) {
				return err.message;
			} else {
				this.bot.logger.error(err);
				const owner = this.bot.config.values.owner ? message.client.users.get('id', this.bot.config.values.owner) : null;
				return stripIndents`
					An error occurred while running the command: \`${err.name}: ${err.message}\`
					${owner ? `Please contact ${owner.name}#${owner.discriminator}${this.bot.config.values.invite ? ` in this server: ${this.bot.config.values.invite}` : '.'}` : ''}
				`;
			}
		}
	}

	async sendMessagesForResult(message, result) {
		const messages = await Promise.all([
			result.plain ? this.sendMessages(message, result.plain, 'plain') : null,
			result.reply ? this.sendMessages(message, result.reply, 'reply') : null,
			result.direct ? this.sendMessages(message, result.direct, 'direct') : null
		]);
		if(result.plain) result.normalMessages = messages[0];
		else if(result.reply) result.normalMessages = messages[1];
		if(result.direct) result.directMessages = messages[2];
	}

	async sendMessages(message, contents, type) {
		const sentMessages = [];
		for(const content of contents) {
			if(type === 'plain') sentMessages.push(await message.client.sendMessage(message, content));
			else if(type === 'reply') sentMessages.push(await message.reply(content));
			else if(type === 'direct') sentMessages.push(await message.client.sendMessage(message.author, content));
		}
		return sentMessages;
	}

	async updateMessagesForResult(message, result, oldResult) {
		// Update the messages
		const messages = await Promise.all([
			result.plain || result.reply ? this.updateMessages(message, oldResult.normalMessages, result.plain ? result.plain : result.reply, result.plain ? 'plain' : 'reply') : null,
			result.direct ? oldResult.direct ? this.updateMessages(message, oldResult.directMessages, result.direct, 'direct') : this.sendMessages(message, result.direct, 'direct') : null
		]);
		if(result.plain || result.reply) result.normalMessages = messages[0];
		if(result.direct) result.directMessages = messages[1];

		// Delete old messages if we're not using them
		if(!result.plain && !result.reply && (oldResult.plain || oldResult.reply)) for(const msg of oldResult.normalMessages) msg.delete();
		if(!result.direct && oldResult.direct) for(const msg of oldResult.directMessages) msg.delete();
	}

	async updateMessages(message, oldMessages, contents, type) {
		const updatedMessages = [];

		// Update/send messages
		for(let i = 0; i < contents.length; i++) {
			if(i < oldMessages.length) updatedMessages.push(await oldMessages[i].update(type === 'reply' ? `${message.author}, ${contents[i]}` : contents[i]));
			else updatedMessages.push((await this.sendMessages(message, [contents[i]], type))[0]);
		}

		// Delete extra old messages
		if(oldMessages.length > contents.length) {
			for(let i = oldMessages.length - 1; i >= contents.length; i--) oldMessages[i].delete();
		}

		return updatedMessages;
	}

	parseMessage(message) {
		// Find the command to run by patterns
		for(const command of this.bot.registry.commands) {
			if(!command.patterns) continue;
			for(const pattern of command.patterns) {
				const matches = pattern.exec(message.content);
				if(matches) return [command, matches, true, true];
			}
		}

		// Find the command to run with default command handling
		const patternIndex = message.server ? message.server.id : '-';
		if(!this._serverCommandPatterns[patternIndex]) this._serverCommandPatterns[patternIndex] = this._buildCommandPattern(message.server, message.client.user);
		let [command, args, isCommandMessage] = this.matchDefault(message, this._serverCommandPatterns[patternIndex], 2);
		if(!command && !message.server) [command, args, isCommandMessage] = this.matchDefault(message, unprefixedCommandPattern);
		if(command) return [command, args, false, true];

		return [null, null, false, isCommandMessage];
	}

	matchDefault(message, pattern, commandNameIndex = 1) {
		const matches = pattern.exec(message.content);
		if(!matches) return [null, null, false];

		const commandName = matches[commandNameIndex].toLowerCase();
		const command = this.bot.registry.commands.find(cmd => cmd.name === commandName || (cmd.aliases && cmd.aliases.some(alias => alias === commandName)));
		if(!command || command.disableDefault) return [null, null, true];

		const argString = message.content.substring(matches[1].length + (matches[2] ? matches[2].length : 0));
		let args;
		if(!('argsType' in command) || command.argsType === 'single') {
			args = [argString.trim()];
		} else if(command.argsType === 'multiple') {
			if(command.argsCount) {
				if(command.argsCount < 2) throw new RangeError(`Command ${command.group}:${command.groupName} argsCount must be at least 2.`);
				args = [];
				const newlinesReplaced = argString.trim().replace(newlinesPattern, newlinesReplacement);
				const argv = stringArgv(newlinesReplaced);
				if(argv.length > 0) {
					for(let i = 0; i < command.argsCount - 1; i++) args.push(argv.shift());
					if(argv.length > 0) args.push(argv.join(' ').replace(newlinesReplacementPattern, '\n').replace(extraNewlinesPattern, '\n\n'));
				}
			} else {
				args = stringArgv(argString);
			}
		} else {
			throw new Error(`Command ${command.group}:${command.groupName} argsType is not one of 'single' or 'multiple'.`);
		}

		return [command, args, true];
	}

	static makeResultObject(result) {
		if(typeof result !== 'object' || Array.isArray(result)) result = { reply: result };
		if(result.plain && result.reply) throw new Error('The command result may contain either "plain" or "reply", not both.');
		if(result.plain && !Array.isArray(result.plain)) result.plain = [result.plain];
		if(result.reply && !Array.isArray(result.reply)) result.reply = [result.reply];
		if(result.direct && !Array.isArray(result.direct)) result.direct = [result.direct];
		if(!('editable' in result)) result.editable = true;
		return result;
	}

	/**
	 * Creates a regular expression to match the command prefix and name in a message
	 * @param {?Server} server - A Discord.js Server instance of the server that the message is from
	 * @param {User} user - The Discord.js User instance of the bot
	 * @return {RegExp} Regular expression that matches a command prefix and name
	 */
	_buildCommandPattern(server, user) {
		let prefix = server ? this.bot.storage.settings.getValue(server, 'command-prefix', this.bot.config.values.commandPrefix) : this.bot.config.values.commandPrefix;
		if(prefix === 'none') prefix = '';
		const escapedPrefix = escapeRegex(prefix);
		const prefixPatternPiece = prefix ? `${escapedPrefix}\\s*|` : '';
		const pattern = new RegExp(`^(${prefixPatternPiece}<@!?${user.id}>\\s+(?:${escapedPrefix})?)([^\\s]+)`, 'i');
		this.bot.logger.info(`Server command pattern built.`, {
			server: server ? server.name : null,
			serverID: server ? server.id : null,
			prefix: prefix, pattern: pattern.source
		});
		return pattern;
	}
}

const unprefixedCommandPattern = /^([^\s]+)/i;
const newlinesPattern = /\n/g;
const newlinesReplacement = '{!~NL~!}';
const newlinesReplacementPattern = new RegExp(newlinesReplacement, 'g');
const extraNewlinesPattern = /\n{3,}/g;
<commit_msg>Changed order of commandRun event params<commit_after>'use babel';
'use strict';

import EventEmitter from 'events';
import stringArgv from 'string-argv';
import { stripIndents } from 'common-tags';
import escapeRegex from 'escape-string-regexp';
import FriendlyError from '../errors/friendly';

export default class CommandDispatcher extends EventEmitter {
	constructor(bot) {
		super();
		if(!bot) throw new Error('A bot must be specified.');
		this.bot = bot;
		this._serverCommandPatterns = {};
		this._results = {};
	}

	async handleMessage(message, oldMessage = null) {
		if(message.author.equals(this.bot.client.user)) return;

		// Make sure the bot is allowed to run in the channel, or the user is an admin
		if(message.server && !this.bot.storage.allowedChannels.isEmpty(message.server)
			&& !this.bot.storage.allowedChannels.exists(message.server, message.channel)
			&& !this.bot.permissions.isAdmin(message.server, message.author)) return;

		// Parse the message, and get the old result if it exists
		const [command, args, fromPattern, isCommandMessage] = this.parseMessage(message);
		const oldResult = oldMessage ? this._results[oldMessage.id] : null;

		// Run the command, or make an error message result
		let result;
		if(command) {
			if(!oldMessage || oldResult) result = this.constructor.makeResultObject(await this.run(command, args, fromPattern, message));
		} else if(isCommandMessage) {
			result = { reply: [`Unknown command. Use ${this.bot.util.usage('help', message.server)} to view the list of all commands.`], editable: true };
		} else if(this.bot.config.values.nonCommandEdit) {
			result = {};
		}

		if(result) {
			// Change a plain or reply response into direct if there isn't a server
			if(!message.server) {
				if(!result.direct) result.direct = result.plain || result.reply;
				delete result.plain;
				delete result.reply;
			}

			// Update old messages or send new ones
			if(oldResult && (oldResult.plain || oldResult.reply || oldResult.direct)) {
				await this.updateMessagesForResult(message, result, oldResult);
			} else {
				await this.sendMessagesForResult(message, result);
			}

			// Cache the result
			if(this.bot.config.values.commandEditable > 0) {
				if(result.editable) {
					result.timeout = oldResult && oldResult.timeout ? oldResult.timeout : setTimeout(() => { delete this._results[message.id]; }, this.bot.config.values.commandEditable * 1000);
					this._results[message.id] = result;
				} else {
					delete this._results[message.id];
				}
			}
		}
	}

	async run(command, args, fromPattern, message) {
		const logInfo = {
			args: String(args),
			user: `${message.author.username}#${message.author.discriminator}`,
			userID: message.author.id,
			server: message.server ? message.server.name : null,
			serverID: message.server ? message.server.id : null
		};

		// Make sure the command is usable
		if(command.serverOnly && !message.server) {
			this.bot.logger.info(`Not running ${command.group}:${command.groupName}; server only.`, logInfo);
			return `The \`${command.name}\` command must be used in a server channel.`;
		}
		if(command.isRunnable && !command.isRunnable(message)) {
			this.bot.logger.info(`Not running ${command.group}:${command.groupName}; not runnable.`, logInfo);
			return `You do not have permission to use the \`${command.name}\` command.`;
		}

		// Run the command
		this.bot.logger.info(`Running ${command.group}:${command.groupName}.`, logInfo);
		try {
			const result = await command.run(message, args, fromPattern);
			this.emit('commandRun', command, this.bot, message, args, fromPattern);
			return result;
		} catch(err) {
			if(err instanceof FriendlyError) {
				return err.message;
			} else {
				this.bot.logger.error(err);
				const owner = this.bot.config.values.owner ? message.client.users.get('id', this.bot.config.values.owner) : null;
				return stripIndents`
					An error occurred while running the command: \`${err.name}: ${err.message}\`
					${owner ? `Please contact ${owner.name}#${owner.discriminator}${this.bot.config.values.invite ? ` in this server: ${this.bot.config.values.invite}` : '.'}` : ''}
				`;
			}
		}
	}

	async sendMessagesForResult(message, result) {
		const messages = await Promise.all([
			result.plain ? this.sendMessages(message, result.plain, 'plain') : null,
			result.reply ? this.sendMessages(message, result.reply, 'reply') : null,
			result.direct ? this.sendMessages(message, result.direct, 'direct') : null
		]);
		if(result.plain) result.normalMessages = messages[0];
		else if(result.reply) result.normalMessages = messages[1];
		if(result.direct) result.directMessages = messages[2];
	}

	async sendMessages(message, contents, type) {
		const sentMessages = [];
		for(const content of contents) {
			if(type === 'plain') sentMessages.push(await message.client.sendMessage(message, content));
			else if(type === 'reply') sentMessages.push(await message.reply(content));
			else if(type === 'direct') sentMessages.push(await message.client.sendMessage(message.author, content));
		}
		return sentMessages;
	}

	async updateMessagesForResult(message, result, oldResult) {
		// Update the messages
		const messages = await Promise.all([
			result.plain || result.reply ? this.updateMessages(message, oldResult.normalMessages, result.plain ? result.plain : result.reply, result.plain ? 'plain' : 'reply') : null,
			result.direct ? oldResult.direct ? this.updateMessages(message, oldResult.directMessages, result.direct, 'direct') : this.sendMessages(message, result.direct, 'direct') : null
		]);
		if(result.plain || result.reply) result.normalMessages = messages[0];
		if(result.direct) result.directMessages = messages[1];

		// Delete old messages if we're not using them
		if(!result.plain && !result.reply && (oldResult.plain || oldResult.reply)) for(const msg of oldResult.normalMessages) msg.delete();
		if(!result.direct && oldResult.direct) for(const msg of oldResult.directMessages) msg.delete();
	}

	async updateMessages(message, oldMessages, contents, type) {
		const updatedMessages = [];

		// Update/send messages
		for(let i = 0; i < contents.length; i++) {
			if(i < oldMessages.length) updatedMessages.push(await oldMessages[i].update(type === 'reply' ? `${message.author}, ${contents[i]}` : contents[i]));
			else updatedMessages.push((await this.sendMessages(message, [contents[i]], type))[0]);
		}

		// Delete extra old messages
		if(oldMessages.length > contents.length) {
			for(let i = oldMessages.length - 1; i >= contents.length; i--) oldMessages[i].delete();
		}

		return updatedMessages;
	}

	parseMessage(message) {
		// Find the command to run by patterns
		for(const command of this.bot.registry.commands) {
			if(!command.patterns) continue;
			for(const pattern of command.patterns) {
				const matches = pattern.exec(message.content);
				if(matches) return [command, matches, true, true];
			}
		}

		// Find the command to run with default command handling
		const patternIndex = message.server ? message.server.id : '-';
		if(!this._serverCommandPatterns[patternIndex]) this._serverCommandPatterns[patternIndex] = this._buildCommandPattern(message.server, message.client.user);
		let [command, args, isCommandMessage] = this.matchDefault(message, this._serverCommandPatterns[patternIndex], 2);
		if(!command && !message.server) [command, args, isCommandMessage] = this.matchDefault(message, unprefixedCommandPattern);
		if(command) return [command, args, false, true];

		return [null, null, false, isCommandMessage];
	}

	matchDefault(message, pattern, commandNameIndex = 1) {
		const matches = pattern.exec(message.content);
		if(!matches) return [null, null, false];

		const commandName = matches[commandNameIndex].toLowerCase();
		const command = this.bot.registry.commands.find(cmd => cmd.name === commandName || (cmd.aliases && cmd.aliases.some(alias => alias === commandName)));
		if(!command || command.disableDefault) return [null, null, true];

		const argString = message.content.substring(matches[1].length + (matches[2] ? matches[2].length : 0));
		let args;
		if(!('argsType' in command) || command.argsType === 'single') {
			args = [argString.trim()];
		} else if(command.argsType === 'multiple') {
			if(command.argsCount) {
				if(command.argsCount < 2) throw new RangeError(`Command ${command.group}:${command.groupName} argsCount must be at least 2.`);
				args = [];
				const newlinesReplaced = argString.trim().replace(newlinesPattern, newlinesReplacement);
				const argv = stringArgv(newlinesReplaced);
				if(argv.length > 0) {
					for(let i = 0; i < command.argsCount - 1; i++) args.push(argv.shift());
					if(argv.length > 0) args.push(argv.join(' ').replace(newlinesReplacementPattern, '\n').replace(extraNewlinesPattern, '\n\n'));
				}
			} else {
				args = stringArgv(argString);
			}
		} else {
			throw new Error(`Command ${command.group}:${command.groupName} argsType is not one of 'single' or 'multiple'.`);
		}

		return [command, args, true];
	}

	static makeResultObject(result) {
		if(typeof result !== 'object' || Array.isArray(result)) result = { reply: result };
		if(result.plain && result.reply) throw new Error('The command result may contain either "plain" or "reply", not both.');
		if(result.plain && !Array.isArray(result.plain)) result.plain = [result.plain];
		if(result.reply && !Array.isArray(result.reply)) result.reply = [result.reply];
		if(result.direct && !Array.isArray(result.direct)) result.direct = [result.direct];
		if(!('editable' in result)) result.editable = true;
		return result;
	}

	/**
	 * Creates a regular expression to match the command prefix and name in a message
	 * @param {?Server} server - A Discord.js Server instance of the server that the message is from
	 * @param {User} user - The Discord.js User instance of the bot
	 * @return {RegExp} Regular expression that matches a command prefix and name
	 */
	_buildCommandPattern(server, user) {
		let prefix = server ? this.bot.storage.settings.getValue(server, 'command-prefix', this.bot.config.values.commandPrefix) : this.bot.config.values.commandPrefix;
		if(prefix === 'none') prefix = '';
		const escapedPrefix = escapeRegex(prefix);
		const prefixPatternPiece = prefix ? `${escapedPrefix}\\s*|` : '';
		const pattern = new RegExp(`^(${prefixPatternPiece}<@!?${user.id}>\\s+(?:${escapedPrefix})?)([^\\s]+)`, 'i');
		this.bot.logger.info(`Server command pattern built.`, {
			server: server ? server.name : null,
			serverID: server ? server.id : null,
			prefix: prefix, pattern: pattern.source
		});
		return pattern;
	}
}

const unprefixedCommandPattern = /^([^\s]+)/i;
const newlinesPattern = /\n/g;
const newlinesReplacement = '{!~NL~!}';
const newlinesReplacementPattern = new RegExp(newlinesReplacement, 'g');
const extraNewlinesPattern = /\n{3,}/g;
<|endoftext|>


<commit_before>//
// Copyright (c) 2016-2017 DrSmugleaf
//

"use strict"
const commando = require("discord.js-commando")
const responses = require("../../util/constants").responses.WHOIS
const moment = require("moment-timezone")

module.exports = class WhoIsCommand extends commando.Command {
  constructor(client) {
    super(client, {
      name: "whois",
      aliases: ["nsa", "user", "user-info", "userinfo", "whois"],
      group: "util",
      memberName: "whois",
      description: "Get information about a user.",
      examples: ["whois DrSmugleaf", "whois @DrSmugleaf#9458", "whois 109067752286715904"],
      guildOnly: true,
      throttling: {
        usages: 2,
        duration: 3
      },
      args: [
        {
          key: "member",
          label: "user",
          prompt: "What member would you like the information of?",
          type: "member"
        }
      ]
    })
  }

  async run(msg, args) {
    const member = args.member
    const user = member.user
    const joined = moment.tz(member.joinedAt, process.env.TZ).format(
      "MMMM Do YYYY, HH:MM:SS zZ"
    )
    const created = moment.tz(user.createdAt, process.env.TZ).format(
      "MMMM Do YYYY, HH:MM:SS zZ"
    )

    return msg.reply(responses[msg.language](user, member, joined, created))
  }
}
<commit_msg>Added who as an alias for whois<commit_after>//
// Copyright (c) 2016-2017 DrSmugleaf
//

"use strict"
const commando = require("discord.js-commando")
const responses = require("../../util/constants").responses.WHOIS
const moment = require("moment-timezone")

module.exports = class WhoIsCommand extends commando.Command {
  constructor(client) {
    super(client, {
      name: "whois",
      aliases: ["nsa", "user", "user-info", "userinfo", "who", "whois"],
      group: "util",
      memberName: "whois",
      description: "Get information about a user.",
      examples: ["who DrSmugleaf", "whois @DrSmugleaf#9458", "whois 109067752286715904"],
      guildOnly: true,
      throttling: {
        usages: 2,
        duration: 3
      },
      args: [
        {
          key: "member",
          label: "user",
          prompt: "What member would you like the information of?",
          type: "member"
        }
      ]
    })
  }

  async run(msg, args) {
    const member = args.member
    const user = member.user
    const joined = moment.tz(member.joinedAt, process.env.TZ).format(
      "MMMM Do YYYY, HH:MM:SS zZ"
    )
    const created = moment.tz(user.createdAt, process.env.TZ).format(
      "MMMM Do YYYY, HH:MM:SS zZ"
    )

    return msg.reply(responses[msg.language](user, member, joined, created))
  }
}
<|endoftext|>


<commit_before>$(function() {
    $('#field-format').change( function() {
        $( "#resource_save" ).trigger( "click" );
    });
});
<commit_msg>comment on warning box<commit_after>// this generates a warning about unsaved data from CKAN
// public/base/javascript/modules/basic-form.js
$(function() {
    $('#field-format').change( function() {
        $( "#resource_save" ).trigger( "click" );
    });
});

<|endoftext|>


<commit_before>
function WS4Redis(options, $) {
	'use strict';
	var opts, ws, deferred, timer, attempts = 1;
	var heartbeat_interval = null, missed_heartbeats = 0;

	if (this === undefined)
		return new WS4Redis(options, $);
	if (options.uri === undefined)
		throw new Error('No Websocket URI in options');
	if ($ === undefined)
		$ = jQuery;
	opts = $.extend({ heartbeat_msg: null }, options);
	connect(opts.uri);

	function connect(uri) {
		try {
			console.log("Connecting to " + uri + " ...");
			deferred = $.Deferred();
			ws = new WebSocket(uri);
			ws.onopen = on_open;
			ws.onmessage = on_message;
			ws.onerror = on_error;
			ws.onclose = on_close;
			timer = null;
		} catch (err) {
			deferred.reject(new Error(err));
		}
	}

	function send_heartbeat() {
		try {
			missed_heartbeats++;
			if (missed_heartbeats > 3)
				throw new Error("Too many missed heartbeats.");
			ws.send(opts.heartbeat_msg);
		} catch(e) {
			clearInterval(heartbeat_interval);
			heartbeat_interval = null;
			console.warn("Closing connection. Reason: " + e.message);
			ws.close();
		}
	}

	function on_open() {
		console.log('Connected!');
		// new connection, reset attemps counter
		attempts = 1;
		deferred.resolve();
		if (opts.heartbeat_msg && heartbeat_interval === null) {
			missed_heartbeats = 0;
			heartbeat_interval = setInterval(send_heartbeat, 5000);
		}
	}

	function on_close(evt) {
		console.log("Connection closed!");
		if (!timer) {
			// try to reconnect
			var interval = generateInteval(attempts);
			timer = setTimeout(function() {
				attempts++;
				connect(ws.url);
			}, interval);
		}
	}

	function on_error(evt) {
		console.error("Websocket connection is broken!");
		deferred.reject(new Error(evt));
	}

	function on_message(evt) {
		if (opts.heartbeat_msg && evt.data === opts.heartbeat_msg) {
			// reset the counter for missed heartbeats
			missed_heartbeats = 0;
		} else if (typeof opts.receive_message === 'function') {
			return opts.receive_message(evt.data);
		}
	}

	// this code is borrowed from http://blog.johnryding.com/post/78544969349/
	//
	// Generate an interval that is randomly between 0 and 2^k - 1, where k is
	// the number of connection attmpts, with a maximum interval of 30 seconds,
	// so it starts at 0 - 1 seconds and maxes out at 0 - 30 seconds
	function generateInteval (k) {
		var maxInterval = (Math.pow(2, k) - 1) * 1000;

		// If the generated interval is more than 30 seconds, truncate it down to 30 seconds.
		if (maxInterval > 30*1000) {
			maxInterval = 30*1000;
		}

		// generate the interval to a random number between 0 and the maxInterval determined from above
		return Math.random() * maxInterval;
	}

	this.send_message = function(message) {
		ws.send(message);
	};
}
<commit_msg>Added a connection callback<commit_after>
function WS4Redis(options, $) {
	'use strict';
	var opts, ws, deferred, timer, attempts = 1;
	var heartbeat_interval = null, missed_heartbeats = 0;

	if (this === undefined)
		return new WS4Redis(options, $);
	if (options.uri === undefined)
		throw new Error('No Websocket URI in options');
	if ($ === undefined)
		$ = jQuery;
	opts = $.extend({ heartbeat_msg: null }, options);
	connect(opts.uri);

	function connect(uri) {
		try {
			console.log("Connecting to " + uri + " ...");
			deferred = $.Deferred();
			ws = new WebSocket(uri);
			ws.onopen = on_open;
			ws.onmessage = on_message;
			ws.onerror = on_error;
			ws.onclose = on_close;
			timer = null;
		} catch (err) {
			deferred.reject(new Error(err));
		}
	}

	function send_heartbeat() {
		try {
			missed_heartbeats++;
			if (missed_heartbeats > 3)
				throw new Error("Too many missed heartbeats.");
			ws.send(opts.heartbeat_msg);
		} catch(e) {
			clearInterval(heartbeat_interval);
			heartbeat_interval = null;
			console.warn("Closing connection. Reason: " + e.message);
			ws.close();
		}
	}

	function on_open() {
		console.log('Connected!');
		// new connection, reset attemps counter
		attempts = 1;
		deferred.resolve();
		if (opts.heartbeat_msg && heartbeat_interval === null) {
			missed_heartbeats = 0;
			heartbeat_interval = setInterval(send_heartbeat, 5000);
		}
		if (typeof opts.connected === 'function') {
			opts.connected();
		}
	}

	function on_close(evt) {
		console.log("Connection closed!");
		if (!timer) {
			// try to reconnect
			var interval = generateInteval(attempts);
			timer = setTimeout(function() {
				attempts++;
				connect(ws.url);
			}, interval);
		}
	}

	function on_error(evt) {
		console.error("Websocket connection is broken!");
		deferred.reject(new Error(evt));
	}

	function on_message(evt) {
		if (opts.heartbeat_msg && evt.data === opts.heartbeat_msg) {
			// reset the counter for missed heartbeats
			missed_heartbeats = 0;
		} else if (typeof opts.receive_message === 'function') {
			return opts.receive_message(evt.data);
		}
	}

	// this code is borrowed from http://blog.johnryding.com/post/78544969349/
	//
	// Generate an interval that is randomly between 0 and 2^k - 1, where k is
	// the number of connection attmpts, with a maximum interval of 30 seconds,
	// so it starts at 0 - 1 seconds and maxes out at 0 - 30 seconds
	function generateInteval (k) {
		var maxInterval = (Math.pow(2, k) - 1) * 1000;

		// If the generated interval is more than 30 seconds, truncate it down to 30 seconds.
		if (maxInterval > 30*1000) {
			maxInterval = 30*1000;
		}

		// generate the interval to a random number between 0 and the maxInterval determined from above
		return Math.random() * maxInterval;
	}

	this.send_message = function(message) {
		ws.send(message);
	};
}
<|endoftext|>


<commit_before>import * as helpers from './helpers.js';

let subState;
let state;
let parentState;
let topState;

describe('helpers.getStoreInstanceName', () => {
  it('should handle when no instance', () => {
    expect(helpers.getStoreInstanceName('foo')).toEqual('foo');
  });

  it('should handle when instance', () => {
    expect(helpers.getStoreInstanceName('foo', 'bar')).toEqual('foo$bar');
  });
});

describe('helpers.toCamelCase', () => {
  it('convert kebab to camel case', () => {
    expect(helpers.toCamelCase('a-foo-bar-a$b')).toEqual('aFooBarA$b');
  });
  it('convert snake to camel case', () => {
    expect(helpers.toCamelCase('a_foo_bar_a$b')).toEqual('aFooBarA$b');
  });
  it('handles numbers', () => {
    expect(helpers.toCamelCase('a-fo1o2-ba3r-4a$5b')).toEqual('aFo1o2Ba3r4a$5b');
  });
  it('keeps camel case intact', () => {
    expect(helpers.toCamelCase('aFooBarA$b')).toEqual('aFooBarA$b');
  });
  it('returns empty string if invalid value', () => {
    expect(helpers.toCamelCase(undefined)).toEqual('');
    expect(helpers.toCamelCase(5)).toEqual('');
  });
});

describe('helpers.getLocalPath', () => {
  beforeEach(() => {
    subState = {
      count: 64,
      'vuex+': {
        storeName: 'foo',
        rootInstance: 'bar',
      },
    };

    state = {
      counter$inst: subState,
      count: 42,
      'vuex+': {
        storeName: 'foo',
        rootInstance: 'bar',
      },
    };

    parentState = {
      choo$test: state,
      count: 3,
      'vuex+': {
        storeName: 'foo',
        rootInstance: 'bar',
      },
    };

    topState = {
      foo$bar: parentState,
    };

    Object.defineProperty(subState, '$parent', {
      get() {
        return state;
      },
    });
    Object.defineProperty(state, '$parent', {
      get() {
        return parentState;
      },
    });
    Object.defineProperty(parentState, '$parent', {
      get() {
        return topState;
      },
    });
  });

  it('replaces $root to root instance', () => {
    expect(helpers.getLocalPath('$root/count', subState)).toEqual('foo$bar/count');
  });

  it('replaces $parent to parent instance', () => {
    expect(helpers.getLocalPath('$parent/count', subState)).toEqual('foo$bar/choo$test/count');
  });

  it('replaces $root to root instance and $parent to parent instance', () => {
    expect(helpers.getLocalPath('$root/$parent/count', subState)).toEqual('foo$bar/choo$test/count');
  });

  it('makes no changes when no keywords are used', () => {
    expect(helpers.getLocalPath('foo/bar/piri/choo', state)).toEqual('foo/bar/piri/choo');
  });
});

describe('helpers.getTagName', () => {
  it('returns unknown if no $parent', () => {
    expect(helpers.getTagName({})).toEqual('<unknown-tag>');
  });

  it('returns tag from $vnode', () => {
    const self = {
      $parent: {
        $vnode: {
          componentOptions: {
            tag: 'foo',
          },
        },
      },
    };
    expect(helpers.getTagName(self)).toEqual('<foo>');
  });

  it('returns tag from _vnode', () => {
    const self = {
      $parent: {
        _vnode: {
          componentOptions: {
            tag: 'foo',
          },
        },
      },
    };
    expect(helpers.getTagName(self)).toEqual('<foo>');
  });
});

describe('helpers.getInstances', () => {
  it('goes though parents and retrieves instances', () => {
    const self = {
      $parent: {
        instance: 'foo',
        $parent: {
          instance: 'bar',
          $parent: {
            instance: 'chuu',
          },
        },
      },
    };
    expect(helpers.getInstances('a/b', self)).toEqual(['$chuu', '$bar', '$foo']);
  });

  it('goes though parents and retrieves instances, and handles when parents lack instances', () => {
    const self = {
      $parent: {
        $parent: {
          $parent: {
            instance: 'chuu',
          },
        },
      },
    };
    expect(helpers.getInstances('a/b', self)).toEqual(['$chuu']);
  });

  it('adds subinstances', () => {
    const self = {
      $parent: {
        instance: 'foo',
      },
    };
    expect(helpers.getInstances('a$a1/b$b1/c$c1/d', self)).toEqual(['$foo', '$a1', '$b1', '$c1']);
  });
});

describe('helpers.getSubInstances', () => {
  it('returns all instances from substring', () => {
    expect(helpers.getSubInstances('a$a1/b/c$c1/d')).toEqual(['$a1', '$c1']);
  });

  it('returns empty array on empty path', () => {
    expect(helpers.getSubInstances('')).toEqual([]);
  });
});
<commit_msg>Removed unneeded code<commit_after>import * as helpers from './helpers.js';

let subState;
let state;
let parentState;
let topState;

describe('helpers.getStoreInstanceName', () => {
  it('should handle when no instance', () => {
    expect(helpers.getStoreInstanceName('foo')).toEqual('foo');
  });

  it('should handle when instance', () => {
    expect(helpers.getStoreInstanceName('foo', 'bar')).toEqual('foo$bar');
  });
});

describe('helpers.toCamelCase', () => {
  it('convert kebab to camel case', () => {
    expect(helpers.toCamelCase('a-foo-bar-a$b')).toEqual('aFooBarA$b');
  });
  it('convert snake to camel case', () => {
    expect(helpers.toCamelCase('a_foo_bar_a$b')).toEqual('aFooBarA$b');
  });
  it('handles numbers', () => {
    expect(helpers.toCamelCase('a-fo1o2-ba3r-4a$5b')).toEqual('aFo1o2Ba3r4a$5b');
  });
  it('keeps camel case intact', () => {
    expect(helpers.toCamelCase('aFooBarA$b')).toEqual('aFooBarA$b');
  });
  it('returns empty string if invalid value', () => {
    expect(helpers.toCamelCase(undefined)).toEqual('');
    expect(helpers.toCamelCase(5)).toEqual('');
  });
});

describe('helpers.getLocalPath', () => {
  beforeEach(() => {
    subState = {
      count: 64,
      'vuex+': {
        storeName: 'foo',
        rootInstance: 'bar',
      },
    };

    state = {
      counter$inst: subState,
      count: 42,
      'vuex+': {
        storeName: 'foo',
        rootInstance: 'bar',
      },
    };

    parentState = {
      choo$test: state,
      count: 3,
      'vuex+': {
        storeName: 'foo',
        rootInstance: 'bar',
      },
    };

    topState = {
      foo$bar: parentState,
    };

    Object.defineProperty(subState, '$parent', {
      get() {
        return state;
      },
    });
    Object.defineProperty(state, '$parent', {
      get() {
        return parentState;
      },
    });
    Object.defineProperty(parentState, '$parent', {
      get() {
        return topState;
      },
    });
  });

  it('replaces $root to root instance', () => {
    expect(helpers.getLocalPath('$root/count', subState)).toEqual('foo$bar/count');
  });

  it('replaces $parent to parent instance', () => {
    expect(helpers.getLocalPath('$parent/count', subState)).toEqual('foo$bar/choo$test/count');
  });

  it('replaces $root to root instance and $parent to parent instance', () => {
    expect(helpers.getLocalPath('$root/$parent/count', subState)).toEqual('foo$bar/choo$test/count');
  });

  it('makes no changes when no keywords are used', () => {
    expect(helpers.getLocalPath('foo/bar/piri/choo', state)).toEqual('foo/bar/piri/choo');
  });
});

describe('helpers.getSubInstances', () => {
  it('returns all instances from substring', () => {
    expect(helpers.getSubInstances('a$a1/b/c$c1/d')).toEqual(['$a1', '$c1']);
  });

  it('returns empty array on empty path', () => {
    expect(helpers.getSubInstances('')).toEqual([]);
  });
});
<|endoftext|>


<commit_before>enyo.kind({
	name: "Connect4Controller",
	kind: "Component",
	COL_SIZE: 7,
	ROW_SIZE: 6,
	components:[
		{kind:"Signals", onPlayResult:"handleUpdateReceived"}
	],
	create:function() {
	    this.inherited(arguments);
	    this.gameboard = {currentBoard:[]};
	    this.moves = this.gameboard.currentBoard;
	},
	load: function(gameboard) {
		if(gameboard) {
			this.gameboard = gameboard;
			this.moves = gameboard.currentBoard;
			this.maxPlayers = this.getMaxPlayers();
		}
		for(var i=0; i<this.ROW_SIZE; i++) {
			var row = enyo.clone(this.view.row);
			row.components = [];
			for(var j=0; j<this.COL_SIZE; j++) {
				var cell = enyo.clone(this.view.cell);
				cell.x = j;
				cell.y = ((this.ROW_SIZE-1)-i);
				cell.components = [enyo.clone(this.view.item)];
				cell.components[0].name = "piece-" + cell.x + "-" + cell.y;
				cell.ontap = "controller.columnSelected";
				row.components.push(cell);
			}
			var c = this.view.$.c4Grid.createComponent(row);
		}
		this.update();
		this.view.$.c4Grid.render();
		if(gameboard.players.length==1) {
			// user just created a new game
			//TODO: show popup with copyable URL in it
			this.view.$.status.setContent("Waiting for opponent...");
		} else if(gameboard.players.length<this.maxPlayers) {

		}
	},
	update: function(gameboard, animateLast) {
		if(gameboard) {
			this.gameboard = gameboard;
			this.moves = gameboard.currentBoard;
			this.maxPlayers = this.getMaxPlayers();
		}
		var self = this;
		var asyncStatusUpdate = function() {
			if(self.view.$.status) {
				self.view.$.status.setContent(self.getUserName(self.gameboard.userToPlay) + "'s turn");
			}
			self.timerID=undefined;
		};
		for(var i=0; i<this.moves.length; i++) {
			var piece = this.view.$.c4Grid.$["piece-" + this.moves[i].x + "-" + this.moves[i].y];
			if(i==this.moves.length-1 && animateLast) {
				piece.setPiece();
				piece.animateIn(this.moves[i].player.id);
				// delay updating status to better align with animation
				this.timerID = setTimeout(asyncStatusUpdate, 1100);
			} else {
				piece.setPiece(this.moves[i].player.id);
			}
		}
		if(this.gameboard.userToPlay && !animateLast && !this.timerID) {
			this.view.$.status.setContent(this.getUserName(this.gameboard.userToPlay) + "'s turn");
		}
	},
	getMaxPlayers:function() {
		for(var i=0; i<window.availableGames.length; i++) {
			if(window.availableGames[i].gameID==this.gameboard.gameID) {
				return window.availableGames[i].maxPlayers;
			}
		}
		return 2;
	},
	getUserName: function(player) {
		var name = "Player";
		if(player && player.id) {
			if(player.id==window.userID) {
				if(player.name && player.name.length>0) {
					name = player.name;
					if(player.name!=window.userName) {
						window.userName = player.name;
					}
				}
			} else {
				name = "Opponent";
				if(player.name && player.name.length>0 && player.name!="Player") {
					name = player.name;
				}
			}
		}
		return name;
	},
	columnSelected: function(inSender, inEvent) {
		if(this.gameboard.userToPlay && this.gameboard.userToPlay.id==window.userID && this.timerID===undefined
					&& this.gameboard.players.length==this.maxPlayers) {
			this.gameboard.userToPlay = undefined;
			var placable = false;
			for(var i=0; i<this.COL_SIZE && !placable; i++) {
				var piece = this.view.$.c4Grid.$["piece-" + inSender.x + "-" + i];
				if(piece && !piece.isFilled()) {
					this.moves.push({x:inSender.x, y:i, player:{id:window.userID, name:window.userName}});
					this.update(undefined, true);
					window.ClientServerComm.sendPlayMoveEvent({x:inSender.x, y:i,
							player:{id:window.userID, name:window.userName}, instanceID:this.gameboard.instanceID});
					placable = true;
				}
			}
		}
	},
	handleUpdateReceived: function(inSender, inEvent) {
		if(this.gameboard.instanceID==inEvent.gameboard.instanceID) {
			if(this.gameboard.players.length<this.maxPlayers
					&& inEvent.gameboard.players.length>this.gameboard.players.length) {
				//new user joined a non-full match
				if(inEvent.gameboard.players.length==this.maxPlayers) {
					this.update(inEvent.gameboard);
				} else {
					this.gameboard = inEvent.gameboard;
					this.moves = inEvent.gameboard.currentBoard;
					this.maxPlayers = this.getMaxPlayers();
				}
				enyo.stage.menu.controller.updateGame(inEvent.gameboard);
			} else if(this.moves.length<inEvent.gameboard.currentBoard.length) {
				//new moves
				this.update(inEvent.gameboard, true);
				if(inEvent.gameboard.status && (typeof inEvent.gameboard.status === "string")) {
					var status = inEvent.gameboard.status.toLowerCase();
					if(status=="winner") {
						//TODO: Show winner popup
						//afterwards, close this game
					} else if(status=="draw") {
						//TODO: Show winner popup
						//afterwards, close this game
					}
				}
			} else if(this.moves.length==inEvent.gameboard.currentBoard.length){
				//move success
				this.update(inEvent.gameboard);
			} else {
				//move failed
				this.update(inEvent.gameboard);
			}
		}
	},
	rendered: function() {
		this.updateRatio();
	},
	reflow: function() {
		this.updateRatio();
	},
	updateRatio: function() {
		var bounds = this.view.getBounds();
		var wrapper = this.view.$.wrapper;
		if(bounds && bounds.height!==undefined && bounds.width!==undefined) {
			if((bounds.height-64)<(bounds.width*(6/7))) {
				wrapper.setBounds({
					width: ((bounds.height-64)*(7/6)),
					height: (bounds.height-64)
				});
			} else {
				wrapper.setBounds({
					width: bounds.width,
					height: (bounds.width*(6/7))
				});
			}
		}
	}
});
<commit_msg>Timing improvements to prevent move spamming<commit_after>enyo.kind({
	name: "Connect4Controller",
	kind: "Component",
	COL_SIZE: 7,
	ROW_SIZE: 6,
	components:[
		{kind:"Signals", onPlayResult:"handleUpdateReceived"}
	],
	create:function() {
	    this.inherited(arguments);
	    this.gameboard = {currentBoard:[]};
	    this.moves = this.gameboard.currentBoard;
	},
	load: function(gameboard) {
		if(gameboard) {
			this.gameboard = gameboard;
			this.moves = gameboard.currentBoard;
			this.maxPlayers = this.getMaxPlayers();
		}
		for(var i=0; i<this.ROW_SIZE; i++) {
			var row = enyo.clone(this.view.row);
			row.components = [];
			for(var j=0; j<this.COL_SIZE; j++) {
				var cell = enyo.clone(this.view.cell);
				cell.x = j;
				cell.y = ((this.ROW_SIZE-1)-i);
				cell.components = [enyo.clone(this.view.item)];
				cell.components[0].name = "piece-" + cell.x + "-" + cell.y;
				cell.ontap = "controller.columnSelected";
				row.components.push(cell);
			}
			var c = this.view.$.c4Grid.createComponent(row);
		}
		this.update();
		this.view.$.c4Grid.render();
		if(gameboard.players.length==1) {
			// user just created a new game
			//TODO: show popup with copyable URL in it
			this.view.$.status.setContent("Waiting for opponent...");
		} else if(gameboard.players.length<this.maxPlayers) {

		}
	},
	update: function(gameboard, animateLast) {
		if(gameboard) {
			this.gameboard = gameboard;
			this.moves = gameboard.currentBoard;
			this.maxPlayers = this.getMaxPlayers();
		}
		var self = this;
		var asyncStatusUpdate = function() {
			if(self.view.$.status) {
				self.view.$.status.setContent(self.getUserName(self.gameboard.userToPlay) + "'s turn");
			}
			self.timerID=undefined;
		};
		for(var i=0; i<this.moves.length; i++) {
			var piece = this.view.$.c4Grid.$["piece-" + this.moves[i].x + "-" + this.moves[i].y];
			if(i==this.moves.length-1 && animateLast) {
				piece.setPiece();
				piece.animateIn(this.moves[i].player.id);
				// delay updating status to better align with animation
				this.timerID = setTimeout(asyncStatusUpdate, 1100);
			} else {
				piece.setPiece(this.moves[i].player.id);
			}
		}
		if(this.gameboard.userToPlay && !animateLast && !this.timerID) {
			this.view.$.status.setContent(this.getUserName(this.gameboard.userToPlay) + "'s turn");
		}
	},
	getMaxPlayers:function() {
		for(var i=0; i<window.availableGames.length; i++) {
			if(window.availableGames[i].gameID==this.gameboard.gameID) {
				return window.availableGames[i].maxPlayers;
			}
		}
		return 2;
	},
	getUserName: function(player) {
		var name = "Player";
		if(player && player.id) {
			if(player.id==window.userID) {
				if(player.name && player.name.length>0) {
					name = player.name;
					if(player.name!=window.userName) {
						window.userName = player.name;
					}
				}
			} else {
				name = "Opponent";
				if(player.name && player.name.length>0 && player.name!="Player") {
					name = player.name;
				}
			}
		}
		return name;
	},
	columnSelected: function(inSender, inEvent) {
		if(this.gameboard.userToPlay && this.gameboard.userToPlay.id==window.userID && this.timerID===undefined
					&& this.gameboard.players.length==this.maxPlayers
					&& (this.moves.length==0 || this.moves[this.moves.length-1].player.id!=window.userID)) {
			this.gameboard.userToPlay = undefined;
			var placable = false;
			for(var i=0; i<this.COL_SIZE && !placable; i++) {
				var piece = this.view.$.c4Grid.$["piece-" + inSender.x + "-" + i];
				if(piece && !piece.isFilled()) {
					this.moves.push({x:inSender.x, y:i, player:{id:window.userID, name:window.userName}});
					this.update(undefined, true);
					window.ClientServerComm.sendPlayMoveEvent({x:inSender.x, y:i,
							player:{id:window.userID, name:window.userName}, instanceID:this.gameboard.instanceID});
					placable = true;
				}
			}
		}
	},
	handleUpdateReceived: function(inSender, inEvent) {
		if(this.gameboard.instanceID==inEvent.gameboard.instanceID) {
			if(this.gameboard.players.length<this.maxPlayers
					&& inEvent.gameboard.players.length>this.gameboard.players.length) {
				//new user joined a non-full match
				if(inEvent.gameboard.players.length==this.maxPlayers) {
					this.update(inEvent.gameboard);
				} else {
					this.gameboard = inEvent.gameboard;
					this.moves = inEvent.gameboard.currentBoard;
					this.maxPlayers = this.getMaxPlayers();
				}
				enyo.stage.menu.controller.updateGame(inEvent.gameboard);
			} else if(this.moves.length<inEvent.gameboard.currentBoard.length) {
				//new moves
				this.update(inEvent.gameboard, true);
				if(inEvent.gameboard.status && (typeof inEvent.gameboard.status === "string")) {
					var status = inEvent.gameboard.status.toLowerCase();
					if(status=="winner") {
						//TODO: Show winner popup
						//afterwards, close this game
					} else if(status=="draw") {
						//TODO: Show winner popup
						//afterwards, close this game
					}
				}
			} else if(this.moves.length==inEvent.gameboard.currentBoard.length){
				//move success
				this.update(inEvent.gameboard);
			} else {
				//move failed
				this.update(inEvent.gameboard);
			}
		}
	},
	rendered: function() {
		this.updateRatio();
	},
	reflow: function() {
		this.updateRatio();
	},
	updateRatio: function() {
		var bounds = this.view.getBounds();
		var wrapper = this.view.$.wrapper;
		if(bounds && bounds.height!==undefined && bounds.width!==undefined) {
			if((bounds.height-64)<(bounds.width*(6/7))) {
				wrapper.setBounds({
					width: ((bounds.height-64)*(7/6)),
					height: (bounds.height-64)
				});
			} else {
				wrapper.setBounds({
					width: bounds.width,
					height: (bounds.width*(6/7))
				});
			}
		}
	}
});
<|endoftext|>


<commit_before>angular.module('convergence.directives')

	.directive('board', function ($timeout, randomise, game) {
		return {
			restrict: 'E',
			template: '
' +
				'<shape ng-repeat="shape in shapes" shape="shape"></shape>' +
				'<timer></timer>' +
				'<target></target>' +
				'<focal-point></focal-point>' +
				'<pin></pin>' +
				'
',
			controller: function ($rootScope, $scope, $element) {
				var board = $element[0];
				board.classList.add('board');
				board.addEventListener("touchstart", dropPin, false);
				board.addEventListener("click", dropPin, false);

				var _this = this;
				_this.width = window.innerWidth;
				_this.height = window.innerHeight;

				var pinPosition = {};

				$rootScope.$on('game.reset', reset);
				$rootScope.$on('game.play', startLevel);
				$rootScope.$on('game.out-of-time', outOfTime);
				$rootScope.$on('game.end-of-level', endLevel);

				// Functions
				// -------------------------------------

				function startLevel() {
					pinPosition = {};
					setFocalPoint();
					addShapes();
					board.classList.remove('fadeOut');
					game.pinEnabled = true;
				}

				function outOfTime() {
					game.pinEnabled = false;
					$timeout(function () {
						$rootScope.$broadcast('game.end-of-level');
					}, 750);
				}

				function endLevel() {
					if (!pinPosition.x || !pinPosition.y) {
						$rootScope.$broadcast('game.over');
						return;
					}
					var a = Math.abs(pinPosition.x - _this.focalPointX);
					var b = Math.abs(pinPosition.y - _this.focalPointY);
					var dist = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); // a2 + b2 = c2

					if (dist > (game.settings.target * _this.width) / 2) {
						// If the pin is outside target, game over
						$rootScope.$broadcast('game.over');

					}	else {
						// If inside target let's crack on
						$rootScope.$broadcast('game.level-complete');
					}
					board.classList.add('fadeOut');
				}

				function dropPin(e) {
					e.preventDefault();
					if (game.pinEnabled) {
						if (e.changedTouches) {
							pinPosition.x = e.changedTouches[0].clientX;
							pinPosition.y = e.changedTouches[0].clientY;
						} else {
							pinPosition.x = e.clientX;
							pinPosition.y = e.clientY;
						}
						$rootScope.$broadcast('game.drop-pin', pinPosition);
					}
				}

				function reset() {
					game.reset();
					$scope.shapes = [];
				}

				function randomDegrees(angle) {
					return randomise.generate(angle, angle + 20);
				}

				function setFocalPoint() {
					var focalPointPadding = 50;
					_this.focalPointX = randomise.generate(focalPointPadding, _this.width - focalPointPadding);
					_this.focalPointY = randomise.generate(focalPointPadding, _this.height - focalPointPadding);
				}

				function addShapes() {
					$scope.shapes = [];
					var colors = [
						'#2c97c0', // blue
						'#b93085', // pink
						'#f2f2f2', // white
						'#333333', // black
						'#08ac98', // green
						'#ffa500'  // orange
					];
					colors = randomise.shuffleArray(colors);
					var angle = 0;
					var angleDiff = 360 / game.settings.noOfShapes;
					var shapes = [];
					for (var i = 0; i < game.settings.noOfShapes; i++) {
						shapes.push({
							shape: game.settings.typeOfShapes,
							color: colors.pop(),
							angle: randomDegrees(angle)
						});
						angle = angle + angleDiff;
					}
					$scope.shapes = shapes;
				}

				_this.calculateAdjacent = function calculateAdjacent(angle) {
					var quadrant = Math.floor(angle / 90);
					switch (quadrant) {
						case 0: // Bottom right corner
							var a = board.offsetHeight - _this.focalPointY; // Distance between focal point and bottom
							var b = board.offsetWidth - _this.focalPointX; // Distance between focal point and right
							var angleReset = angle * 2;
							break;

						case 1: // Bottom left corner
							var a = board.offsetHeight - _this.focalPointY; // Distance between focal point and bottom
							var b = _this.focalPointX; // Distance between left and focal point
							var angleReset = 180;
							break;

						case 2: // Top left corner
							var a = _this.focalPointX; // Distance between left and focal point
							var b = _this.focalPointY; // Distance between top and focal point
							var angleReset = 270;
							break;

						case 3: // Top right corner
							var a = _this.focalPointY; // Distance between top and focal point
							var b = board.offsetWidth - _this.focalPointX; // Distance between focal point and right
							var angleReset = 360;
							break;
					}

					angle = 90 - Math.atan(b / a) * (180 / Math.PI) - (angleReset - angle); // The Cosine angle
					var hypotenuse = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); // Diagonal distance between focal point and corner
					var adjacentLength = Math.cos(angle * (Math.PI / 180)) * hypotenuse;
					return adjacentLength;
				}
			}
		}
	})
;<commit_msg>Use board width not window width<commit_after>angular.module('convergence.directives')

	.directive('board', function ($timeout, randomise, game) {
		return {
			restrict: 'E',
			template: '
' +
				'<shape ng-repeat="shape in shapes" shape="shape"></shape>' +
				'<timer></timer>' +
				'<target></target>' +
				'<focal-point></focal-point>' +
				'<pin></pin>' +
				'
',
			controller: function ($rootScope, $scope, $element) {
				var board = $element[0];
				board.classList.add('board');
				board.addEventListener("touchstart", dropPin, false);
				board.addEventListener("click", dropPin, false);

				var _this = this;
				_this.width = board.clientWidth;
				_this.height = board.clientHeight;

				var pinPosition = {};

				$rootScope.$on('game.reset', reset);
				$rootScope.$on('game.play', startLevel);
				$rootScope.$on('game.out-of-time', outOfTime);
				$rootScope.$on('game.end-of-level', endLevel);

				// Functions
				// -------------------------------------

				function startLevel() {
					pinPosition = {};
					setFocalPoint();
					addShapes();
					board.classList.remove('fadeOut');
					game.pinEnabled = true;
				}

				function outOfTime() {
					game.pinEnabled = false;
					$timeout(function () {
						$rootScope.$broadcast('game.end-of-level');
					}, 750);
				}

				function endLevel() {
					if (!pinPosition.x || !pinPosition.y) {
						$rootScope.$broadcast('game.over');
						return;
					}
					var a = Math.abs(pinPosition.x - _this.focalPointX);
					var b = Math.abs(pinPosition.y - _this.focalPointY);
					var dist = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); // a2 + b2 = c2

					if (dist > (game.settings.target * _this.width) / 2) {
						// If the pin is outside target, game over
						$rootScope.$broadcast('game.over');

					}	else {
						// If inside target let's crack on
						$rootScope.$broadcast('game.level-complete');
					}
					board.classList.add('fadeOut');
				}

				function dropPin(e) {
					e.preventDefault();
					if (game.pinEnabled) {
						if (e.changedTouches) {
							pinPosition.x = e.changedTouches[0].clientX;
							pinPosition.y = e.changedTouches[0].clientY;
						} else {
							pinPosition.x = e.clientX;
							pinPosition.y = e.clientY;
						}
						$rootScope.$broadcast('game.drop-pin', pinPosition);
					}
				}

				function reset() {
					game.reset();
					$scope.shapes = [];
				}

				function randomDegrees(angle) {
					return randomise.generate(angle, angle + 20);
				}

				function setFocalPoint() {
					var focalPointPadding = 50;
					_this.focalPointX = randomise.generate(focalPointPadding, _this.width - focalPointPadding);
					_this.focalPointY = randomise.generate(focalPointPadding, _this.height - focalPointPadding);
				}

				function addShapes() {
					$scope.shapes = [];
					var colors = [
						'#2c97c0', // blue
						'#b93085', // pink
						'#f2f2f2', // white
						'#333333', // black
						'#08ac98', // green
						'#ffa500'  // orange
					];
					colors = randomise.shuffleArray(colors);
					var angle = 0;
					var angleDiff = 360 / game.settings.noOfShapes;
					var shapes = [];
					for (var i = 0; i < game.settings.noOfShapes; i++) {
						shapes.push({
							shape: game.settings.typeOfShapes,
							color: colors.pop(),
							angle: randomDegrees(angle)
						});
						angle = angle + angleDiff;
					}
					$scope.shapes = shapes;
				}

				_this.calculateAdjacent = function calculateAdjacent(angle) {
					var quadrant = Math.floor(angle / 90);
					switch (quadrant) {
						case 0: // Bottom right corner
							var a = board.offsetHeight - _this.focalPointY; // Distance between focal point and bottom
							var b = board.offsetWidth - _this.focalPointX; // Distance between focal point and right
							var angleReset = angle * 2;
							break;

						case 1: // Bottom left corner
							var a = board.offsetHeight - _this.focalPointY; // Distance between focal point and bottom
							var b = _this.focalPointX; // Distance between left and focal point
							var angleReset = 180;
							break;

						case 2: // Top left corner
							var a = _this.focalPointX; // Distance between left and focal point
							var b = _this.focalPointY; // Distance between top and focal point
							var angleReset = 270;
							break;

						case 3: // Top right corner
							var a = _this.focalPointY; // Distance between top and focal point
							var b = board.offsetWidth - _this.focalPointX; // Distance between focal point and right
							var angleReset = 360;
							break;
					}

					angle = 90 - Math.atan(b / a) * (180 / Math.PI) - (angleReset - angle); // The Cosine angle
					var hypotenuse = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); // Diagonal distance between focal point and corner
					var adjacentLength = Math.cos(angle * (Math.PI / 180)) * hypotenuse;
					return adjacentLength;
				}
			}
		}
	})
;<|endoftext|>


<commit_before>const MBContract       = require('./mb_contract');
const MBDefaults       = require('./mb_defaults');
const MBNotifications  = require('./mb_notifications');
const MBPrice          = require('./mb_price');
const MBSymbols        = require('./mb_symbols');
const MBTick           = require('./mb_tick');
const commonTrading    = require('../trade/common');
const BinaryPjax       = require('../../base/binary_pjax');
const Client           = require('../../base/client');
const BinarySocket     = require('../../base/socket');
const jpClient         = require('../../common/country_base').jpClient;
const isCryptocurrency = require('../../common/currency').isCryptocurrency;
const getLanguage      = require('../../../_common/language').get;
const localize         = require('../../../_common/localize').localize;
const State            = require('../../../_common/storage').State;
const urlForStatic     = require('../../../_common/url').urlForStatic;
const getPropertyValue = require('../../../_common/utility').getPropertyValue;

const MBProcess = (() => {
    let market_status = '';

    let symbols_timeout,
        contract_timeout;

    const getSymbols = () => {
        BinarySocket.wait('website_status').then((website_status) => {
            const landing_company_obj = State.getResponse('landing_company');
            const allowed_markets     = Client.currentLandingCompany().legal_allowed_markets;
            if (Client.isLoggedIn() && allowed_markets && allowed_markets.indexOf('forex') === -1) {
                BinaryPjax.load('trading');
                return;
            }
            const req = {
                active_symbols: 'brief',
                product_type  : 'multi_barrier',
            };
            if (landing_company_obj) {
                req.landing_company = landing_company_obj.financial_company ? landing_company_obj.financial_company.shortcode : 'japan';
            } else if (website_status.website_status.clients_country === 'jp' || getLanguage() === 'JA') {
                req.landing_company = 'japan';
            }
            BinarySocket.send(req, { msg_type: 'active_symbols' }).then((response) => {
                processActiveSymbols(response);
            });
        });
    };

    /*
     * This function processes the active symbols to get markets
     * and underlying list
     */
    const processActiveSymbols = (data) => {
        if (getPropertyValue(data, 'error')) {
            MBNotifications.show({ text: data.error.message, uid: 'ACTIVE_SYMBOLS' });
            return;
        }

        // populate the Symbols object
        MBSymbols.details(data);

        const is_show_all  = Client.isLoggedIn() && !jpClient();
        const symbols_list = is_show_all ? MBSymbols.getAllSymbols() : MBSymbols.underlyings().major_pairs;
        let symbol         = MBDefaults.get('underlying');

        if (!symbol || !symbols_list[symbol]) {
            symbol = undefined;
            MBDefaults.remove('underlying');
        }

        // check if all symbols are inactive
        let is_market_closed = true;
        Object.keys(symbols_list).forEach((s) => {
            if (symbols_list[s].is_active) {
                is_market_closed = false;
            }
        });
        clearSymbolTimeout();
        if (is_market_closed) {
            handleMarketClosed();
        } else {
            handleMarketOpen();
            populateUnderlyings(symbol);

            if (symbol && !symbols_list[symbol].is_active) {
                MBNotifications.show({ text: localize('This symbol is not active. Please try another symbol.'), uid: 'SYMBOL_INACTIVE' });
            } else {
                processMarketUnderlying();
            }
        }
    };

    const populateUnderlyings = (selected) => {
        const $underlyings = $('#underlying');
        const all_symbols  = MBSymbols.getAllSymbols();

        const $list = $underlyings.find('.list');
        $list.empty();
        $underlyings.find('.current').html($('<div/>', { class: 'gr-row' })
            .append($('<img/>', { class: 'gr-3 gr-no-gutter-m' }))
            .append($('<span/>', { class: 'name gr-6 gr-5-m align-self-center' }))
            .append($('<span/>', { class: 'gr-3 gr-4-m align-self-center still', id: 'spot' })));

        let selected_symbol = selected;
        if (Object.keys(all_symbols).indexOf(selected) === -1) selected_symbol = '';
        Object.keys(all_symbols).forEach((symbol, idx) => {
            if (all_symbols[symbol].is_active) {
                const is_current = (!selected_symbol && idx === 0) || symbol === selected_symbol;
                const $current   = $('<div/>', { value: symbol, class: 'gr-4 gr-4-t gr-4-m' })
                    .append($('<img/>', { src: urlForStatic(`/images/pages/mb_trading/${symbol.toLowerCase()}.svg`), alt: '' }))
                    .append($('<div/>', { text: all_symbols[symbol].display, class: 'name align-self-center' }));
                $list.append($current);
                if (is_current) {
                    MBContract.setCurrentItem($underlyings, symbol, 1);
                }
            }
        });
    };

    const selectors = '.trade_form, .price-table, #trading_bottom_content, .selection_wrapper, #trade_live_chart';

    const handleMarketClosed = () => {
        $(selectors).setVisibility(0);
        hideShowMbTrading('hide');
        MBNotifications.show({ text: localize('Market is closed. Please try again later.'), uid: 'MARKET_CLOSED' });
        symbols_timeout = setTimeout(() => { getSymbols(); }, 30000);
    };

    const handleMarketOpen = () => {
        $(selectors).setVisibility(1);
        hideShowMbTrading('show');
        MBNotifications.hide('MARKET_CLOSED');
    };

    const hideShowMbTrading = (action) => {
        const classes = ['gr-5 ', 'gr-12 ']; // the extra space is so gr-5-m is not replaced
        const show    = action === 'show';
        const $parent = $('#mb_trading').parent();
        $parent.attr('class', $parent.attr('class').replace(classes[+show], classes[+!show]));
    };

    const clearSymbolTimeout = () => {
        clearTimeout(symbols_timeout);
    };

    /*
     * Function to call when underlying has changed
     */
    const processMarketUnderlying = () => {
        const underlying = $('#underlying').attr('value');
        MBDefaults.set('underlying', underlying);

        commonTrading.showFormOverlay();

        // forget the old tick id i.e. close the old tick stream
        processForgetTicks().then(() => {
            // get ticks for current underlying
            MBTick.request(underlying);
            MBTick.clean();
        });

        BinarySocket.clearTimeouts();

        getContracts(underlying);
    };

    const getContracts = (underlying) => {
        const req = {
            contracts_for: (underlying || MBDefaults.get('underlying')),
            currency     : MBContract.getCurrency(),
            product_type : 'multi_barrier',
        };
        BinarySocket.send(req).then((response) => {
            MBNotifications.hide('CONNECTION_ERROR');
            MBContract.setContractsResponse(response);
            // contracts_for is triggered every 15 seconds to check for expired barriers
            // but we don't want to send proposal in that case
            // so getContracts will be called without underlying param to distinguish these two cases
            processContract(response, underlying);
        });
        if (contract_timeout) clearContractTimeout();
        contract_timeout = setTimeout(getContracts, 15000);
    };

    const clearContractTimeout = () => { clearTimeout(contract_timeout); };

    /*
     * Function to display contract form for current underlying
     */
    const processContract = (contracts, send_proposal) => {
        if (getPropertyValue(contracts, 'error')) {
            MBNotifications.show({ text: contracts.error.message, uid: contracts.error.code });
            return;
        }

        State.set('is_chart_allowed', !(contracts.contracts_for && contracts.contracts_for.feed_license && contracts.contracts_for.feed_license === 'chartonly'));

        checkMarketStatus(contracts.contracts_for.close);

        MBContract.populateOptions((send_proposal ? 'rebuild' : null));
        if (send_proposal) {
            processPriceRequest();
        } else {
            processExpiredBarriers();
        }
    };

    const checkMarketStatus = (close) => {
        const now = window.time.unix();

        // if market is closed, else if market is open
        if (now > close) {
            if (market_status === 'open') {
                handleMarketClosed();
            }
            market_status = 'closed';
        } else {
            if (market_status === 'closed') {
                getSymbols();
                handleMarketOpen();
            }
            market_status = 'open';
        }
    };

    const processPriceRequest = () => {
        MBPrice.increaseReqId();
        MBPrice.showPriceOverlay();
        const available_contracts = MBContract.getCurrentContracts();
        const durations           = MBDefaults.get('period').split('_');
        const jp_client           = jpClient();
        const is_crypto           = isCryptocurrency(MBDefaults.get('currency'));
        const payout              = parseFloat(MBDefaults.get(`payout${is_crypto ? '_crypto' : ''}`));

        const req = {
            proposal_array: 1,
            subscribe     : 1,
            basis         : 'payout',
            amount        : jp_client ? (parseInt(payout) || 1) * 1000 : payout,
            currency      : MBContract.getCurrency(),
            symbol        : MBDefaults.get('underlying'),
            passthrough   : { req_id: MBPrice.getReqId() },
            date_expiry   : durations[1],
            contract_type : [],
            barriers      : [],
            product_type  : 'multi_barrier',

            trading_period_start: durations[0],
        };

        // contract_type
        available_contracts.forEach(c => req.contract_type.push(c.contract_type));

        // barriers
        let all_expired = true;
        const contract  = available_contracts[0];
        contract.available_barriers.forEach((barrier) => {
            const barrier_item = {};
            if (+contract.barriers === 2) {
                barrier_item.barrier  = barrier[1];
                barrier_item.barrier2 = barrier[0];
            } else {
                barrier_item.barrier = barrier;
            }
            if (!barrierHasExpired(contract.expired_barriers, barrier_item.barrier, barrier_item.barrier2)) {
                all_expired = false;
                req.barriers.push(barrier_item);
            }
        });

        processForgetProposals().then(() => {
            // send request
            if (req.barriers.length) {
                MBPrice.addPriceObj(req);
                BinarySocket.send(req, { callback: processProposal });
            }
        });

        // all barriers expired
        if (all_expired) {
            MBNotifications.show({ text: `${localize('All barriers in this trading window are expired')}.`, uid: 'ALL_EXPIRED' });
            MBPrice.hidePriceOverlay();
        } else {
            MBNotifications.hide('ALL_EXPIRED');
        }
    };

    const processProposal = (response) => {
        const req_id = MBPrice.getReqId();
        if (response.passthrough.req_id === req_id) {
            if (response.error) {
                const error_message = response.error.error ? response.error.error.message : response.error.message;
                MBNotifications.show({ text: error_message, uid: 'PROPOSAL', dismissible: false });
                return;
            }
            MBNotifications.hide('PROPOSAL');
            MBPrice.display(response);
        }
    };

    const processExpiredBarriers = () => {
        const contracts = MBContract.getCurrentContracts();
        let expired_barrier,
            $expired_barrier_element;
        contracts.forEach((c) => {
            const expired_barriers = c.expired_barriers;
            for (let i = 0; i < expired_barriers.length; i++) {
                if (+c.barriers === 2) {
                    expired_barrier = [expired_barriers[i][0], expired_barriers[i][1]].join('_');
                } else {
                    expired_barrier = expired_barriers[i];
                }
                $expired_barrier_element = $(`div [data-barrier="${expired_barrier}"]`);
                if ($expired_barrier_element.length > 0) {
                    processForgetProposal(expired_barrier);
                    $expired_barrier_element.remove();
                }
            }
        });
    };

    const barrierHasExpired = (expired_barriers, barrier, barrier2) => {
        if (barrier2) {
            return containsArray(expired_barriers, [[barrier2, barrier]]);
        }
        return (expired_barriers.indexOf((barrier).toString()) > -1);
    };

    const processForgetProposal = (expired_barrier) => {
        const prices = MBPrice.getPrices();
        Object.keys(prices[expired_barrier]).forEach((c) => {
            if (!getPropertyValue(prices[expired_barrier][c], 'error')) {
                BinarySocket.send({ forget: prices[expired_barrier][c].proposal.id });
            }
        });
    };

    const processForgetProposals = () => {
        MBPrice.showPriceOverlay();
        const forget_proposal = BinarySocket.send({
            forget_all: 'proposal_array',
        });
        forget_proposal.then(() => {
            MBPrice.cleanup();
        });
        return forget_proposal;
    };

    const processForgetTicks = () => BinarySocket.send({ forget_all: 'ticks' });

    const forgetTradingStreams = () => {
        processForgetProposals();
        processForgetTicks();
    };

    const containsArray = (array, val) => {
        const hash = {};
        for (let i = 0; i < array.length; i++) {
            hash[array[i]] = i;
        }
        return getPropertyValue(hash, val);
    };

    const onUnload = () => {
        forgetTradingStreams();
        clearSymbolTimeout();
        clearContractTimeout();
        MBSymbols.clearData();
        MBTick.clean();
    };

    return {
        getSymbols,
        getContracts,
        processPriceRequest,
        processForgetTicks,
        onUnload,
    };
})();

module.exports = MBProcess;
<commit_msg>change naming to make boolean more obvious<commit_after>const MBContract       = require('./mb_contract');
const MBDefaults       = require('./mb_defaults');
const MBNotifications  = require('./mb_notifications');
const MBPrice          = require('./mb_price');
const MBSymbols        = require('./mb_symbols');
const MBTick           = require('./mb_tick');
const commonTrading    = require('../trade/common');
const BinaryPjax       = require('../../base/binary_pjax');
const Client           = require('../../base/client');
const BinarySocket     = require('../../base/socket');
const jpClient         = require('../../common/country_base').jpClient;
const isCryptocurrency = require('../../common/currency').isCryptocurrency;
const getLanguage      = require('../../../_common/language').get;
const localize         = require('../../../_common/localize').localize;
const State            = require('../../../_common/storage').State;
const urlForStatic     = require('../../../_common/url').urlForStatic;
const getPropertyValue = require('../../../_common/utility').getPropertyValue;

const MBProcess = (() => {
    let market_status = '';

    let symbols_timeout,
        contract_timeout;

    const getSymbols = () => {
        BinarySocket.wait('website_status').then((website_status) => {
            const landing_company_obj = State.getResponse('landing_company');
            const allowed_markets     = Client.currentLandingCompany().legal_allowed_markets;
            if (Client.isLoggedIn() && allowed_markets && allowed_markets.indexOf('forex') === -1) {
                BinaryPjax.load('trading');
                return;
            }
            const req = {
                active_symbols: 'brief',
                product_type  : 'multi_barrier',
            };
            if (landing_company_obj) {
                req.landing_company = landing_company_obj.financial_company ? landing_company_obj.financial_company.shortcode : 'japan';
            } else if (website_status.website_status.clients_country === 'jp' || getLanguage() === 'JA') {
                req.landing_company = 'japan';
            }
            BinarySocket.send(req, { msg_type: 'active_symbols' }).then((response) => {
                processActiveSymbols(response);
            });
        });
    };

    /*
     * This function processes the active symbols to get markets
     * and underlying list
     */
    const processActiveSymbols = (data) => {
        if (getPropertyValue(data, 'error')) {
            MBNotifications.show({ text: data.error.message, uid: 'ACTIVE_SYMBOLS' });
            return;
        }

        // populate the Symbols object
        MBSymbols.details(data);

        const is_show_all  = Client.isLoggedIn() && !jpClient();
        const symbols_list = is_show_all ? MBSymbols.getAllSymbols() : MBSymbols.underlyings().major_pairs;
        let symbol         = MBDefaults.get('underlying');

        if (!symbol || !symbols_list[symbol]) {
            symbol = undefined;
            MBDefaults.remove('underlying');
        }

        // check if all symbols are inactive
        let is_market_closed = true;
        Object.keys(symbols_list).forEach((s) => {
            if (symbols_list[s].is_active) {
                is_market_closed = false;
            }
        });
        clearSymbolTimeout();
        if (is_market_closed) {
            handleMarketClosed();
        } else {
            handleMarketOpen();
            populateUnderlyings(symbol);

            if (symbol && !symbols_list[symbol].is_active) {
                MBNotifications.show({ text: localize('This symbol is not active. Please try another symbol.'), uid: 'SYMBOL_INACTIVE' });
            } else {
                processMarketUnderlying();
            }
        }
    };

    const populateUnderlyings = (selected) => {
        const $underlyings = $('#underlying');
        const all_symbols  = MBSymbols.getAllSymbols();

        const $list = $underlyings.find('.list');
        $list.empty();
        $underlyings.find('.current').html($('<div/>', { class: 'gr-row' })
            .append($('<img/>', { class: 'gr-3 gr-no-gutter-m' }))
            .append($('<span/>', { class: 'name gr-6 gr-5-m align-self-center' }))
            .append($('<span/>', { class: 'gr-3 gr-4-m align-self-center still', id: 'spot' })));

        let selected_symbol = selected;
        if (Object.keys(all_symbols).indexOf(selected) === -1) selected_symbol = '';
        Object.keys(all_symbols).forEach((symbol, idx) => {
            if (all_symbols[symbol].is_active) {
                const is_current = (!selected_symbol && idx === 0) || symbol === selected_symbol;
                const $current   = $('<div/>', { value: symbol, class: 'gr-4 gr-4-t gr-4-m' })
                    .append($('<img/>', { src: urlForStatic(`/images/pages/mb_trading/${symbol.toLowerCase()}.svg`), alt: '' }))
                    .append($('<div/>', { text: all_symbols[symbol].display, class: 'name align-self-center' }));
                $list.append($current);
                if (is_current) {
                    MBContract.setCurrentItem($underlyings, symbol, 1);
                }
            }
        });
    };

    const selectors = '.trade_form, .price-table, #trading_bottom_content, .selection_wrapper, #trade_live_chart';

    const handleMarketClosed = () => {
        $(selectors).setVisibility(0);
        hideShowMbTrading('hide');
        MBNotifications.show({ text: localize('Market is closed. Please try again later.'), uid: 'MARKET_CLOSED' });
        symbols_timeout = setTimeout(() => { getSymbols(); }, 30000);
    };

    const handleMarketOpen = () => {
        $(selectors).setVisibility(1);
        hideShowMbTrading('show');
        MBNotifications.hide('MARKET_CLOSED');
    };

    const hideShowMbTrading = (action) => {
        const classes = ['gr-5 ', 'gr-12 ']; // the extra space is so gr-5-m is not replaced
        const show    = action === 'show';
        const $parent = $('#mb_trading').parent();
        $parent.attr('class', $parent.attr('class').replace(classes[+show], classes[+!show]));
    };

    const clearSymbolTimeout = () => {
        clearTimeout(symbols_timeout);
    };

    /*
     * Function to call when underlying has changed
     */
    const processMarketUnderlying = () => {
        const underlying = $('#underlying').attr('value');
        MBDefaults.set('underlying', underlying);

        commonTrading.showFormOverlay();

        // forget the old tick id i.e. close the old tick stream
        processForgetTicks().then(() => {
            // get ticks for current underlying
            MBTick.request(underlying);
            MBTick.clean();
        });

        BinarySocket.clearTimeouts();

        getContracts(underlying);
    };

    const getContracts = (underlying) => {
        const req = {
            contracts_for: (underlying || MBDefaults.get('underlying')),
            currency     : MBContract.getCurrency(),
            product_type : 'multi_barrier',
        };
        BinarySocket.send(req).then((response) => {
            MBNotifications.hide('CONNECTION_ERROR');
            MBContract.setContractsResponse(response);
            // contracts_for is triggered every 15 seconds to check for expired barriers
            // but we don't want to send proposal in that case
            // so getContracts will be called without underlying param to distinguish these two cases
            processContract(response, underlying);
        });
        if (contract_timeout) clearContractTimeout();
        contract_timeout = setTimeout(getContracts, 15000);
    };

    const clearContractTimeout = () => { clearTimeout(contract_timeout); };

    /*
     * Function to display contract form for current underlying
     */
    const processContract = (contracts, should_send_proposal) => {
        if (getPropertyValue(contracts, 'error')) {
            MBNotifications.show({ text: contracts.error.message, uid: contracts.error.code });
            return;
        }

        State.set('is_chart_allowed', !(contracts.contracts_for && contracts.contracts_for.feed_license && contracts.contracts_for.feed_license === 'chartonly'));

        checkMarketStatus(contracts.contracts_for.close);

        MBContract.populateOptions(should_send_proposal ? 'rebuild' : null);
        if (should_send_proposal) {
            processPriceRequest();
        } else {
            processExpiredBarriers();
        }
    };

    const checkMarketStatus = (close) => {
        const now = window.time.unix();

        // if market is closed, else if market is open
        if (now > close) {
            if (market_status === 'open') {
                handleMarketClosed();
            }
            market_status = 'closed';
        } else {
            if (market_status === 'closed') {
                getSymbols();
                handleMarketOpen();
            }
            market_status = 'open';
        }
    };

    const processPriceRequest = () => {
        MBPrice.increaseReqId();
        MBPrice.showPriceOverlay();
        const available_contracts = MBContract.getCurrentContracts();
        const durations           = MBDefaults.get('period').split('_');
        const jp_client           = jpClient();
        const is_crypto           = isCryptocurrency(MBDefaults.get('currency'));
        const payout              = parseFloat(MBDefaults.get(`payout${is_crypto ? '_crypto' : ''}`));

        const req = {
            proposal_array: 1,
            subscribe     : 1,
            basis         : 'payout',
            amount        : jp_client ? (parseInt(payout) || 1) * 1000 : payout,
            currency      : MBContract.getCurrency(),
            symbol        : MBDefaults.get('underlying'),
            passthrough   : { req_id: MBPrice.getReqId() },
            date_expiry   : durations[1],
            contract_type : [],
            barriers      : [],
            product_type  : 'multi_barrier',

            trading_period_start: durations[0],
        };

        // contract_type
        available_contracts.forEach(c => req.contract_type.push(c.contract_type));

        // barriers
        let all_expired = true;
        const contract  = available_contracts[0];
        contract.available_barriers.forEach((barrier) => {
            const barrier_item = {};
            if (+contract.barriers === 2) {
                barrier_item.barrier  = barrier[1];
                barrier_item.barrier2 = barrier[0];
            } else {
                barrier_item.barrier = barrier;
            }
            if (!barrierHasExpired(contract.expired_barriers, barrier_item.barrier, barrier_item.barrier2)) {
                all_expired = false;
                req.barriers.push(barrier_item);
            }
        });

        processForgetProposals().then(() => {
            // send request
            if (req.barriers.length) {
                MBPrice.addPriceObj(req);
                BinarySocket.send(req, { callback: processProposal });
            }
        });

        // all barriers expired
        if (all_expired) {
            MBNotifications.show({ text: `${localize('All barriers in this trading window are expired')}.`, uid: 'ALL_EXPIRED' });
            MBPrice.hidePriceOverlay();
        } else {
            MBNotifications.hide('ALL_EXPIRED');
        }
    };

    const processProposal = (response) => {
        const req_id = MBPrice.getReqId();
        if (response.passthrough.req_id === req_id) {
            if (response.error) {
                const error_message = response.error.error ? response.error.error.message : response.error.message;
                MBNotifications.show({ text: error_message, uid: 'PROPOSAL', dismissible: false });
                return;
            }
            MBNotifications.hide('PROPOSAL');
            MBPrice.display(response);
        }
    };

    const processExpiredBarriers = () => {
        const contracts = MBContract.getCurrentContracts();
        let expired_barrier,
            $expired_barrier_element;
        contracts.forEach((c) => {
            const expired_barriers = c.expired_barriers;
            for (let i = 0; i < expired_barriers.length; i++) {
                if (+c.barriers === 2) {
                    expired_barrier = [expired_barriers[i][0], expired_barriers[i][1]].join('_');
                } else {
                    expired_barrier = expired_barriers[i];
                }
                $expired_barrier_element = $(`div [data-barrier="${expired_barrier}"]`);
                if ($expired_barrier_element.length > 0) {
                    processForgetProposal(expired_barrier);
                    $expired_barrier_element.remove();
                }
            }
        });
    };

    const barrierHasExpired = (expired_barriers, barrier, barrier2) => {
        if (barrier2) {
            return containsArray(expired_barriers, [[barrier2, barrier]]);
        }
        return (expired_barriers.indexOf((barrier).toString()) > -1);
    };

    const processForgetProposal = (expired_barrier) => {
        const prices = MBPrice.getPrices();
        Object.keys(prices[expired_barrier]).forEach((c) => {
            if (!getPropertyValue(prices[expired_barrier][c], 'error')) {
                BinarySocket.send({ forget: prices[expired_barrier][c].proposal.id });
            }
        });
    };

    const processForgetProposals = () => {
        MBPrice.showPriceOverlay();
        const forget_proposal = BinarySocket.send({
            forget_all: 'proposal_array',
        });
        forget_proposal.then(() => {
            MBPrice.cleanup();
        });
        return forget_proposal;
    };

    const processForgetTicks = () => BinarySocket.send({ forget_all: 'ticks' });

    const forgetTradingStreams = () => {
        processForgetProposals();
        processForgetTicks();
    };

    const containsArray = (array, val) => {
        const hash = {};
        for (let i = 0; i < array.length; i++) {
            hash[array[i]] = i;
        }
        return getPropertyValue(hash, val);
    };

    const onUnload = () => {
        forgetTradingStreams();
        clearSymbolTimeout();
        clearContractTimeout();
        MBSymbols.clearData();
        MBTick.clean();
    };

    return {
        getSymbols,
        getContracts,
        processPriceRequest,
        processForgetTicks,
        onUnload,
    };
})();

module.exports = MBProcess;
<|endoftext|>


<commit_before>
Object.defineConstant = function defineConstant(obj, prop, value, enumerable) {
	return Object.defineProperty(obj, prop, { value:value, enumerable:enumerable===true, writable:false, configurable:false });
};

var monkeyscript = {
	version: '0.0.0.0a',
	hooks: {},
	rc: [],
	included: []
};

(function(_native) {
	delete global._native;

	// Arguments
	var args = Array.slice(_native.arguments);
	if ( args[0] === '-h' ) {
		args.shift();
		monkeyscript.hookName = args.shift();
	} else {
		monkeyscript.scriptName = args.shift();
	}

	Object.seal(args);
	monkeyscript.arguments = args;
	global.arguments = Array.slice(args);

	global.Kernel = _native.Kernel;
	Object.seal(Kernel);

	monkeyscript.rc.push(
		Kernel.configDir + Kernel.os.fileSeparator + 'monkeyscriptrc.js',
		Kernel.configDir + Kernel.os.fileSeparator + 'monkeyscriptrc'
	);
	var baseNames = ['.monkeyscriptrc.js', '.monkeyscriptrc'];
	[
		Kernel.home,
		Kernel.currentWorkingDirectory
	].forEach(function(basePath) {
		baseNames.forEach(function(baseName) {
			monkeyscript.rc.push( basePath + Kernel.os.fileSeparator + baseName );
		});
	});
	Object.seal(monkeyscript.rc);

})(_native);

// Setup global Error objects
var IOError = Kernel.newError("IOError");

for each( let rc in monkeyscript.rc ) {
	try { include.ifExists(rc); }
	// Other errors will be printed, but still not affect running the script
	catch( e ) { print(e); }
}

if ( monkeyscript.hookName ) {
	let scriptName = monkeyscript.hooks[monkeyscript.hookName];
	if ( scriptName )
		include(scriptName);
	else
		throw new Error("The hook "+monkeyscript.hookName+" does not exist");
} else if ( monkeyscript.scriptName ) {
	if ( monkeyscript.scriptName.startsWith('javascript:') )
		eval(monkeyscript.scriptName.substr('javascript:'.length));
	else
		include(monkeyscript.scriptName);
} else
	throw new Error("No script specified");

<commit_msg>Use die instead of throw for load issues<commit_after>
Object.defineConstant = function defineConstant(obj, prop, value, enumerable) {
	return Object.defineProperty(obj, prop, { value:value, enumerable:enumerable===true, writable:false, configurable:false });
};

var monkeyscript = {
	version: '0.0.0.0a',
	hooks: {},
	rc: [],
	included: []
};

(function(_native) {
	delete global._native;

	// Arguments
	var args = Array.slice(_native.arguments);
	if ( args[0] === '-h' ) {
		args.shift();
		monkeyscript.hookName = args.shift();
	} else {
		monkeyscript.scriptName = args.shift();
	}

	Object.seal(args);
	monkeyscript.arguments = args;
	global.arguments = Array.slice(args);

	global.Kernel = _native.Kernel;
	Object.seal(Kernel);

	monkeyscript.rc.push(
		Kernel.configDir + Kernel.os.fileSeparator + 'monkeyscriptrc.js',
		Kernel.configDir + Kernel.os.fileSeparator + 'monkeyscriptrc'
	);
	var baseNames = ['.monkeyscriptrc.js', '.monkeyscriptrc'];
	[
		Kernel.home,
		Kernel.currentWorkingDirectory
	].forEach(function(basePath) {
		baseNames.forEach(function(baseName) {
			monkeyscript.rc.push( basePath + Kernel.os.fileSeparator + baseName );
		});
	});
	Object.seal(monkeyscript.rc);

})(_native);

// Setup global Error objects
var IOError = Kernel.newError("IOError");

for each( let rc in monkeyscript.rc ) {
	try { include.ifExists(rc); }
	// Other errors will be printed, but still not affect running the script
	catch( e ) { print(e); }
}

if ( monkeyscript.hookName ) {
	let scriptName = monkeyscript.hooks[monkeyscript.hookName];
	if ( scriptName )
		include(scriptName);
	else
		Kernel.die("The hook "+monkeyscript.hookName+" does not exist");
} else if ( monkeyscript.scriptName ) {
	if ( monkeyscript.scriptName.startsWith('javascript:') )
		eval(monkeyscript.scriptName.substr('javascript:'.length));
	else
		include(monkeyscript.scriptName);
} else
	Kernel.die("No script specified");

<|endoftext|>