<commit_before>"use strict";

var WebdriverIO = require('webdriverio/lib/webdriverio');
var MultiDevice = require('./multidevice');
var Flow = require('./flow/flow')
var Device = require('./flow/device')
var Checkpoint = require('./flow/checkpoint')
var Step = require('./flow/step')
var path = require('path')
var fs = require('fs')

/**
 * @param options
 * @param modifier
 * @param {MultiDevice} multiDevice
 * @param {Flow} flow
 * @returns {WebdriverIO.Client}
 */
function remote(options, modifier, multiDevice, flow) {

    options = options || {};

    let device = new Device(options)
    if (flow) {
        flow.addDevices([device])
    }

    let screenshotBlackList = {
        protocolQueries: ['element', 'elementActive', 'elementIdAttribute', 'elementIdCssProperty', 'elementIdDisplayed', 'elementIdElement', 'elementIdElements', 'elementIdEnabled', 'elementIdLocation', 'elementIdLocationInView', 'elementIdName', 'elementIdSelected', 'elementIdSize', 'elementIdText', 'elements', 'title', 'windowHandle', 'windowHandles'],
        stateQueries: ['isEnabled', 'isExisting', 'isSelected', 'isVisible', 'isVisibleWithinViewport'],
        cookieCommands: ['deleteCookie', 'getCookie', 'setCookie', 'cookie'],
        elementPropertyQueries: ['getAttribute', 'getCssProperty', 'getElementSize', 'getHTML', 'getLocation', 'getLocationInView', 'getSource', 'getTagName', 'getText', 'getTitle', 'getUrl', 'getValue'],
        interferingCommands: ['init', 'end', 'endAll', 'session', 'sessions', 'saveScreenshot', 'screenshot'],
        otherCommandsWithoutSideEffects: ['checkpoint', 'timeouts', 'timeoutsAsyncScript', 'timeoutsImplicitWait', 'windowHandlePosition', 'getCommandHistory', 'getTabIds', 'getViewportSize'],
    };
    // Merge all blacklists into one list
    screenshotBlackList = Object.keys(screenshotBlackList).map(key => screenshotBlackList[key]).reduce((previous, current) => previous.concat(current), [])

    /**
     * Initialise monad
     */
    var unit = WebdriverIO(options, modifier)

    unit.lift('checkpoint', function(checkpointName) {
        //let deviceId = this.options.id;

        return this.saveScreenshot(function (err, screenshot, response) {
            //let device = flow.device(deviceId)

            // Create new step with screenshot
            device.addStep(new Step(flow.generateStepId(), [], screenshot))

            // Create new checkpoint
            device.addCheckpoint(new Checkpoint(flow.generateCheckpointId(), checkpointName))
        })
    })

    /**
     * Add the webdriverIO commands
     * Build prototype: commands
     */
    ;['protocol', 'commands'].forEach(function(commandType) {
        var dir = path.join(__dirname, '../node_modules/webdriverio', 'lib', commandType)
        var files = fs.readdirSync(dir)

        files.forEach(function(filename) {
            let commandName = filename.slice(0, -3)
            let commandFunc = require(path.join(dir, filename))

            /**
             * Command proxy: Take a screenshot between commands
             * @returns {WebdriverIO.Client}
             */
            let proxyCommandFunc = function () {
                // Default: no additional behaviour
                let intermediate = this;

                if (commandName === 'end' || commandName == 'endAll') {
                    // Serialize and store the steps and screenshots only at the end.
                    // Storing flows with large screenshots can take a while.
                    // TODO only rewrite file of cache is dirty
                    flow.store()
                } else if (screenshotBlackList.indexOf(commandName) === -1) {
                    //let deviceId = this.options.id;

                    //console.log(globalStep, deviceId, commandName);

                    device.addStep(new Step(flow.generateStepId(), commandName, null))

                    // Take a screenshot before each command
                    // This does not override the original commands return value
                    // TODO look at promise.finally
                    // TODO ensure last command is finished, use waitUntil?
                    // TODO think about taking screenshots on all devices to capture side effects
                    intermediate = this.saveScreenshot(function (err, screenshot, response) {
                        if (err) {
                            console.log(err);
                            throw new err;
                        }
                        device.addStep(new Step(flow.generateStepId(), [], screenshot));
                    });
                }

                // Execute function after the intermediate
                return commandFunc.apply(intermediate, arguments);
            }

            unit.lift(commandName, proxyCommandFunc);
        });
    });

    /**
     * Add xdTesting commands
     */
    ['commands'].forEach(function (commandType) {
        var dir = path.join(__dirname, commandType),
            files = fs.readdirSync(dir);

        files.forEach(function (filename) {
            var commandName = filename.slice(0, -3); // remove .js suffix
            unit.lift(commandName, require(path.join(dir, filename))(multiDevice));
        });
    });

    var prototype = unit();
    prototype.defer.resolve();
    return prototype;
}

module.exports.remote = remote;

/**
 * @param options
 * @returns {WebdriverIO.Client}
 */
function multiremote(options) {

    // Add id key to options
    Object.keys(options).forEach(function (id) {
        options[id]['id'] = id;
    });

    var multiDevice = new MultiDevice(options);
    var flow = new Flow();

    Object.keys(options).forEach(function(browserName) {
        multiDevice.addInstance(
            browserName,
            remote(options[browserName], multiDevice.getInstanceModifier(), multiDevice, flow)
        );
    });

    var multiModifier = multiDevice.getModifier();
    return remote(options, multiModifier, multiDevice);
}

module.exports.multiremote = multiremote;
<commit_msg>Fix JSDoc<commit_after>"use strict";

var WebdriverIO = require('webdriverio/lib/webdriverio');
var MultiDevice = require('./multidevice');
var Flow = require('./flow/flow')
var Device = require('./flow/device')
var Checkpoint = require('./flow/checkpoint')
var Step = require('./flow/step')
var path = require('path')
var fs = require('fs')

/**
 * @param options
 * @param modifier
 * @param {MultiDevice} multiDevice
 * @param {Flow|null} flow Required for and only for instances of a multi device, but not for the multi device collection itself.
 * @returns {WebdriverIO.Client}
 */
function remote(options, modifier, multiDevice, flow) {

    options = options || {};

    let device = new Device(options)
    if (flow) {
        flow.addDevices([device])
    }

    let screenshotBlackList = {
        protocolQueries: ['element', 'elementActive', 'elementIdAttribute', 'elementIdCssProperty', 'elementIdDisplayed', 'elementIdElement', 'elementIdElements', 'elementIdEnabled', 'elementIdLocation', 'elementIdLocationInView', 'elementIdName', 'elementIdSelected', 'elementIdSize', 'elementIdText', 'elements', 'title', 'windowHandle', 'windowHandles'],
        stateQueries: ['isEnabled', 'isExisting', 'isSelected', 'isVisible', 'isVisibleWithinViewport'],
        cookieCommands: ['deleteCookie', 'getCookie', 'setCookie', 'cookie'],
        elementPropertyQueries: ['getAttribute', 'getCssProperty', 'getElementSize', 'getHTML', 'getLocation', 'getLocationInView', 'getSource', 'getTagName', 'getText', 'getTitle', 'getUrl', 'getValue'],
        interferingCommands: ['init', 'end', 'endAll', 'session', 'sessions', 'saveScreenshot', 'screenshot'],
        otherCommandsWithoutSideEffects: ['checkpoint', 'timeouts', 'timeoutsAsyncScript', 'timeoutsImplicitWait', 'windowHandlePosition', 'getCommandHistory', 'getTabIds', 'getViewportSize'],
    };
    // Merge all blacklists into one list
    screenshotBlackList = Object.keys(screenshotBlackList).map(key => screenshotBlackList[key]).reduce((previous, current) => previous.concat(current), [])

    /**
     * Initialise monad
     */
    var unit = WebdriverIO(options, modifier)

    unit.lift('checkpoint', function(checkpointName) {
        //let deviceId = this.options.id;

        return this.saveScreenshot(function (err, screenshot, response) {
            //let device = flow.device(deviceId)

            // Create new step with screenshot
            device.addStep(new Step(flow.generateStepId(), [], screenshot))

            // Create new checkpoint
            device.addCheckpoint(new Checkpoint(flow.generateCheckpointId(), checkpointName))
        })
    })

    /**
     * Add the webdriverIO commands
     * Build prototype: commands
     */
    ;['protocol', 'commands'].forEach(function(commandType) {
        var dir = path.join(__dirname, '../node_modules/webdriverio', 'lib', commandType)
        var files = fs.readdirSync(dir)

        files.forEach(function(filename) {
            let commandName = filename.slice(0, -3)
            let commandFunc = require(path.join(dir, filename))

            /**
             * Command proxy: Take a screenshot between commands
             * @returns {WebdriverIO.Client}
             */
            let proxyCommandFunc = function () {
                // Default: no additional behaviour
                let intermediate = this;

                if (commandName === 'end' || commandName == 'endAll') {
                    // Serialize and store the steps and screenshots only at the end.
                    // Storing flows with large screenshots can take a while.
                    // TODO only rewrite file of cache is dirty
                    flow.store()
                } else if (screenshotBlackList.indexOf(commandName) === -1) {
                    //let deviceId = this.options.id;

                    //console.log(globalStep, deviceId, commandName);

                    device.addStep(new Step(flow.generateStepId(), commandName, null))

                    // Take a screenshot before each command
                    // This does not override the original commands return value
                    // TODO look at promise.finally
                    // TODO ensure last command is finished, use waitUntil?
                    // TODO think about taking screenshots on all devices to capture side effects
                    intermediate = this.saveScreenshot(function (err, screenshot, response) {
                        if (err) {
                            console.log(err);
                            throw new err;
                        }
                        device.addStep(new Step(flow.generateStepId(), [], screenshot));
                    });
                }

                // Execute function after the intermediate
                return commandFunc.apply(intermediate, arguments);
            }

            unit.lift(commandName, proxyCommandFunc);
        });
    });

    /**
     * Add xdTesting commands
     */
    ['commands'].forEach(function (commandType) {
        var dir = path.join(__dirname, commandType),
            files = fs.readdirSync(dir);

        files.forEach(function (filename) {
            var commandName = filename.slice(0, -3); // remove .js suffix
            unit.lift(commandName, require(path.join(dir, filename))(multiDevice));
        });
    });

    var prototype = unit();
    prototype.defer.resolve();
    return prototype;
}

module.exports.remote = remote;

/**
 * @param options
 * @returns {WebdriverIO.Client}
 */
function multiremote(options) {

    // Add id key to options
    Object.keys(options).forEach(function (id) {
        options[id]['id'] = id;
    });

    var multiDevice = new MultiDevice(options);
    var flow = new Flow();

    Object.keys(options).forEach(function(browserName) {
        multiDevice.addInstance(
            browserName,
            remote(options[browserName], multiDevice.getInstanceModifier(), multiDevice, flow)
        );
    });

    var multiModifier = multiDevice.getModifier();
    return remote(options, multiModifier, multiDevice, null);
}

module.exports.multiremote = multiremote;
<|endoftext|>


<commit_before>(function() {
	function searchInElement(element) {
		for (var i = 0; i < element.childNodes.length; i++) {
			var child = element.childNodes[i];
			if (child.nodeType == Node.ELEMENT_NODE) {
				var tag = child.nodeName.toLowerCase();
				if (tag == 'a') {
					searchInLink(child);
				} else if (tag != 'style' && tag != 'script' ) // special cases, don't touch CDATA elements
					searchInElement(child);
			} else if (child.nodeType == Node.TEXT_NODE) {
				searchInText(child);
			}
		}
	}
	function searchInText(text) {
		var match;
		var matches = [];
		idRegex.lastIndex = 0; //Reset the regex object
		while ((match = idRegex.exec(text.data)) !== null) {
			matches.push(match);
		}

		function generateCallback(match, text) {
			return function(courseInfo) {
				if (courseInfo !== undefined) {
					insertLink(courseInfo, match, text);
					insertButton(courseInfo, text.nextSibling);
				}
			};
		}

		for (var i = 0; i < matches.length; i++) {
			var id = normalizeCourseId(matches[i][0]);
			getCourseInfo(id, generateCallback(matches[i], text));
		}
	}

	function searchInLink(link) {
		function generateCallback(link) {
			return function(courseInfo) {
				if (courseInfo !== undefined) {
					insertButton(courseInfo, link);
				}
			};
		}

		linkRegex.lastIndex = 0; //Reset the regex object
		var match = linkRegex.exec(link.href);
		if (match !== null) {
			var id = normalizeCourseId(match[1]);
			getCourseInfo(id, generateCallback(link));
		}
	}

	function getCourseInfo(id, callback) {
		if (courseInfoCache[id] !== undefined) {
			callback(courseInfoCache[id]);
		}
		else if (courseInfoCallbacks[id] !== undefined) {
			courseInfoCallbacks[id].push(callback);
		} else {
			courseInfoCallbacks[id] = [callback];

			var request = new XMLHttpRequest();

			request.onreadystatechange = function() {
				if (request.readyState == XMLHttpRequest.DONE) {
					if (request.status == 200) {
						var parser = new DOMParser();
						var doc = parser.parseFromString(request.responseText, "text/html");

						var metas = doc.getElementsByTagName("meta");
						var token = null;
						for (var i = 0; i < metas.length; i++) {
							if (metas[i].getAttribute("name") == "csrf-token") {
								token = metas[i].getAttribute("content");
							}
						}

						var bookmarkbutton = doc.getElementsByClassName("button playlist link off")[0];
						var isBookmarked = true;
						if (bookmarkbutton.classList.contains("disabled")) {
							isBookmarked = false;
						}

						if (token !== null) {
							var courseInfo = {
								id: id,
								isBookmarked: isBookmarked,
								token: token
							};
							courseInfoCache[id] = courseInfo;
							for (i = 0; i < courseInfoCallbacks[id].length; i++) {
								courseInfoCallbacks[id][i](courseInfo);
							}
							courseInfoCallbacks.splice(id, 1);
						}
					}
				}
			};
			request.open("GET", "https://supermariomakerbookmark.nintendo.net/courses/"+id);
			request.withCredentials = true;
			request.send();
		}
	}

	function normalizeCourseId(string) {
		return string.replace(/ /g, '-').toUpperCase();
	}

	function insertLink(courseInfo, match, text) {
		var link = document.createElement('a');
		link.href = 'https://supermariomakerbookmark.nintendo.net/courses/'+courseInfo.id;
		link.appendChild(document.createTextNode(match[0]));
		text.splitText(match.index);
		text.nextSibling.splitText(match[0].length);
		text.parentNode.replaceChild(link, text.nextSibling);

	}

	function insertButton(courseInfo, text) {
		var directbookmark = document.createElement('a');
		directbookmark.href = "";
		if (courseInfo.isBookmarked) {
			directbookmark.onclick = function() {
				setBookmark(courseInfo.id, courseInfo.token);
				return false;
			};
			imageSrc = imageBookmark;
		}
		else {
			directbookmark.onclick = function() {
				unsetBookmark(courseInfo.id, courseInfo.token);
				return false;
			};
			imageSrc = imageUnbookmark;
		}
		directbookmarkimage = document.createElement('img');
		directbookmarkimage.setAttribute('src', imageSrc);
		directbookmarkimage.className = "marioMakerDirectBookmark";
		directbookmark.appendChild(directbookmarkimage);

		text.parentNode.insertBefore(directbookmark, text.nextSibling);
		if (bookmarkButtonDict[courseInfo.id] === undefined) {
			bookmarkButtonDict[courseInfo.id] = [];
		}
		bookmarkButtonDict[courseInfo.id].push(directbookmark);

	}

	function setBookmark(id, token) {
		var request = new XMLHttpRequest();

		request.onreadystatechange = function() {
			if (request.readyState == XMLHttpRequest.DONE) {
				if (request.status == 200) {
					for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
						bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageUnbookmark);
						bookmarkButtonDict[id][i].onclick = function() {
							unsetBookmark(id, token);
							return false;
						};
					}
				}
				else {
					alert("Error. Please make sure that you are logged in on supermariomakerbookmark.nintendo.net");
                    for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
							bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageBookmark);
							bookmarkButtonDict[id][i].onclick = function(event) {
								setBookmark(id, token, event.target);
								return false;
						};
					}
				}
			}
		};

		request.open('POST', "https://supermariomakerbookmark.nintendo.net/courses/"+id+"/play_at_later");
		request.setRequestHeader("Accept", "application/json");
		request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
		request.setRequestHeader("X-CSRF-Token", token);
		request.setRequestHeader("Referer", "https://supermariomakerbookmark.nintendo.net/courses/"+id);
		request.withCredentials = true;
        for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
			 bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageLoading);
			bookmarkButtonDict[id][i].onclick = doNothing;
		}
		request.send();
	}

	function unsetBookmark(id, token) {
		var request = new XMLHttpRequest();

		request.onreadystatechange = function() {
			if (request.readyState == XMLHttpRequest.DONE) {
				if (request.status == 200) {
					for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
						bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageBookmark);
						bookmarkButtonDict[id][i].onclick = function() {
							setBookmark(id, token);
							return false;
						};
					}
				}
				else {
					alert("Error. Please make sure that you are logged in on supermariomakerbookmark.nintendo.net");
                    for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
						bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageUnBookmark);
						bookmarkButtonDict[id][i].onclick = function(event) {
							unsetBookmark(id, token, event.target);
							return false;
						};
					}
				}
			}
		};

		request.open('DELETE', "https://supermariomakerbookmark.nintendo.net/bookmarks/"+id);
		request.setRequestHeader("Accept", "application/json");
		request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
		request.setRequestHeader("X-CSRF-Token", token);
		request.setRequestHeader("Referer", "https://supermariomakerbookmark.nintendo.net/courses/"+id);
		request.withCredentials = true;
        for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
			bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageLoading);
			bookmarkButtonDict[id][i].onclick = doNothing;
		}
		request.send();
	}

	function doNothing() { //What? This way I don't have to recreate the function all the time.
		return false;
	}

	function onDOMChange(mutations) {
		mutations.forEach(function(mutation) {
			for (var i = 0; i < mutation.addedNodes.length; i++) {
				node = mutation.addedNodes[i];
				if (node.parentNode.nextSibling === null || !node.parentNode.nextSibling.classList.contains("marioMakerDirectBookmark")) {
					if (node.nodeType == Node.ELEMENT_NODE) {
						var tag = node.nodeName.toLowerCase();
						if (tag == 'a') {
							searchInLink(node);
						}
						else if (tag != 'style' && tag != 'script' ) // special cases, don't touch CDATA elements
							searchInElement(node);
					} else if (node.nodeType == Node.TEXT_NODE) {
						searchInText(node);
					}
				}
			}
		});
	}

	//Base64 encoded images to prevent dealing with browser specific resource loading
	var imageBookmark = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wwWEgEDAI72/QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAQElEQVQ4y2P8//8/AyWACZ+k3EbG/3IbGf+TbQDFLhghBjD+//+fgVBI4wKP/P8zUscF+NIBzKbRaKR1OqAEAACkzxevPUlGPgAAAABJRU5ErkJggg==';
	var imageUnbookmark = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wwWEgIEtccwnQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAASklEQVQ4y2P8//8/AyWACZ+k3EZGBrmNjOQbQLELhoYBjPhi4R0PD5wt9OULbgPe8fCQFZdCX74w0tYLsDTwyP//aDoYsIREDAAAcYEarSA1+NcAAAAASUVORK5CYII=';
	var imageLoading = 'data:image/gif;base64,R0lGODlhEAAQAMIAAP+EAP+ICP+ICf////+EAP+EAP+EAP+EACH/C05FVFNDQVBFMi4wAwEAAAAh/hFDcmVhdGVkIHdpdGggR0lNUAAh+QQFCgAEACwAAAAAEAAQAAADIji63L7hPQCkqg5bRrf/kKeJQASOYNN91HpN7QII6IXWaQIAIfkEBQoABwAsAQACAA0ADQAAAyQ4urwHoC3wYJTK0sN7h154YM1IOifziCEgXA34MJsXQ+kQSAkAIfkEBQoABwAsAQABAA0ADQAAAyR4CtMNQC0RF4yuWnicf9UnNsroBZBJMuogvXDXeunMftgXHwkAIfkEBQoABwAsAgABAA0ADQAAAyI4urHuIL5DK4BUvgEEqCD4beRBTqcThtEqRtcJf/HWpk4CADs=';

	var idRegex = /(?:[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}|[0-9a-f]{4} [0-9a-f]{4} [0-9a-f]{4} [0-9a-f]{4})/gi;
	var linkRegex = /[Hh][Tt][Tt][Pp][Ss]?:\/\/[Ss][Uu][Pp][Ee][Rr][Mm][Aa][Rr][Ii][Oo][Mm][Aa][Kk][Ee][Rr][Bb][Oo][Oo][Kk][Mm][Aa][Rr][Kk].[Nn][Ii][Nn][Tt][Ee][Nn][Dd][Oo].[Nn][Ee][Tt]\/courses\/([0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4})/g; //Sorry ._. Only way to make regex partually case insensitive

	var courseInfoCache = [];
	var courseInfoCallbacks = [];
	var bookmarkButtonDict = [];

	searchInElement(document.body);

	var observer = new MutationObserver(onDOMChange);
	var config = {childList: true, subtree: true};
	observer.observe(document.body, config);

})();
<commit_msg>Probably fixed issue #11. Testing needed.<commit_after>(function() {
	function searchInElement(element) {
		for (var i = element.childNodes.length-1; i >= 0; i--) {
			var child = element.childNodes[i];
			if (child.nodeType == Node.ELEMENT_NODE) {
				var tag = child.nodeName.toLowerCase();
				if (tag == 'a') {
					searchInLink(child);
				} else if (tag != 'style' && tag != 'script' ) // special cases, don't touch CDATA elements
					searchInElement(child);
			} else if (child.nodeType == Node.TEXT_NODE) {
				searchInText(child);
			}
		}
	}
	function searchInText(text) {
		var match;
		var matches = [];
		idRegex.lastIndex = 0; //Reset the regex object
		while ((match = idRegex.exec(text.data)) !== null) {
			matches.push(match);
		}

		function generateCallback(match, text) {
			return function(courseInfo) {
				if (courseInfo !== undefined) {
					insertLink(courseInfo, match, text);
					insertButton(courseInfo, text.nextSibling);
				}
			};
		}

		for (var i = 0; i < matches.length; i++) {
			var id = normalizeCourseId(matches[i][0]);
			getCourseInfo(id, generateCallback(matches[i], text));
		}
	}

	function searchInLink(link) {
		function generateCallback(link) {
			return function(courseInfo) {
				if (courseInfo !== undefined) {
					insertButton(courseInfo, link);
				}
			};
		}

		linkRegex.lastIndex = 0; //Reset the regex object
		var match = linkRegex.exec(link.href);
		if (match !== null) {
			var id = normalizeCourseId(match[1]);
			getCourseInfo(id, generateCallback(link));
		}
	}

	function getCourseInfo(id, callback) {
		if (courseInfoCache[id] !== undefined) {
			callback(courseInfoCache[id]);
		}
		else if (courseInfoCallbacks[id] !== undefined) {
			courseInfoCallbacks[id].push(callback);
		} else {
			courseInfoCallbacks[id] = [callback];

			var request = new XMLHttpRequest();

			request.onreadystatechange = function() {
				if (request.readyState == XMLHttpRequest.DONE) {
					if (request.status == 200) {
						var parser = new DOMParser();
						var doc = parser.parseFromString(request.responseText, "text/html");

						var metas = doc.getElementsByTagName("meta");
						var token = null;
						for (var i = 0; i < metas.length; i++) {
							if (metas[i].getAttribute("name") == "csrf-token") {
								token = metas[i].getAttribute("content");
							}
						}

						var bookmarkbutton = doc.getElementsByClassName("button playlist link off")[0];
						var isBookmarked = true;
						if (bookmarkbutton.classList.contains("disabled")) {
							isBookmarked = false;
						}

						if (token !== null) {
							var courseInfo = {
								id: id,
								isBookmarked: isBookmarked,
								token: token
							};
							courseInfoCache[id] = courseInfo;
							for (i = 0; i < courseInfoCallbacks[id].length; i++) {
								courseInfoCallbacks[id][i](courseInfo);
							}
							courseInfoCallbacks.splice(id, 1);
						}
					}
				}
			};
			request.open("GET", "https://supermariomakerbookmark.nintendo.net/courses/"+id);
			request.withCredentials = true;
			request.send();
		}
	}

	function normalizeCourseId(string) {
		return string.replace(/ /g, '-').toUpperCase();
	}

	function insertLink(courseInfo, match, text) {
		var link = document.createElement('a');
		link.href = 'https://supermariomakerbookmark.nintendo.net/courses/'+courseInfo.id;
		link.appendChild(document.createTextNode(match[0]));
		text.splitText(match.index);
		text.nextSibling.splitText(match[0].length);
		text.parentNode.replaceChild(link, text.nextSibling);

	}

	function insertButton(courseInfo, text) {
		var directbookmark = document.createElement('a');
		directbookmark.href = "";
		if (courseInfo.isBookmarked) {
			directbookmark.onclick = function() {
				setBookmark(courseInfo.id, courseInfo.token);
				return false;
			};
			imageSrc = imageBookmark;
		}
		else {
			directbookmark.onclick = function() {
				unsetBookmark(courseInfo.id, courseInfo.token);
				return false;
			};
			imageSrc = imageUnbookmark;
		}
		directbookmarkimage = document.createElement('img');
		directbookmarkimage.setAttribute('src', imageSrc);
		directbookmarkimage.className = "marioMakerDirectBookmark";
		directbookmark.appendChild(directbookmarkimage);

		text.parentNode.insertBefore(directbookmark, text.nextSibling);
		if (bookmarkButtonDict[courseInfo.id] === undefined) {
			bookmarkButtonDict[courseInfo.id] = [];
		}
		bookmarkButtonDict[courseInfo.id].push(directbookmark);

	}

	function setBookmark(id, token) {
		var request = new XMLHttpRequest();

		request.onreadystatechange = function() {
			if (request.readyState == XMLHttpRequest.DONE) {
				if (request.status == 200) {
					for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
						bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageUnbookmark);
						bookmarkButtonDict[id][i].onclick = function() {
							unsetBookmark(id, token);
							return false;
						};
					}
				}
				else {
					alert("Error. Please make sure that you are logged in on supermariomakerbookmark.nintendo.net");
                    for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
							bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageBookmark);
							bookmarkButtonDict[id][i].onclick = function(event) {
								setBookmark(id, token, event.target);
								return false;
						};
					}
				}
			}
		};

		request.open('POST', "https://supermariomakerbookmark.nintendo.net/courses/"+id+"/play_at_later");
		request.setRequestHeader("Accept", "application/json");
		request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
		request.setRequestHeader("X-CSRF-Token", token);
		request.setRequestHeader("Referer", "https://supermariomakerbookmark.nintendo.net/courses/"+id);
		request.withCredentials = true;
        for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
			 bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageLoading);
			bookmarkButtonDict[id][i].onclick = doNothing;
		}
		request.send();
	}

	function unsetBookmark(id, token) {
		var request = new XMLHttpRequest();

		request.onreadystatechange = function() {
			if (request.readyState == XMLHttpRequest.DONE) {
				if (request.status == 200) {
					for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
						bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageBookmark);
						bookmarkButtonDict[id][i].onclick = function() {
							setBookmark(id, token);
							return false;
						};
					}
				}
				else {
					alert("Error. Please make sure that you are logged in on supermariomakerbookmark.nintendo.net");
                    for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
						bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageUnBookmark);
						bookmarkButtonDict[id][i].onclick = function(event) {
							unsetBookmark(id, token, event.target);
							return false;
						};
					}
				}
			}
		};

		request.open('DELETE', "https://supermariomakerbookmark.nintendo.net/bookmarks/"+id);
		request.setRequestHeader("Accept", "application/json");
		request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
		request.setRequestHeader("X-CSRF-Token", token);
		request.setRequestHeader("Referer", "https://supermariomakerbookmark.nintendo.net/courses/"+id);
		request.withCredentials = true;
        for (var i = 0; i < bookmarkButtonDict[id].length; i++) {
			bookmarkButtonDict[id][i].firstChild.setAttribute('src', imageLoading);
			bookmarkButtonDict[id][i].onclick = doNothing;
		}
		request.send();
	}

	function doNothing() { //What? This way I don't have to recreate the function all the time.
		return false;
	}

	function onDOMChange(mutations) {
		mutations.forEach(function(mutation) {
			for (var i = 0; i < mutation.addedNodes.length; i++) {
				node = mutation.addedNodes[i];
				if (node.parentNode !== null && (node.parentNode.nextSibling === null || (node.parentNode.nextSibling.classList !== undefined && node.parentNode.nextSibling.classList.contains("marioMakerDirectBookmark")))) {
					if (node.nodeType == Node.ELEMENT_NODE) {
						var tag = node.nodeName.toLowerCase();
						if (tag == 'a') {
							searchInLink(node);
						}
						else if (tag != 'style' && tag != 'script' ) // special cases, don't touch CDATA elements
							searchInElement(node);
					} else if (node.nodeType == Node.TEXT_NODE) {
						searchInText(node);
					}
				}
			}
		});
	}

	//Base64 encoded images to prevent dealing with browser specific resource loading
	var imageBookmark = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wwWEgEDAI72/QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAQElEQVQ4y2P8//8/AyWACZ+k3EbG/3IbGf+TbQDFLhghBjD+//+fgVBI4wKP/P8zUscF+NIBzKbRaKR1OqAEAACkzxevPUlGPgAAAABJRU5ErkJggg==';
	var imageUnbookmark = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wwWEgIEtccwnQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAASklEQVQ4y2P8//8/AyWACZ+k3EZGBrmNjOQbQLELhoYBjPhi4R0PD5wt9OULbgPe8fCQFZdCX74w0tYLsDTwyP//aDoYsIREDAAAcYEarSA1+NcAAAAASUVORK5CYII=';
	var imageLoading = 'data:image/gif;base64,R0lGODlhEAAQAMIAAP+EAP+ICP+ICf////+EAP+EAP+EAP+EACH/C05FVFNDQVBFMi4wAwEAAAAh/hFDcmVhdGVkIHdpdGggR0lNUAAh+QQFCgAEACwAAAAAEAAQAAADIji63L7hPQCkqg5bRrf/kKeJQASOYNN91HpN7QII6IXWaQIAIfkEBQoABwAsAQACAA0ADQAAAyQ4urwHoC3wYJTK0sN7h154YM1IOifziCEgXA34MJsXQ+kQSAkAIfkEBQoABwAsAQABAA0ADQAAAyR4CtMNQC0RF4yuWnicf9UnNsroBZBJMuogvXDXeunMftgXHwkAIfkEBQoABwAsAgABAA0ADQAAAyI4urHuIL5DK4BUvgEEqCD4beRBTqcThtEqRtcJf/HWpk4CADs=';

	var idRegex = /(?:[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}|[0-9a-f]{4} [0-9a-f]{4} [0-9a-f]{4} [0-9a-f]{4})/gi;
	var linkRegex = /[Hh][Tt][Tt][Pp][Ss]?:\/\/[Ss][Uu][Pp][Ee][Rr][Mm][Aa][Rr][Ii][Oo][Mm][Aa][Kk][Ee][Rr][Bb][Oo][Oo][Kk][Mm][Aa][Rr][Kk].[Nn][Ii][Nn][Tt][Ee][Nn][Dd][Oo].[Nn][Ee][Tt]\/courses\/([0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4})/g; //Sorry ._. Only way to make regex partually case insensitive

	var courseInfoCache = [];
	var courseInfoCallbacks = [];
	var bookmarkButtonDict = [];

	searchInElement(document.body);

	var observer = new MutationObserver(onDOMChange);
	var config = {childList: true, subtree: true};
	observer.observe(document.body, config);

})();
<|endoftext|>


<commit_before>/**
 * Corus JS Client
 * Copyright (c) 2016 Corus.io <juancarlos.vinas@corus.io>
 * MIT Licensed
 */

/*
 * Requires
 */

var request = require('request');
var WebSocket = require('ws');
var events = require('events');
var assign = require('object-assign');
var querystring = require('querystring');
var util = require('util');

/**
 * HttpError
 */

var HttpError = function(statusCode, message){

    Error.call(this);

    this.message = message || 'Unknown error';

    this.statusCode = statusCode || 500;

};

/**
 * Private Methods
 */

var requestInvokeFn = function(options){

    return function(bodyOrFilter, callback){

        if(!callback){

            callback = bodyOrFilter;
            bodyOrFilter = null;

        }

        if(!callback){

            callback = function(){};

        }

        if(bodyOrFilter) {

            if (options.method === 'POST' || options.method === 'PUT') {

                options = assign({}, options, {json: true, body: bodyOrFilter});

            } else {

                var filter = assign({}, bodyOrFilter);

                if(filter.where){

                    filter.where = JSON.stringify(filter.where);

                }

                if(filter.aggregation){

                    filter.aggregation = JSON.stringify(filter.aggregation);

                }

                if(filter.groupBy){

                    filter.groupBy = JSON.stringify(filter.groupBy);

                }

                options.url += '?' + querystring.stringify(filter);

            }

        }

        request(options, function(err, response, body){

            if(err){

                callback(err);

            } else if(response.statusCode < 200 || response.statusCode >= 400) {

                callback(new HttpError(response.statusCode, body));

            } else {

                var jsonBody = body;

                try { jsonBody = JSON.parse(body); } catch(e){}

                callback(null, jsonBody);

            }

            //Limpiamos el callback para evitar leaks de memoria

            callback = null;
            return;

        });

    };

};

var checkCredentials = function(headers){

    if(!headers.key){

        throw new Error('Invalid credentials');

    }

};

var pushInvokeFn = function(options, filter){

    return function(notification, callback){

        options = assign({}, options, {method: 'POST', json: true, body: notification});

        if(filter.where){

            filter.where = JSON.stringify(filter.where);

        }

        options.url += '?' + querystring.stringify(filter);

        request(options, function(err, response, body){

            if(err){

                callback(err);

            } else if(response.statusCode < 200 || response.statusCode >= 400) {

                callback(new HttpError(response.statusCode, body));

            } else {

                var jsonBody = body;

                try { jsonBody = JSON.parse(body); } catch(e){}

                callback(null, jsonBody);

            }

            //Limpiamos el callback para evitar leaks de memoria

            callback = null;
            return;

        });

    }

};

/**
 * Constructor
 *
 * @param options
 * Must contain a "host" property (ex: http://mycorp.corus.io)
 * Can contain a "key" property. If not supplied, then we must invoke "login" before any other method.
 */

var Corus = function(options){

    /**
     * Private Properties
     */

    var baseUrl = 'http://';
    var baseChannelsUrl = 'http://';
    var baseWSUrl = 'ws://';

    var baseHeaders = {};

    /**
     * Check Options
     */

    if(!options){

        throw new Error('options json is required');

    }

    if(!options.host){

        throw new Error('options must contain a "host" property');

    } else {

        baseUrl += options.host + '/api/v1';
        baseChannelsUrl += options.host;
        baseWSUrl += options.host;

    }

    if(options.key){

        baseHeaders.key = options.key;

    }

    if(options.lang){

        baseHeaders.lang = options.lang;

        if(options.fillWithDefaultLang){

            baseHeaders['fill-with-default-lang'] = 'true';

        }

    }

    if(options.avoidTrigger){

        // avoid-trigger: To avoid infinete recursive loops when invoking collection's data methods.

        baseHeaders['avoid-trigger'] = 'true';

    }

    /**
     * Login
     */

    this.login = function(emailAndPassword, callback){

        var url = baseUrl + '/me';

        var options = {

            url: url,
            headers: {
                authorization: 'Basic ' + new Buffer(emailAndPassword.email + ":" + emailAndPassword.password).toString('base64')
            }

        };

        requestInvokeFn(options)(function(err, user){

            if(!callback){

                callback = function(){};

            }

            if(err){

                return callback(err);

            } else {

                if(!baseHeaders.key) {

                    baseHeaders.key = user.key;

                }

                return callback(null, user);

            }

        });

    };

    /**
     * Users
     */

    this.users = function(user){

        //Check if we have a valid "key"
        checkCredentials(baseHeaders);

        var url = baseUrl + '/users';
        var headers = assign({}, baseHeaders);

        if(!user){

            return {

                //POST ==> /users

                post: requestInvokeFn({method: 'POST', url: url, headers: headers}),

                //GET ==> /users

                get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

            }

        } else {

            url += '/' + user;

            return {

                //GET ==> /apps/:user

                get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                //PUT ==> /apps/:user

                put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                //DELETE ==> /apps/:user

                delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers}),

                /**
                 * Apps
                 */

                apps: function(app){

                    url += '/apps';

                    if(!app){

                        throw new Error('Invalid app');

                    } else {

                        url += '/' + app;

                        return {

                            //GET ==> /users/:users/apps/:app

                            get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                            //PUT ==> /users/:users/apps/:app

                            put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                            //DELETE ==> /users/:users/apps/:app

                            delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers})
                        }

                    }

                }

            }

        }

    };

    /**
     * Apps
     */

    this.apps = function(app){

        //Check if we have a valid "key"
        checkCredentials(baseHeaders);

        var url = baseUrl + '/apps';
        var headers = assign({}, baseHeaders);

        if(!app){

            return {

                //POST ==> /apps

                post: requestInvokeFn({method: 'POST', url: url, headers: headers}),

                //GET ==> /apps

                get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

            }

        } else {

            url += '/' + app;

            return {

                //GET ==> /apps/:app

                get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                //PUT ==> /apps/:app

                put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                //DELETE ==> /apps/:app

                delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers}),

                /**
                 * Collections
                 */

                collections: function(collection){

                    url += '/collections';

                    if(!collection){

                        return {

                            //POST ==> /apps/:app/collections

                            post: requestInvokeFn({method: 'POST', url: url, headers: headers}),

                            //GET ==> /apps/:app/collections

                            get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                        }

                    } else {

                        url += '/' + collection;

                        return {

                            //GET ==> /apps/:app/collections/:collection

                            get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                            //PUT ==> /apps/:app/collections/:collection

                            put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                            //DELETE ==> /apps/:app/collections/:collection

                            delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers}),

                            /**
                             * Data
                             */

                            data: function(id){

                                url += '/data';

                                if(!id){

                                    return {

                                        //POST ==> /apps/:app/collections/:collection/data

                                        post: requestInvokeFn({method: 'POST', url: url, headers: headers}),

                                        //GET ==> /apps/:app/collections/:collection/data

                                        get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                                    }

                                } else {

                                    url += '/' + id;

                                    return {

                                        //GET ==> /apps/:app/collections/:collection/data/:id

                                        get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                                        //PUT ==> /apps/:app/collections/:collection/data/:id

                                        put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                                        //DELETE ==> /apps/:app/collections/:collection/data/:id

                                        delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers}),

                                    }

                                }

                            }

                        }

                    }

                },

                /**
                 * Users
                 */

                users: function(email){

                    var pushUrl = url + '/push';
                    url += '/users';

                    return {

                        //GET ==> /apps/:app/users

                        get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                        push: pushInvokeFn({url: pushUrl, headers: headers}, {where: {user: email}})

                    }

                },

                /**
                 * Installations
                 */

                installations: function(device){

                    url += '/installations';

                    if(!id){

                        return {

                            //POST ==> /apps/:app/installations

                            post: requestInvokeFn({method: 'POST', url: url, headers: headers})

                        }

                    } else {

                        url += '/' + device;

                        return {

                            //GET ==> /apps/:app/installations/:device

                            get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                            //DELETE ==> /apps/:app/installations/:device

                            delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers})

                        }

                    }

                }

            }

        }

    };

    /**
     * Channels
     */

    this.channels = function(app){

        //Check if we have a valid "key"
        checkCredentials(baseHeaders);

        var wsUrl = baseWSUrl + '/channels';
        var url = baseChannelsUrl + '/channels';
        var headers = assign({}, baseHeaders);

        if(!app) {

            throw new Error('Invalid app');

        } else if(!headers.key){

            throw new Error('Invalid user key');

        } else {

            url += '/' + app;
            wsUrl += '/' + app + '?key=' + headers.key;

            return {

                connect: function(){

                    var ws = new WebSocket(wsUrl);
                    var eventEmitter = new events.EventEmitter();

                    /**
                     * WS Events
                     */

                    ws.on('open', function(){

                        setTimeout(function(){

                            eventEmitter.emit('connected');

                        }, 500);

                    });

                    ws.on('error', function(err){

                        eventEmitter.emit('error', new Error('WS Error: ' + err.toString()));

                    });

                    ws.on('message', function(data){

                        var json  = null;

                        try { json = JSON.parse(data) } catch(e) { };

                        if(!json){

                            eventEmitter.emit('error', new Error('Invalid message'));

                        } else if(json.err){

                            eventEmitter.emit('error', json.err);

                        } else {

                            eventEmitter.emit('message', json.message);

                        }

                    });

                    /**
                     * Send Method
                     */

                    eventEmitter.send = function(to, data){

                        if(!to){

                            throw new Error('"to" parameter is required')

                        }

                        if(!data){

                            throw new Error('"data" parameter is required')

                        }

                        var jsonStr = JSON.stringify({

                            to: to,
                            data: data

                        });

                        ws.send(jsonStr);

                    };

                    return eventEmitter;

                },

                post: requestInvokeFn({method: 'POST', url: url, headers: headers})

            }

        }

    };

};

/**
 * Export
 */

module.exports = Corus;

<commit_msg>no message<commit_after>/**
 * Corus JS Client
 * Copyright (c) 2016 Corus.io <juancarlos.vinas@corus.io>
 * MIT Licensed
 */

/*
 * Requires
 */

var request = require('request');
var WebSocket = require('ws');
var events = require('events');
var assign = require('object-assign');
var querystring = require('querystring');
var util = require('util');

/**
 * HttpError
 */

var HttpError = function(statusCode, message){

    Error.call(this);

    this.message = message || 'Unknown error';

    this.statusCode = statusCode || 500;

};

/**
 * Private Methods
 */

var requestInvokeFn = function(options){

    return function(bodyOrFilter, callback){

        if(!callback){

            callback = bodyOrFilter;
            bodyOrFilter = null;

        }

        if(!callback){

            callback = function(){};

        }

        if(bodyOrFilter) {

            if (options.method === 'POST' || options.method === 'PUT') {

                options = assign({}, options, {json: true, body: bodyOrFilter});

            } else {

                var filter = assign({}, bodyOrFilter);

                if(filter.where){

                    filter.where = JSON.stringify(filter.where);

                }

                if(filter.aggregation){

                    filter.aggregation = JSON.stringify(filter.aggregation);

                }

                if(filter.groupBy){

                    filter.groupBy = JSON.stringify(filter.groupBy);

                }

                options.url += '?' + querystring.stringify(filter);

            }

        }

        request(options, function(err, response, body){

            if(err){

                callback(err);

            } else if(response.statusCode < 200 || response.statusCode >= 400) {

                callback(new HttpError(response.statusCode, body));

            } else {

                var jsonBody = body;

                try { jsonBody = JSON.parse(body); } catch(e){}

                callback(null, jsonBody);

            }

            //Limpiamos el callback para evitar leaks de memoria

            callback = null;
            return;

        });

    };

};

var checkCredentials = function(headers){

    if(!headers.key){

        throw new Error('Invalid credentials');

    }

};

var pushInvokeFn = function(options, filter){

    return function(notification, callback){

        options = assign({}, options, {method: 'POST', json: true, body: notification});

        if(filter.where){

            filter.where = JSON.stringify(filter.where);

        }

        options.url += '?' + querystring.stringify(filter);

        request(options, function(err, response, body){

            if(err){

                callback(err);

            } else if(response.statusCode < 200 || response.statusCode >= 400) {

                callback(new HttpError(response.statusCode, body));

            } else {

                var jsonBody = body;

                try { jsonBody = JSON.parse(body); } catch(e){}

                callback(null, jsonBody);

            }

            //Limpiamos el callback para evitar leaks de memoria

            callback = null;
            return;

        });

    }

};

/**
 * Constructor
 *
 * @param options
 * Must contain a "host" property (ex: http://mycorp.corus.io)
 * Can contain a "key" property. If not supplied, then we must invoke "login" before any other method.
 */

var Corus = function(options){

    /**
     * Private Properties
     */

    var baseUrl = 'http://';
    var baseChannelsUrl = 'http://';
    var baseWSUrl = 'ws://';

    var baseHeaders = {};

    /**
     * Check Options
     */

    if(!options){

        throw new Error('options json is required');

    }

    if(!options.host){

        throw new Error('options must contain a "host" property');

    } else {

        baseUrl += options.host + '/api/v1';
        baseChannelsUrl += options.host;
        baseWSUrl += options.host;

    }

    if(options.key){

        baseHeaders.key = options.key;

    }

    if(options.lang){

        baseHeaders.lang = options.lang;

        if(options.fillWithDefaultLang){

            baseHeaders['fill-with-default-lang'] = 'true';

        }

    }

    if(options.avoidTrigger){

        // avoid-trigger: To avoid infinete recursive loops when invoking collection's data methods.

        baseHeaders['avoid-trigger'] = 'true';

    }

    /**
     * Login
     */

    this.login = function(emailAndPassword, callback){

        var url = baseUrl + '/me';

        var options = {

            url: url,
            headers: {
                authorization: 'Basic ' + new Buffer(emailAndPassword.email + ":" + emailAndPassword.password).toString('base64')
            }

        };

        requestInvokeFn(options)(function(err, user){

            if(!callback){

                callback = function(){};

            }

            if(err){

                return callback(err);

            } else {

                if(!baseHeaders.key) {

                    baseHeaders.key = user.key;

                }

                return callback(null, user);

            }

        });

    };

    /**
     * Users
     */

    this.users = function(user){

        //Check if we have a valid "key"
        checkCredentials(baseHeaders);

        var url = baseUrl + '/users';
        var headers = assign({}, baseHeaders);

        if(!user){

            return {

                //POST ==> /users

                post: requestInvokeFn({method: 'POST', url: url, headers: headers}),

                //GET ==> /users

                get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

            }

        } else {

            url += '/' + user;

            return {

                //GET ==> /apps/:user

                get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                //PUT ==> /apps/:user

                put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                //DELETE ==> /apps/:user

                delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers}),

                /**
                 * Apps
                 */

                apps: function(app){

                    url += '/apps';

                    if(!app){

                        throw new Error('Invalid app');

                    } else {

                        url += '/' + app;

                        return {

                            //GET ==> /users/:users/apps/:app

                            get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                            //PUT ==> /users/:users/apps/:app

                            put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                            //DELETE ==> /users/:users/apps/:app

                            delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers})
                        }

                    }

                }

            }

        }

    };

    /**
     * Apps
     */

    this.apps = function(app){

        //Check if we have a valid "key"
        checkCredentials(baseHeaders);

        var url = baseUrl + '/apps';
        var headers = assign({}, baseHeaders);

        if(!app){

            return {

                //POST ==> /apps

                post: requestInvokeFn({method: 'POST', url: url, headers: headers}),

                //GET ==> /apps

                get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

            }

        } else {

            url += '/' + app;

            return {

                //GET ==> /apps/:app

                get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                //PUT ==> /apps/:app

                put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                //DELETE ==> /apps/:app

                delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers}),

                /**
                 * Collections
                 */

                collections: function(collection){

                    url += '/collections';

                    if(!collection){

                        return {

                            //POST ==> /apps/:app/collections

                            post: requestInvokeFn({method: 'POST', url: url, headers: headers}),

                            //GET ==> /apps/:app/collections

                            get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                        }

                    } else {

                        url += '/' + collection;

                        return {

                            //GET ==> /apps/:app/collections/:collection

                            get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                            //PUT ==> /apps/:app/collections/:collection

                            put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                            //DELETE ==> /apps/:app/collections/:collection

                            delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers}),

                            /**
                             * Data
                             */

                            data: function(id){

                                url += '/data';

                                if(!id){

                                    return {

                                        //POST ==> /apps/:app/collections/:collection/data

                                        post: requestInvokeFn({method: 'POST', url: url, headers: headers}),

                                        //GET ==> /apps/:app/collections/:collection/data

                                        get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                                    }

                                } else {

                                    url += '/' + id;

                                    return {

                                        //GET ==> /apps/:app/collections/:collection/data/:id

                                        get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                                        //PUT ==> /apps/:app/collections/:collection/data/:id

                                        put: requestInvokeFn({method: 'PUT', url: url, headers: headers}),

                                        //DELETE ==> /apps/:app/collections/:collection/data/:id

                                        delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers}),

                                    }

                                }

                            }

                        }

                    }

                },

                /**
                 * Users
                 */

                users: function(email){

                    var pushUrl = url + '/push';
                    url += '/users';

                    return {

                        //GET ==> /apps/:app/users

                        get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                        //POST ==> /apps/:app/push

                        push: pushInvokeFn({url: pushUrl, headers: headers}, {where: {createdBy: email}})

                    }

                },

                /**
                 * Installations
                 */

                installations: function(device){

                    var pushUrl = url + '/push';
                    url += '/installations';

                    if(!device){

                        return {

                            //POST ==> /apps/:app/installations

                            post: requestInvokeFn({method: 'POST', url: url, headers: headers}),

                            //POST ==> /apps/:app/push

                            push: pushInvokeFn({url: pushUrl, headers: headers}, {where: {device: device}})

                        }

                    } else {

                        url += '/' + device;

                        return {

                            //GET ==> /apps/:app/installations/:device

                            get: requestInvokeFn({method: 'GET', url: url, headers: headers}),

                            //DELETE ==> /apps/:app/installations/:device

                            delete: requestInvokeFn({method: 'DELETE', url: url, headers: headers}),

                            //POST ==> /apps/:app/push

                            push: function(notification, where, callback){

                                pushInvokeFn({url: pushUrl, headers: headers}, {where: where})(notification, callback);

                            }

                        }

                    }

                }

            }

        }

    };

    /**
     * Channels
     */

    this.channels = function(app){

        //Check if we have a valid "key"
        checkCredentials(baseHeaders);

        var wsUrl = baseWSUrl + '/channels';
        var url = baseChannelsUrl + '/channels';
        var headers = assign({}, baseHeaders);

        if(!app) {

            throw new Error('Invalid app');

        } else if(!headers.key){

            throw new Error('Invalid user key');

        } else {

            url += '/' + app;
            wsUrl += '/' + app + '?key=' + headers.key;

            return {

                connect: function(){

                    var ws = new WebSocket(wsUrl);
                    var eventEmitter = new events.EventEmitter();

                    /**
                     * WS Events
                     */

                    ws.on('open', function(){

                        setTimeout(function(){

                            eventEmitter.emit('connected');

                        }, 500);

                    });

                    ws.on('error', function(err){

                        eventEmitter.emit('error', new Error('WS Error: ' + err.toString()));

                    });

                    ws.on('message', function(data){

                        var json  = null;

                        try { json = JSON.parse(data) } catch(e) { };

                        if(!json){

                            eventEmitter.emit('error', new Error('Invalid message'));

                        } else if(json.err){

                            eventEmitter.emit('error', json.err);

                        } else {

                            eventEmitter.emit('message', json.message);

                        }

                    });

                    /**
                     * Send Method
                     */

                    eventEmitter.send = function(to, data){

                        if(!to){

                            throw new Error('"to" parameter is required')

                        }

                        if(!data){

                            throw new Error('"data" parameter is required')

                        }

                        var jsonStr = JSON.stringify({

                            to: to,
                            data: data

                        });

                        ws.send(jsonStr);

                    };

                    return eventEmitter;

                },

                post: requestInvokeFn({method: 'POST', url: url, headers: headers})

            }

        }

    };

};

/**
 * Export
 */

module.exports = Corus;

<|endoftext|>


<commit_before>// Auto-create new intput-groups
$(".js-lists").on("focus", ".disabled", function () {
	var disabledGroups = $(".input-group.disabled");
	var newInputGroupL = disabledGroups.clone()[0];
	var newInputGroupR = disabledGroups.clone()[1];
	disabledGroups.removeClass("disabled").addClass("js-create-on-keypress");
	$(".js-lists").one("keypress", ".js-create-on-keypress", function () {
		$(".js-create-on-keypress")
			.removeClass("js-create-on-keypress");
		$(".js-list-l > div:last-child")
			.after(newInputGroupL);
		$(".js-list-r > div:last-child")
			.after(newInputGroupR);
		recomputeTabIndices();
	});
});

$(".js-lists").on("blur", recountTotals);
$(".js-lists").on("keyup", recountTotals);
$(".js-lists").on("keyup", "input", function (event) {
	var upArrow = 38;
	var downArrow = 40;
	if (event.which == upArrow) {
		$(event.target).parent().prev().child("input").focus();
	} else if (event.which == downArrow) {
		$(event.target).parent().next().child("input").focus();
	}
});

// Count the non-blank inputs in each column
function recountTotals () {
	$(".js-listcount").each(function (index, element) {
		var count = 0;
		$("input", $(".js-list").get(index)).each(function (i, el) {
			if (el.value.trim()) {
				++count;
			}
		})
		$(element).text(count);
	})
}
<commit_msg>Arrows!<commit_after>// Auto-create new intput-groups
$(".js-lists").on("focus", ".disabled", function () {
	var disabledGroups = $(".input-group.disabled");
	var newInputGroupL = disabledGroups.clone()[0];
	var newInputGroupR = disabledGroups.clone()[1];
	disabledGroups.removeClass("disabled").addClass("js-create-on-keypress");
	$(".js-lists").one("keypress", ".js-create-on-keypress", function () {
		$(".js-create-on-keypress")
			.removeClass("js-create-on-keypress");
		$(".js-list-l > div:last-child")
			.after(newInputGroupL);
		$(".js-list-r > div:last-child")
			.after(newInputGroupR);
		recomputeTabIndices();
	});
});

$(".js-lists").on("blur", recountTotals);
$(".js-lists").on("keyup", recountTotals);
$(".js-lists").on("keyup", "input", function (event) {
	var upArrow = 38;
	var downArrow = 40;
	var parent = $(event.target).parent();
	if (event.which == upArrow && parent.prev().length) {
		parent.prev().children("input").focus();
	} else if (event.which == downArrow && parent.next().length) {
		parent.next().children("input").focus();
	}
});

// Count the non-blank inputs in each column
function recountTotals () {
	$(".js-listcount").each(function (index, element) {
		var count = 0;
		$("input", $(".js-list").get(index)).each(function (i, el) {
			if (el.value.trim()) {
				++count;
			}
		})
		$(element).text(count);
	})
}
<|endoftext|>


<commit_before>'use strict';

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var path = require('path');
var fs = require('fs');
var os = require('os');
var spawn = require('win-spawn');
var _ = require('lodash');
var async = require('async');
var opn = require('opn');
var Gaze = require('gaze').Gaze;
var request = require('request');
var mkdirp = require('mkdirp');
var chalk = require('chalk');

var verifyEnv = require('./utils/verify-env');
var pkg = require('../package.json');

var cwd;
var basename;
var jsonPath;
var server;
var io;
var gaze;
var commandQueue;

var apiUrl = 'https://bower-component-list.herokuapp.com/';
var tmpDir = path.join(os.tmpdir(), pkg.name);
var apiCacheFile = path.join(tmpDir, 'bower-component-list.json');
var settingsFile = path.join(tmpDir, 'settings.json');
var defaults = {
  path: null,
  port: 3010,
  cache: 86400,   // 86400s = 24hours
  open: true,
  silent: false
};

// Constructor
var BowerBrowser = function (options) {
  var self = this;
  var warnings = verifyEnv();

  EventEmitter.call(this);

  this.options = _.merge({}, defaults, options);
  this.running = false;

  if (warnings) {
    warnings.forEach(function (warning) {
      self.print(warning);
    });
    process.exit(1);
  }

  mkdirp.sync(tmpDir);

  if (this.options.cache === 0 || !this.hasApiCache()) {
    this.fetch(function () {
      self.start();
    });
  }
  else {
    // Start server after the constructor
    setTimeout(function () {
      self.start();
    }, 0);
  }
};

// Inherit EventEmitter
util.inherits(BowerBrowser, EventEmitter);

// Start server
BowerBrowser.prototype.start = function () {
  var self = this;
  var app = require('connect')();
  var serveStatic = require('serve-static');

  // Target project
  cwd = this.options.path ? path.resolve(this.options.path) : process.cwd();
  basename = path.basename(cwd);
  jsonPath = path.join(cwd, 'bower.json');

  // Start HTTP server
  server = require('http').Server(app);
  io = require('socket.io')(server);
  server.listen(this.options.port, 'localhost');
  app.use(serveStatic(path.join(__dirname, 'public')));
  app.use('/api', serveStatic(tmpDir));

  // Queue for sequential command execution
  commandQueue = async.queue(function (data, callback) {
    self.execute(data.command, data.id, callback);
  }, 1);
  commandQueue.drain = function () {
    io.sockets.emit('done');
  };

  // Handle WebSocket
  io.on('connection', function (socket) {
    self.sendBowerData(socket);
    socket.on('execute', function (data) {
      self.register(data.command, data.id);
    });
    socket.on('load', function () {
      self.sendBowerData(socket);
    });
    socket.on('settings', function (data) {
      self.saveSettings(data);
    });
  });

  this.running = true;
  this.print('Started bower-browser on ' + chalk.magenta('http://localhost:' + this.options.port) + '\n');
  this.emit('start');
  if (this.options.open) {
    this.open();
  }
};

// Fetch package list from the Bower registry
BowerBrowser.prototype.fetch = function (callback) {
  var self = this;
  var i = 0;
  var processMessage = 'Fetching package list';
  var timer;

  if (!this.options.silent) {
    timer = setInterval(function () {
      process.stdout.clearLine();
      process.stdout.cursorTo(0);
      i = (i + 1) % 4;
      var dots = new Array(i + 1).join('.');
      process.stdout.write(processMessage + dots);
    }, 500);
  }

  request({
    url: apiUrl,
    gzip: true
  }, function (error) {
    if (!self.options.silent) {
      clearInterval(timer);
      process.stdout.clearLine();
      process.stdout.cursorTo(0);
      process.stdout.write(processMessage + '... ');
    }
    if (error) {
      self.print(chalk.red('Error!') + '\nCouldn\'t fetch package list from the Bower registry.\nPlease try again later.\n');
      return;
    }
    self.print(chalk.green('Complete!') + '\n');
    if (typeof callback === 'function') {
      callback();
    }
  })
  .pipe(fs.createWriteStream(apiCacheFile));
};

// API cache is effective or not
BowerBrowser.prototype.hasApiCache = function () {
  var cached;
  var stat;
  try {
    stat = fs.statSync(apiCacheFile);
    cached = new Date() - stat.mtime < this.options.cache * 1000;
  }
  catch (e) {
    cached = false;
  }
  return cached;
};

// Open application in browser
BowerBrowser.prototype.open = function () {
  opn('http://localhost:' + this.options.port);
};

// Send bower data to client(s)
BowerBrowser.prototype.sendBowerData = function (socket) {
  this.readBowerJson(function (error, json) {
    var sender = socket || io.sockets;
    var data = {
      name: basename,
      path: cwd
    };
    if (error) {
      data.message = error.toString();
    }
    else {
      data.json = json;
    }
    sender.emit('bower', data);
  });
};

// Read and watch bower.json
BowerBrowser.prototype.readBowerJson = function (callback) {
  var self = this;
  var json = null;
  var error = null;
  try {
    var buffer = fs.readFileSync(jsonPath);
    if (!gaze) {
      gaze = new Gaze('bower.json', {cwd: cwd});
      gaze.on('all', function () {
        self.sendBowerData();
      });
    }
    json = JSON.parse(buffer);
  }
  catch (e) {
    error = e;
  }
  if (typeof callback === 'function') {
    callback(error, json);
  }
};

// Register command to queue
BowerBrowser.prototype.register = function (command, id) {
  id = id || '';
  commandQueue.push({
    command: command,
    id: id
  });
  io.sockets.emit('added', id);
};

// Execute command
BowerBrowser.prototype.execute = function (input, id, callback) {
  var self = this;
  var inputArray = input.trim().split(/\s+/);
  var command = inputArray.shift();
  var args = inputArray;
  var options = {
    cwd: cwd,
    env: process.env
  };
  var child = spawn(command, args, options);

  io.sockets.emit('start', id);
  this.log('\n> ' + input + '\n');

  child.stdout.on('data', function (data) {
    self.log(data.toString());
  });
  child.stderr.on('data', function (data) {
    self.log(data.toString());
  });
  child.on('exit', function () {
    io.sockets.emit('end', id);
    if (callback) {
      callback();
    }
  });
};

// Save client settings
BowerBrowser.prototype.saveSettings = function (data) {
  try {
    fs.writeFileSync(settingsFile, JSON.stringify(data, null, 2));
  }
  catch (e) {
    this.print(chalk.red('Failed to save settings!') + '\n' + e.message + '\n');
  }
};

// Log to stdout and socket
BowerBrowser.prototype.log = function (message) {
  this.print(message);
  io.sockets.emit('log', message);
  this.emit('log', message);
};

// Print to stdout unless `--silent`
BowerBrowser.prototype.print = function (data) {
  if (!this.options.silent) {
    process.stdout.write(data);
  }
};

// Stop server and wathcers
BowerBrowser.prototype.close = function () {
  if (this.running) {
    if (gaze) {
      gaze.close();
    }
    if (io) {
      io.close();
    }
    this.running = false;
  }
  this.print('\nClosed bower-browser\n');
  this.emit('close');
};

// Module entry point
module.exports = function (options) {
  return new BowerBrowser(options);
};
<commit_msg>Prevent 404 error of settings.json<commit_after>'use strict';

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var path = require('path');
var fs = require('fs');
var os = require('os');
var spawn = require('win-spawn');
var _ = require('lodash');
var async = require('async');
var opn = require('opn');
var Gaze = require('gaze').Gaze;
var request = require('request');
var mkdirp = require('mkdirp');
var chalk = require('chalk');

var verifyEnv = require('./utils/verify-env');
var pkg = require('../package.json');

var cwd;
var basename;
var jsonPath;
var server;
var io;
var gaze;
var commandQueue;

var apiUrl = 'https://bower-component-list.herokuapp.com/';
var tmpDir = path.join(os.tmpdir(), pkg.name);
var apiCacheFile = path.join(tmpDir, 'bower-component-list.json');
var settingsFile = path.join(tmpDir, 'settings.json');
var defaults = {
  path: null,
  port: 3010,
  cache: 86400,   // 86400s = 24hours
  open: true,
  silent: false
};

// Constructor
var BowerBrowser = function (options) {
  var self = this;
  var warnings = verifyEnv();

  EventEmitter.call(this);

  this.options = _.merge({}, defaults, options);
  this.running = false;

  if (warnings) {
    warnings.forEach(function (warning) {
      self.print(warning);
    });
    process.exit(1);
  }

  this.setup();

  if (this.options.cache === 0 || !this.hasApiCache()) {
    this.fetch(function () {
      self.start();
    });
  }
  else {
    // Start server after the constructor
    setTimeout(function () {
      self.start();
    }, 0);
  }
};

// Inherit EventEmitter
util.inherits(BowerBrowser, EventEmitter);

// Setup local files
BowerBrowser.prototype.setup = function () {
  var hasSettings;
  mkdirp.sync(tmpDir);
  try {
    hasSettings = fs.statSync(settingsFile).isFile();
  }
  catch (e) {
    hasSettings = false;
  }
  if (!hasSettings) {
    this.saveSettings({});
  }
};

// Start server
BowerBrowser.prototype.start = function () {
  var self = this;
  var app = require('connect')();
  var serveStatic = require('serve-static');

  // Target project
  cwd = this.options.path ? path.resolve(this.options.path) : process.cwd();
  basename = path.basename(cwd);
  jsonPath = path.join(cwd, 'bower.json');

  // Start HTTP server
  server = require('http').Server(app);
  io = require('socket.io')(server);
  server.listen(this.options.port, 'localhost');
  app.use(serveStatic(path.join(__dirname, 'public')));
  app.use('/api', serveStatic(tmpDir));

  // Queue for sequential command execution
  commandQueue = async.queue(function (data, callback) {
    self.execute(data.command, data.id, callback);
  }, 1);
  commandQueue.drain = function () {
    io.sockets.emit('done');
  };

  // Handle WebSocket
  io.on('connection', function (socket) {
    self.sendBowerData(socket);
    socket.on('execute', function (data) {
      self.register(data.command, data.id);
    });
    socket.on('load', function () {
      self.sendBowerData(socket);
    });
    socket.on('settings', function (data) {
      self.saveSettings(data);
    });
  });

  this.running = true;
  this.print('Started bower-browser on ' + chalk.magenta('http://localhost:' + this.options.port) + '\n');
  this.emit('start');
  if (this.options.open) {
    this.open();
  }
};

// Fetch package list from the Bower registry
BowerBrowser.prototype.fetch = function (callback) {
  var self = this;
  var i = 0;
  var processMessage = 'Fetching package list';
  var timer;

  if (!this.options.silent) {
    timer = setInterval(function () {
      process.stdout.clearLine();
      process.stdout.cursorTo(0);
      i = (i + 1) % 4;
      var dots = new Array(i + 1).join('.');
      process.stdout.write(processMessage + dots);
    }, 500);
  }

  request({
    url: apiUrl,
    gzip: true
  }, function (error) {
    if (!self.options.silent) {
      clearInterval(timer);
      process.stdout.clearLine();
      process.stdout.cursorTo(0);
      process.stdout.write(processMessage + '... ');
    }
    if (error) {
      self.print(chalk.red('Error!') + '\nCouldn\'t fetch package list from the Bower registry.\nPlease try again later.\n');
      return;
    }
    self.print(chalk.green('Complete!') + '\n');
    if (typeof callback === 'function') {
      callback();
    }
  })
  .pipe(fs.createWriteStream(apiCacheFile));
};

// API cache is effective or not
BowerBrowser.prototype.hasApiCache = function () {
  var cached;
  var stat;
  try {
    stat = fs.statSync(apiCacheFile);
    cached = new Date() - stat.mtime < this.options.cache * 1000;
  }
  catch (e) {
    cached = false;
  }
  return cached;
};

// Open application in browser
BowerBrowser.prototype.open = function () {
  opn('http://localhost:' + this.options.port);
};

// Send bower data to client(s)
BowerBrowser.prototype.sendBowerData = function (socket) {
  this.readBowerJson(function (error, json) {
    var sender = socket || io.sockets;
    var data = {
      name: basename,
      path: cwd
    };
    if (error) {
      data.message = error.toString();
    }
    else {
      data.json = json;
    }
    sender.emit('bower', data);
  });
};

// Read and watch bower.json
BowerBrowser.prototype.readBowerJson = function (callback) {
  var self = this;
  var json = null;
  var error = null;
  try {
    var buffer = fs.readFileSync(jsonPath);
    if (!gaze) {
      gaze = new Gaze('bower.json', {cwd: cwd});
      gaze.on('all', function () {
        self.sendBowerData();
      });
    }
    json = JSON.parse(buffer);
  }
  catch (e) {
    error = e;
  }
  if (typeof callback === 'function') {
    callback(error, json);
  }
};

// Register command to queue
BowerBrowser.prototype.register = function (command, id) {
  id = id || '';
  commandQueue.push({
    command: command,
    id: id
  });
  io.sockets.emit('added', id);
};

// Execute command
BowerBrowser.prototype.execute = function (input, id, callback) {
  var self = this;
  var inputArray = input.trim().split(/\s+/);
  var command = inputArray.shift();
  var args = inputArray;
  var options = {
    cwd: cwd,
    env: process.env
  };
  var child = spawn(command, args, options);

  io.sockets.emit('start', id);
  this.log('\n> ' + input + '\n');

  child.stdout.on('data', function (data) {
    self.log(data.toString());
  });
  child.stderr.on('data', function (data) {
    self.log(data.toString());
  });
  child.on('exit', function () {
    io.sockets.emit('end', id);
    if (callback) {
      callback();
    }
  });
};

// Save client settings
BowerBrowser.prototype.saveSettings = function (data) {
  try {
    fs.writeFileSync(settingsFile, JSON.stringify(data, null, 2));
  }
  catch (e) {
    this.print(chalk.red('Failed to save settings!') + '\n' + e.message + '\n');
  }
};

// Log to stdout and socket
BowerBrowser.prototype.log = function (message) {
  this.print(message);
  io.sockets.emit('log', message);
  this.emit('log', message);
};

// Print to stdout unless `--silent`
BowerBrowser.prototype.print = function (data) {
  if (!this.options.silent) {
    process.stdout.write(data);
  }
};

// Stop server and wathcers
BowerBrowser.prototype.close = function () {
  if (this.running) {
    if (gaze) {
      gaze.close();
    }
    if (io) {
      io.close();
    }
    this.running = false;
  }
  this.print('\nClosed bower-browser\n');
  this.emit('close');
};

// Module entry point
module.exports = function (options) {
  return new BowerBrowser(options);
};
<|endoftext|>


<commit_before>window.MoveToList = function() {
};

/**
 * 駅geojsonファイルを読み込み、moveToList配列に格納する
 * @return {[type]} [description]
 */
MoveToList.prototype.loadStationJson = function()
{
    var d = new $.Deferred();
    // 駅位置JSONデータ読み込み〜セレクトボックス追加
    $.getJSON(
        "data/station.geojson",
        function(data){
            moveToList.push( {name: "公共交通機関施設", header:true} );
            var lineName = "";
            for(var i=0; i<data.features.length; i++) {
                //_s = data.features[i].properties["shubetsu"] + " (" + data.features[i].properties["line"] + ")";
                _s = data.features[i].properties["line"];
                if(lineName !== _s) {
                    moveToList.push({name: _s, header: true});
                    lineName = _s;
                }
                _name = data.features[i].properties.station_name;
                _lat  = data.features[i].properties.lat;
                _lon  = data.features[i].properties.lon;
                _lat = parseFloat(_lat);
                _lon = parseFloat(_lon);
                moveToList.push(
                    {name: _name, lat: _lat, lon: _lon, header:false}
                    );
            }
            d.resolve();
        }).fail(function(){
            console.log('station data load failed.');
            d.reject('load error.');
        });
    return d.promise();
};

/**
 * 最寄駅セレクトボックスに要素を追加する
 * @param  array moveToList [description]
 * @return {[type]}            [description]
 */
MoveToList.prototype.appendToMoveToListBox = function(moveToList)
{
    nesting = "";
    for(i=0; i < moveToList.length; i++) {
        if(moveToList[i].header) {
            if(nesting !== "") {
                $('#moveTo').append(nesting);
            }
            nesting = $('<optgroup>').attr('label', moveToList[i].name);
        } else {
            nesting.append($('<option>').html(moveToList[i].name).val(i));
        }
    }
    if(nesting !== "") {
        $('#moveTo').append(nesting);
    }
};
<commit_msg>fix a name of select box lists<commit_after>window.MoveToList = function() {
};

/**
 * 駅geojsonファイルを読み込み、moveToList配列に格納する
 * @return {[type]} [description]
 */
MoveToList.prototype.loadStationJson = function()
{
    var d = new $.Deferred();
    // 駅位置JSONデータ読み込み〜セレクトボックス追加
    $.getJSON(
        "data/station.geojson",
        function(data){
            moveToList.push( {name: "市町村役場", header:true} );
            var lineName = "";
            for(var i=0; i<data.features.length; i++) {
                //_s = data.features[i].properties["shubetsu"] + " (" + data.features[i].properties["line"] + ")";
                _s = data.features[i].properties["line"];
                if(lineName !== _s) {
                    moveToList.push({name: _s, header: true});
                    lineName = _s;
                }
                _name = data.features[i].properties.station_name;
                _lat  = data.features[i].properties.lat;
                _lon  = data.features[i].properties.lon;
                _lat = parseFloat(_lat);
                _lon = parseFloat(_lon);
                moveToList.push(
                    {name: _name, lat: _lat, lon: _lon, header:false}
                    );
            }
            d.resolve();
        }).fail(function(){
            console.log('station data load failed.');
            d.reject('load error.');
        });
    return d.promise();
};

/**
 * 最寄駅セレクトボックスに要素を追加する
 * @param  array moveToList [description]
 * @return {[type]}            [description]
 */
MoveToList.prototype.appendToMoveToListBox = function(moveToList)
{
    nesting = "";
    for(i=0; i < moveToList.length; i++) {
        if(moveToList[i].header) {
            if(nesting !== "") {
                $('#moveTo').append(nesting);
            }
            nesting = $('<optgroup>').attr('label', moveToList[i].name);
        } else {
            nesting.append($('<option>').html(moveToList[i].name).val(i));
        }
    }
    if(nesting !== "") {
        $('#moveTo').append(nesting);
    }
};
<|endoftext|>


<commit_before>//Importing request
const request = require('request');

//Cache for requests
const cache = {};

const defaultOptions = {
 preferOnline: false
}

/*
    Entry point of application.
    Returned with:
        const api = require('apidown')
        const testApi = api('www.sample-api.com')
*/
function ApiOptions(url) {
    this.url = url;

    //TODO: Add configuration methods

    //Returns new ApiObject
    return new Api(url);
}

/*
    The functions in Api get called with:
        testApi.fetch('/users', cb);
*/
function Api(url) {

    //Calls the endpoint and returns the body. If available, return cache first.
    //The callback is called with vars "error" and "result".
    function fetch(endpoint, cb, options) {
        options = Object.assign({}, defaultOptions, options);
        //Avoid wrong input.
        url = url.startsWith('http') ? url : 'http://' + url;
        url = url.endsWith('/') ? url : (url += '/');
        endpoint = endpoint.startsWith('/') ? endpoint.replace('/', '') : endpoint;
        const finUrl = url + endpoint;

        //If data in cache, callback with no error and data in cache
        if (cache[finUrl] && !options.preferOnline) {
            cb(undefined, { data: cache[finUrl], cache: true });
        }
        //Else, fetch data from URL.
        else {
            request(finUrl, function (error, response, body) {
                if (error) {
                    if (options.preferOnline && cache[finUrl]) {
                        cb(undefined, { data: cache[finUrl], cache: true });
                    }
                    else {
                        cb(error);
                    }
                } else if (response && response.statusCode >= 400) {
                    if (options.preferOnline && cache[finUrl]) {
                        cb(undefined, { data: cache[finUrl], cache: true });
                    }
                    else {
                        cb(new Error(response.statusCode + ' returned.'));
                    }
                    //TODO: Add more error cases (no body, error statusCodes, ...)
                } else {
                    //Save data to cache
                    cache[finUrl] = body;

                    //Call callback
                    cb(undefined, { data: body, cache: false });
                }
            })
        }
    }

    //Set object methods with this
    this.fetch = fetch;

    //a method to help with testing.
    this.__addToCache = (endpoint, obj) => {
         url = url.startsWith('http') ? url : 'http://' + url;
        url = url.endsWith('/') ? url : (url += '/');
        endpoint = endpoint.startsWith('/') ? endpoint.replace('/', '') : endpoint;
        const finUrl = url + endpoint;
        cache[finUrl] = obj;
    }
}

//Export ApiOptions function
module.exports = ApiOptions;
<commit_msg>Add comments<commit_after>//Importing request
const request = require('request');

//Cache for requests
const cache = {};

//Default options, which will be overridden with parameters
const defaultOptions = {
 preferOnline: false
}

/*
    Entry point of application.
    Returned with:
        const api = require('apidown')
        const testApi = api('www.sample-api.com')
*/
function ApiOptions(url) {
    this.url = url;

    //TODO: Add configuration methods

    //Returns new ApiObject
    return new Api(url);
}

/*
    The functions in Api get called with:
        testApi.fetch('/users', cb);
*/
function Api(url) {

    //Calls the endpoint and returns the body. If available, return cache first.
    //The callback is called with vars "error" and "result".
    function fetch(endpoint, cb, options) {
        //Assign default options.
        options = Object.assign({}, defaultOptions, options);
        //Avoid wrong input.
        url = url.startsWith('http') ? url : 'http://' + url;
        url = url.endsWith('/') ? url : (url += '/');
        endpoint = endpoint.startsWith('/') ? endpoint.replace('/', '') : endpoint;
        const finUrl = url + endpoint;

        //If data in cache, callback with no error and data in cache
        if (cache[finUrl] && !options.preferOnline) {
            cb(undefined, { data: cache[finUrl], cache: true });
        }
        //Else, fetch data from URL.
        else {
            request(finUrl, function (error, response, body) {
                if (error) {
                    if (options.preferOnline && cache[finUrl]) {
                        cb(undefined, { data: cache[finUrl], cache: true });
                    }
                    else {
                        cb(error);
                    }
                } else if (response && response.statusCode >= 400) {
                    if (options.preferOnline && cache[finUrl]) {
                        cb(undefined, { data: cache[finUrl], cache: true });
                    }
                    else {
                        cb(new Error(response.statusCode + ' returned.'));
                    }
                    //TODO: Add more error cases (no body, error statusCodes, ...)
                } else {
                    //Save data to cache
                    cache[finUrl] = body;

                    //Call callback
                    cb(undefined, { data: body, cache: false });
                }
            })
        }
    }

    //Set object methods with this
    this.fetch = fetch;

    //TEST_METHOD: Add data to cache
    this.__addToCache = (endpoint, obj) => {
        url = url.startsWith('http') ? url : 'http://' + url;
        url = url.endsWith('/') ? url : (url += '/');
        endpoint = endpoint.startsWith('/') ? endpoint.replace('/', '') : endpoint;
        const finUrl = url + endpoint;
        cache[finUrl] = obj;
    }
}

//Export ApiOptions function
module.exports = ApiOptions;
<|endoftext|>


<commit_before>
// ----------------------------------------------------------------
// Card Class

class CardModel extends SwitchModel {
  constructor({
    name,
    lsKeyView,
    triggerSelector,
    switchSelector,
    hash = null
  } = {}) {
    super({
      name: name,
      lsKeyView: lsKeyView,
      triggerSelector: triggerSelector,
      switchSelector: switchSelector
    });

    this.NAME = name;
    this.HASH = hash;

    this.CARD_AREA_SELECTOR = '#card-area';
    this.$CARD_AREA_SELECTOR = $(this.CARD_AREA_SELECTOR);
    this.TEMPLATE_CARD_TABLE_SELECTOR = '#card-table-template';
    this.$TEMPLATE_CARD_TABLE_SELECTOR = $(this.TEMPLATE_CARD_TABLE_SELECTOR);

    this.CARD_TBODY = '#card-tbody';
    this.$CARD_TBODY = $(this.CARD_TBODY);
  }
}

class CardView extends SwitchView {
  constructor(_model = new CardModel()) {
    super(_model);
  }

  generateCardArea(_alertType = 'success', _message = null) {
    this.model.$CARD_AREA_SELECTOR.empty();
    this.view.generateAlert(this.model.$CARD_AREA_SELECTOR, _alertType, _message);

    let template = null;
    if (this.model.LOGIN) {
      // ログインしているとき
      template = this.model.$TEMPLATE_CARD_TABLE_SELECTOR.text();
      const compiled = _.template(template);
      const model = {};
      this.model.$CARD_AREA_SELECTOR.append(compiled(model));
      this.model.$CARD_TBODY = $(this.model.CARD_TBODY);
    }
  }
}

// ----------------------------------------------------------------
// Controller

class CardController extends CommonController {
  constructor(_obj) {
    super(_obj);

    this.model = new CardModel(_obj);
    this.view = new CardView(this.model);

    this.model.ID = null;
    this.model.HASH = null;
    this.model.LOGIN = null;
  }

  setUser(_id = null, _hash = null) {
    this.model.ID = _id;
    this.model.HASH = _hash;
    if (this.model.ID != null && this.model.HASH != null) {
      this.model.LOGIN = true;
    } else {
      this.model.LOGIN = false;
    }

    this.downloadCard(this.model.ID, this.model.HASH)
  }

  downloadCard(_id = null, _hash = null) {

    this.view.generateLoading(this.model.$CARD_AREA_SELECTOR, '通信中', `ユーザーID ${this.model.ID} の名刺データを取得中...`);
  }
}

// ----------------------------------------------------------------
// Event

class CardEvent extends CommonEvent {
  constructor({
    name = 'Card Event'
  } = {})
  {
    super({
      name: name
    });

    this.NAME = name;
    this.CONTROLLER = new CardController({
      name: 'Card Switch',
      lsKeyView: 'card',
      triggerSelector: '#action-card',
      switchSelector: '#card-area'
    });
  }
}
<commit_msg>Fix function generateCardArea in CardView<commit_after>
// ----------------------------------------------------------------
// Card Class

class CardModel extends SwitchModel {
  constructor({
    name,
    lsKeyView,
    triggerSelector,
    switchSelector,
    hash = null
  } = {}) {
    super({
      name: name,
      lsKeyView: lsKeyView,
      triggerSelector: triggerSelector,
      switchSelector: switchSelector
    });

    this.NAME = name;
    this.HASH = hash;

    this.CARD_AREA_SELECTOR = '#card-area';
    this.$CARD_AREA_SELECTOR = $(this.CARD_AREA_SELECTOR);
    this.TEMPLATE_CARD_TABLE_SELECTOR = '#card-table-template';
    this.$TEMPLATE_CARD_TABLE_SELECTOR = $(this.TEMPLATE_CARD_TABLE_SELECTOR);

    this.CARD_TBODY = '#card-tbody';
    this.$CARD_TBODY = $(this.CARD_TBODY);
  }
}

class CardView extends SwitchView {
  constructor(_model = new CardModel()) {
    super(_model);
  }

  generateCardArea(_alertType = 'success', _message = null) {
    this.model.$CARD_AREA_SELECTOR.empty();
    this.generateAlert(this.model.$CARD_AREA_SELECTOR, _alertType, _message);

    let template = null;
    if (this.model.LOGIN) {
      // ログインしているとき
      template = this.model.$TEMPLATE_CARD_TABLE_SELECTOR.text();
      const compiled = _.template(template);
      const model = {};
      this.model.$CARD_AREA_SELECTOR.append(compiled(model));
      this.model.$CARD_TBODY = $(this.model.CARD_TBODY);
    }
  }
}

// ----------------------------------------------------------------
// Controller

class CardController extends CommonController {
  constructor(_obj) {
    super(_obj);

    this.model = new CardModel(_obj);
    this.view = new CardView(this.model);

    this.model.ID = null;
    this.model.HASH = null;
    this.model.LOGIN = null;
  }

  setUser(_id = null, _hash = null) {
    this.model.ID = _id;
    this.model.HASH = _hash;
    if (this.model.ID != null && this.model.HASH != null) {
      this.model.LOGIN = true;
    } else {
      this.model.LOGIN = false;
    }

    this.downloadCard(this.model.ID, this.model.HASH)
  }

  downloadCard(_id = null, _hash = null) {

    this.view.generateLoading(this.model.$CARD_AREA_SELECTOR, '通信中', `ユーザーID ${this.model.ID} の名刺データを取得中...`);
  }
}

// ----------------------------------------------------------------
// Event

class CardEvent extends CommonEvent {
  constructor({
    name = 'Card Event'
  } = {})
  {
    super({
      name: name
    });

    this.NAME = name;
    this.CONTROLLER = new CardController({
      name: 'Card Switch',
      lsKeyView: 'card',
      triggerSelector: '#action-card',
      switchSelector: '#card-area'
    });
  }
}
<|endoftext|>