<commit_before>var bt       = require("bittorrent");
var benc     = require("benc");
var config   = require("config");
var fs       = require("fs");
var logger   = require("logger").get("core");
var rss      = require("rss");
var session  = bt.session;
var timer    = require("timer");

function saveTorrent(torrent) {
    var torrentsPath = getTorrentsPath();
    var file         = fs.combine(torrentsPath, torrent.infoHash + ".torrent");

    if(fs.fileExists(file)) {
        return;
    }

    var info = torrent.getTorrentInfo();

    if(!info) {
        return;
    }

    var creator = new bt.TorrentCreator(info);
    var entry   = creator.generate();
    var buffer  = benc.encode(entry);

    // Save the actual metadata
    fs.writeBuffer(file, buffer);
    logger.info("Saved torrent file for '" + info.name + "' to " + file);
}

session.on("torrent.removed", function(e) {
    var torrentsPath = getTorrentsPath();

    var torrentFile  = fs.combine(torrentsPath, e.infoHash + ".torrent");
    var resumeFile   = fs.combine(torrentsPath, e.infoHash + ".resume");
    var metadataFile = fs.combine(torrentsPath, e.infoHash + ".metadata");

    if(fs.fileExists(torrentFile)) {
        fs.deleteFile(torrentFile);
    }

    if(fs.fileExists(resumeFile)) {
        fs.deleteFile(resumeFile);
    }

    if(fs.fileExists(metadataFile)) {
        fs.deleteFile(metadataFile);
    }
});

session.on("torrent.metadataReceived", function(e) {
    if(!e.torrent.isValid) {
        logger.error("Received metadata for an invalid torrent.");
        return;
    }

    saveTorrent(e.torrent);
});

session.on("torrent.add", function(e) {
    if(e.error) {
        logger.error("Could not add torrent (code " + e.error.code + "): " +
                     e.error.message);
        return;
    }

    // Add any metadata to the torrent
    var metadata = e.params.metadata || {};

    if(!metadata["options"]) {
        // Add any default options to the metadata
        var defaultOptions = config.get("bittorrent.defaultOptions") || {};

        var options = {
            seedRatio: (defaultOptions["seedRatio"] || 2.0),
            seedTime: (defaultOptions["seedTime"] || 0),
            seedAction: (defaultOptions["seedAction"] || "pause")
        };

        metadata["options"] = options;

        logger.debug("Torrent '' assigned Seed Ratio: " + options.seedRatio + "%, Seed Time: " + options.seedTime + "minutes , Seed Action: " + options.seedAction);
    }

    var keys = Object.keys(metadata);

    for(var i = 0; i < keys.length; i++) {
        e.torrent.metadata(keys[i], metadata[keys[i]]);
    }
});

session.on("torrent.added", function(e) {
    if(!e.torrent.isValid) {
        logger.error("Invalid torrent added.");
        return;
    }

    saveTorrent(e.torrent);
});

session.on("torrent.checked", function(e) {
    var torrent = e.torrent;

    if(torrent.metadata("_pauseAfterRecheck")) {
        torrent.pause();
        torrent.metadata("_pauseAfterRecheck", false);
    }

    logger.debug("Torrent '" + e.torrent.infoHash + "' hash checked.");
});

session.on("torrent.hashUpdated", function(e) {
    /*
    On migrating metadata
    ---------------------
    When we add a torrent URL (with eg. tags), the info hash
    is not known at the time. We store each metadata object
    in a std::map keyed by the info hash.

    When libtorrent receives the torrent file and gets the correct
    info hash, it sends us this alert.

    We have a special key handler for "_migrate" which takes the
    old info hash as a value, and then moves the metadata from
    the old info hash to the new info hash.

    It is a bit of a hack, but works.
    */

    e.torrent.metadata("_migrate", e.oldInfoHash);
});

session.on("torrent.stateUpdate", function(e) {
    for(var i = 0; i < e.status.length; i++) {
        var status = e.status[i];

        // Only check our seed goal if the torrent is finished
        // and not paused.
        if(!status.isFinished || status.isPaused) {
            return;
        }

        var torrent = status.torrent;
        var options = torrent.metadata("options") || {};

        var goalRatio = options.seedRatio || 2.0;
        var goalTime  = options.goalTime || 0;

        if(status.ratio >= parseFloat(goalRatio)
            || (goalTime > 0 && status.seedingTime > goalTime)) {

            logger.info("Torrent '" + status.name + "' reached its seed goal of " + (status.ratio >= parseFloat(goalRatio) ? (goalRatio + "%") : (goalTime + "minutes")));

            var action = (options.seedAction || "pause");

            switch(action) {
                case "pause":
                    torrent.autoManaged = false;
                    torrent.pause();
                    break;

                case "remove":
                    session.removeTorrent(torrent);
                    break;
            }
        }
    }
});

function getTorrentsPath() {
    var statePath = config.getString("bittorrent.statePath") || "state";
    var torrentsPath = fs.combine(statePath, "torrents");

    if(!fs.directoryExists(torrentsPath)) {
        fs.createDirectories(torrentsPath);
    }

    return torrentsPath;
}

function loadState() {
    var statePath = config.getString("bittorrent.statePath") || "state";

    if(!fs.directoryExists(statePath)) {
        logger.info("Creating state path: " + statePath);
        fs.createDirectories(statePath);
    }

    var stateFile = fs.combine(statePath, ".session_state");

    if(!fs.fileExists(stateFile)) {
        logger.info("No previous session state to load.");
        return;
    }

    var data = fs.readBuffer(stateFile);
    var decoded = benc.decode(data);

    // load session state
    session.loadState(decoded);
    logger.info("Loaded session state from '" + stateFile + "'");
}

function loadTorrents() {
    var torrentsPath = getTorrentsPath();
    var files = fs.getFiles(torrentsPath);

    for(var i = 0; i < files.length; i++) {
        var file = files[i];
        if(!file.endsWith(".torrent")) { continue; }

        var params = bt.AddTorrentParams.getDefault();
        params.torrent = new bt.TorrentInfo(file);

        // Load metadata file
        var metadataFile = file.replace(/\.torrent/i, ".metadata");

        if(fs.fileExists(metadataFile)) {
            var data = fs.readText(metadataFile);
            params.metadata = JSON.parse(data);
        }

        // Load resume file
        var resumeFile = file.replace(/\.torrent$/i, ".resume");

        if(fs.fileExists(resumeFile)) {
            params.resumeData = fs.readBuffer(resumeFile);
        }

        session.addTorrent(params);
        logger.info("Torrent '" + params.torrent.name + "' added.");
    }
}

function load() {
    // Set up listen port for BitTorrent communication
    var listenPort = (config.get("bittorrent.listenPort") || 6881);
    if(isNaN(listenPort)) {
        logger.warn("Invalid listen port specified. Using default (6881).");
        listenPort = 6881;
    }

    session.listenOn([listenPort, listenPort]);

    // Load country database
    var geoIp = config.getString("bittorrent.geoIpFile");

    if(geoIp) {
        logger.info("Loading GeoIP database " + geoIp);
        session.loadCountryDb(geoIp);
    }

    // Add DHT routers.
    var routers = config.get("bittorrent.dht.routers") || [];
    for(var i = 0; i < routers.length; i++) {
        session.addDhtRouter(routers[i][0], routers[i][1]);
    }

    // Enable DHT
    if(config.get("bittorrent.dht.enabled")) {
        logger.info("Enabling DHT.");
        session.startDht();
    }

    // Enable LSD
    if(config.get("bittorrent.lsd.enabled")) {
        logger.info("Enabling LSD.");
        session.startLsd();
    }

    // Enable NatPmp
    if(config.get("bittorrent.natpmp.enabled")) {
        logger.info("Enabling NATPMP.");
        session.startNatPmp();
    }

    // Enable UPnP
    if(config.get("bittorrent.upnp.enabled")) {
        logger.info("Enabling UPnP.");
        session.startUpnp();
    }

    // Load session settings
    var settings = session.getSettings();
    settings.connectionsLimit = (config.get("bittorrent.connectionsLimit") || settings.connectionsLimit);
    settings.downloadRateLimit = (config.get("bittorrent.downloadRateLimit") || settings.downloadRateLimit);
    settings.mixedModeAlgorithm = (config.get("bittorrent.mixedModeAlgorithm") || settings.mixedModeAlgorithm);
    settings.rateLimitIpOverhead = (config.get("bittorrent.rateLimitIpOverhead") || settings.rateLimitIpOverhead);
    settings.rateLimitUtp = (config.get("bittorrent.rateLimitUtp") || settings.rateLimitUtp);
    settings.uploadRateLimit = (config.get("bittorrent.uploadRateLimit") || settings.uploadRateLimit);

    // Advanced settings
    var allowMultipleConnectionsPerIp = config.get("bittorrent.allowMultipleConnectionsPerIp");
    if(typeof allowMultipleConnectionsPerIp !== "undefined") { settings.allowMultipleConnectionsPerIp = allowMultipleConnectionsPerIp; }

    var anonymousMode = config.get("bittorrent.anonymousMode");
    if(typeof anonymousMode !== "undefined") { settings.anonymousMode = anonymousMode; }

    var enableUtp = config.get("bittorrent.utp.enabled");
    if(typeof enableUtp !== "undefined") { enableUtp = true; }
    settings.enableOutgoingUtp = enableUtp;
    settings.enableIncomingUtp = enableUtp;

    settings.halfOpenLimit = (config.get("bittorrent.halfOpenLimit") || settings.halfOpenLimit);

    session.setSettings(settings);

    logger.info("Loaded session settings.");

    loadState();
    loadTorrents();

    rss.load();
    require("plugins").load();

    // Post torrent updates each tick of the timer.
    timer.tick(function() { session.postTorrentUpdates(); });
}

function saveState() {
    var statePath = config.getString("bittorrent.statePath") || "state";
    var stateFile = fs.combine(statePath, ".session_state");

    var entry  = session.saveState();
    var buffer = benc.encode(entry);

    fs.writeBuffer(stateFile, buffer);
    logger.info("Saved session state to '" + stateFile + "'");
}

function saveTorrents() {
    session.pause();

    var torrentsPath = getTorrentsPath();
    var num          = 0;
    var torrents     = session.getTorrents();

    for(var i = 0; i < torrents.length; i++) {
        var t = torrents[i];
        if(!t.isValid) { continue; }

        var s = t.getStatus();

        if(!s.hasMetadata) { continue; }
        if(!s.needSaveResume) { continue; }

        num += 1;
        t.saveResumeData();
    }

    logger.info("Saving resume data for " + num + " torrent(s)");

    while(num > 0) {
        var a = session.waitForAlert(10 * 1000);
        if(!a) { continue; }

        var alerts = session.getAlerts();

        for(var i = 0; i < alerts.length; i++) {
            var alert = alerts[i];

            if(alert == null) {
                continue;
            }

            if(alert.name === "torrent_paused_alert") {
                continue;
            }

            if(alert.name === "save_resume_data_failed_alert") {
                num -= 1;
                continue;
            }

            if(alert.name !== "save_resume_data_alert") {
                continue;
            }

            num -= 1;

            if(!alert.resumeData) {
                logger.info("No resume data for torrent.");
                continue;
            }

            var buffer = benc.encode(alert.resumeData);
            var resumeFile = fs.combine(torrentsPath, alert.torrent.infoHash + ".resume");
            var data = alert.torrent.metadata();

            if(data) {
                var metadataFile = fs.combine(torrentsPath, alert.torrent.infoHash + ".metadata");
                fs.writeText(metadataFile, JSON.stringify(data));
            }

            fs.writeBuffer(resumeFile, buffer);
            logger.info("Saved resume data for '" + alert.torrent.getTorrentInfo().name + "'");
        }
    }
}

function saveConfig() {
    // This will move the hadouken.json file we loaded *this*
    // session to hadouken.json.old, and save our current config
    // as hadouken.json. Safeguarding against failures.

    if(fs.fileExists(__CONFIG__)) {
        var old = fs.combine(__CONFIG_PATH__, "hadouken.json.old");
        fs.rename(__CONFIG__, old);
        logger.info("Configuration backed up.");
    }

    var data = JSON.stringify(config.get(), null, "  ");
    fs.writeText(__CONFIG__, data);
}

function unload() {
    rss.unload();

    saveState();
    saveTorrents();
    saveConfig();
}

exports.load = load;
exports.unload = unload;
<commit_msg>Fix debug statements to show the correct units<commit_after>var bt       = require("bittorrent");
var benc     = require("benc");
var config   = require("config");
var fs       = require("fs");
var logger   = require("logger").get("core");
var rss      = require("rss");
var session  = bt.session;
var timer    = require("timer");

function saveTorrent(torrent) {
    var torrentsPath = getTorrentsPath();
    var file         = fs.combine(torrentsPath, torrent.infoHash + ".torrent");

    if(fs.fileExists(file)) {
        return;
    }

    var info = torrent.getTorrentInfo();

    if(!info) {
        return;
    }

    var creator = new bt.TorrentCreator(info);
    var entry   = creator.generate();
    var buffer  = benc.encode(entry);

    // Save the actual metadata
    fs.writeBuffer(file, buffer);
    logger.info("Saved torrent file for '" + info.name + "' to " + file);
}

session.on("torrent.removed", function(e) {
    var torrentsPath = getTorrentsPath();

    var torrentFile  = fs.combine(torrentsPath, e.infoHash + ".torrent");
    var resumeFile   = fs.combine(torrentsPath, e.infoHash + ".resume");
    var metadataFile = fs.combine(torrentsPath, e.infoHash + ".metadata");

    if(fs.fileExists(torrentFile)) {
        fs.deleteFile(torrentFile);
    }

    if(fs.fileExists(resumeFile)) {
        fs.deleteFile(resumeFile);
    }

    if(fs.fileExists(metadataFile)) {
        fs.deleteFile(metadataFile);
    }
});

session.on("torrent.metadataReceived", function(e) {
    if(!e.torrent.isValid) {
        logger.error("Received metadata for an invalid torrent.");
        return;
    }

    saveTorrent(e.torrent);
});

session.on("torrent.add", function(e) {
    if(e.error) {
        logger.error("Could not add torrent (code " + e.error.code + "): " +
                     e.error.message);
        return;
    }

    // Add any metadata to the torrent
    var metadata = e.params.metadata || {};

    if(!metadata["options"]) {
        // Add any default options to the metadata
        var defaultOptions = config.get("bittorrent.defaultOptions") || {};

        var options = {
            seedRatio: (defaultOptions["seedRatio"] || 2.0),
            seedTime: (defaultOptions["seedTime"] || 0),
            seedAction: (defaultOptions["seedAction"] || "pause")
        };

        metadata["options"] = options;

        logger.debug("Torrent '" + e.torrent.infoHash + "' assigned Seed Ratio: " + options.seedRatio + "%, Seed Time: " + options.seedTime + " seconds , Seed Action: " + options.seedAction);
    }

    var keys = Object.keys(metadata);

    for(var i = 0; i < keys.length; i++) {
        e.torrent.metadata(keys[i], metadata[keys[i]]);
    }
});

session.on("torrent.added", function(e) {
    if(!e.torrent.isValid) {
        logger.error("Invalid torrent added.");
        return;
    }

    saveTorrent(e.torrent);
});

session.on("torrent.checked", function(e) {
    var torrent = e.torrent;

    if(torrent.metadata("_pauseAfterRecheck")) {
        torrent.pause();
        torrent.metadata("_pauseAfterRecheck", false);
    }

    logger.debug("Torrent '" + e.torrent.infoHash + "' hash checked.");
});

session.on("torrent.hashUpdated", function(e) {
    /*
    On migrating metadata
    ---------------------
    When we add a torrent URL (with eg. tags), the info hash
    is not known at the time. We store each metadata object
    in a std::map keyed by the info hash.

    When libtorrent receives the torrent file and gets the correct
    info hash, it sends us this alert.

    We have a special key handler for "_migrate" which takes the
    old info hash as a value, and then moves the metadata from
    the old info hash to the new info hash.

    It is a bit of a hack, but works.
    */

    e.torrent.metadata("_migrate", e.oldInfoHash);
});

session.on("torrent.stateUpdate", function(e) {
    for(var i = 0; i < e.status.length; i++) {
        var status = e.status[i];

        // Only check our seed goal if the torrent is finished
        // and not paused.
        if(!status.isFinished || status.isPaused) {
            return;
        }

        var torrent = status.torrent;
        var options = torrent.metadata("options") || {};

        var goalRatio = options.seedRatio || 2.0;
        var goalTime  = options.goalTime || 0;

        if(status.ratio >= parseFloat(goalRatio)
            || (goalTime > 0 && status.seedingTime > goalTime)) {

            logger.info("Torrent '" + status.name + "' reached its seed goal of " + (status.ratio >= parseFloat(goalRatio) ? (goalRatio + "%") : (goalTime + " seconds")));

            var action = (options.seedAction || "pause");

            switch(action) {
                case "pause":
                    torrent.autoManaged = false;
                    torrent.pause();
                    break;

                case "remove":
                    session.removeTorrent(torrent);
                    break;
            }
        }
    }
});

function getTorrentsPath() {
    var statePath = config.getString("bittorrent.statePath") || "state";
    var torrentsPath = fs.combine(statePath, "torrents");

    if(!fs.directoryExists(torrentsPath)) {
        fs.createDirectories(torrentsPath);
    }

    return torrentsPath;
}

function loadState() {
    var statePath = config.getString("bittorrent.statePath") || "state";

    if(!fs.directoryExists(statePath)) {
        logger.info("Creating state path: " + statePath);
        fs.createDirectories(statePath);
    }

    var stateFile = fs.combine(statePath, ".session_state");

    if(!fs.fileExists(stateFile)) {
        logger.info("No previous session state to load.");
        return;
    }

    var data = fs.readBuffer(stateFile);
    var decoded = benc.decode(data);

    // load session state
    session.loadState(decoded);
    logger.info("Loaded session state from '" + stateFile + "'");
}

function loadTorrents() {
    var torrentsPath = getTorrentsPath();
    var files = fs.getFiles(torrentsPath);

    for(var i = 0; i < files.length; i++) {
        var file = files[i];
        if(!file.endsWith(".torrent")) { continue; }

        var params = bt.AddTorrentParams.getDefault();
        params.torrent = new bt.TorrentInfo(file);

        // Load metadata file
        var metadataFile = file.replace(/\.torrent/i, ".metadata");

        if(fs.fileExists(metadataFile)) {
            var data = fs.readText(metadataFile);
            params.metadata = JSON.parse(data);
        }

        // Load resume file
        var resumeFile = file.replace(/\.torrent$/i, ".resume");

        if(fs.fileExists(resumeFile)) {
            params.resumeData = fs.readBuffer(resumeFile);
        }

        session.addTorrent(params);
        logger.info("Torrent '" + params.torrent.name + "' added.");
    }
}

function load() {
    // Set up listen port for BitTorrent communication
    var listenPort = (config.get("bittorrent.listenPort") || 6881);
    if(isNaN(listenPort)) {
        logger.warn("Invalid listen port specified. Using default (6881).");
        listenPort = 6881;
    }

    session.listenOn([listenPort, listenPort]);

    // Load country database
    var geoIp = config.getString("bittorrent.geoIpFile");

    if(geoIp) {
        logger.info("Loading GeoIP database " + geoIp);
        session.loadCountryDb(geoIp);
    }

    // Add DHT routers.
    var routers = config.get("bittorrent.dht.routers") || [];
    for(var i = 0; i < routers.length; i++) {
        session.addDhtRouter(routers[i][0], routers[i][1]);
    }

    // Enable DHT
    if(config.get("bittorrent.dht.enabled")) {
        logger.info("Enabling DHT.");
        session.startDht();
    }

    // Enable LSD
    if(config.get("bittorrent.lsd.enabled")) {
        logger.info("Enabling LSD.");
        session.startLsd();
    }

    // Enable NatPmp
    if(config.get("bittorrent.natpmp.enabled")) {
        logger.info("Enabling NATPMP.");
        session.startNatPmp();
    }

    // Enable UPnP
    if(config.get("bittorrent.upnp.enabled")) {
        logger.info("Enabling UPnP.");
        session.startUpnp();
    }

    // Load session settings
    var settings = session.getSettings();
    settings.connectionsLimit = (config.get("bittorrent.connectionsLimit") || settings.connectionsLimit);
    settings.downloadRateLimit = (config.get("bittorrent.downloadRateLimit") || settings.downloadRateLimit);
    settings.mixedModeAlgorithm = (config.get("bittorrent.mixedModeAlgorithm") || settings.mixedModeAlgorithm);
    settings.rateLimitIpOverhead = (config.get("bittorrent.rateLimitIpOverhead") || settings.rateLimitIpOverhead);
    settings.rateLimitUtp = (config.get("bittorrent.rateLimitUtp") || settings.rateLimitUtp);
    settings.uploadRateLimit = (config.get("bittorrent.uploadRateLimit") || settings.uploadRateLimit);

    // Advanced settings
    var allowMultipleConnectionsPerIp = config.get("bittorrent.allowMultipleConnectionsPerIp");
    if(typeof allowMultipleConnectionsPerIp !== "undefined") { settings.allowMultipleConnectionsPerIp = allowMultipleConnectionsPerIp; }

    var anonymousMode = config.get("bittorrent.anonymousMode");
    if(typeof anonymousMode !== "undefined") { settings.anonymousMode = anonymousMode; }

    var enableUtp = config.get("bittorrent.utp.enabled");
    if(typeof enableUtp !== "undefined") { enableUtp = true; }
    settings.enableOutgoingUtp = enableUtp;
    settings.enableIncomingUtp = enableUtp;

    settings.halfOpenLimit = (config.get("bittorrent.halfOpenLimit") || settings.halfOpenLimit);

    session.setSettings(settings);

    logger.info("Loaded session settings.");

    loadState();
    loadTorrents();

    rss.load();
    require("plugins").load();

    // Post torrent updates each tick of the timer.
    timer.tick(function() { session.postTorrentUpdates(); });
}

function saveState() {
    var statePath = config.getString("bittorrent.statePath") || "state";
    var stateFile = fs.combine(statePath, ".session_state");

    var entry  = session.saveState();
    var buffer = benc.encode(entry);

    fs.writeBuffer(stateFile, buffer);
    logger.info("Saved session state to '" + stateFile + "'");
}

function saveTorrents() {
    session.pause();

    var torrentsPath = getTorrentsPath();
    var num          = 0;
    var torrents     = session.getTorrents();

    for(var i = 0; i < torrents.length; i++) {
        var t = torrents[i];
        if(!t.isValid) { continue; }

        var s = t.getStatus();

        if(!s.hasMetadata) { continue; }
        if(!s.needSaveResume) { continue; }

        num += 1;
        t.saveResumeData();
    }

    logger.info("Saving resume data for " + num + " torrent(s)");

    while(num > 0) {
        var a = session.waitForAlert(10 * 1000);
        if(!a) { continue; }

        var alerts = session.getAlerts();

        for(var i = 0; i < alerts.length; i++) {
            var alert = alerts[i];

            if(alert == null) {
                continue;
            }

            if(alert.name === "torrent_paused_alert") {
                continue;
            }

            if(alert.name === "save_resume_data_failed_alert") {
                num -= 1;
                continue;
            }

            if(alert.name !== "save_resume_data_alert") {
                continue;
            }

            num -= 1;

            if(!alert.resumeData) {
                logger.info("No resume data for torrent.");
                continue;
            }

            var buffer = benc.encode(alert.resumeData);
            var resumeFile = fs.combine(torrentsPath, alert.torrent.infoHash + ".resume");
            var data = alert.torrent.metadata();

            if(data) {
                var metadataFile = fs.combine(torrentsPath, alert.torrent.infoHash + ".metadata");
                fs.writeText(metadataFile, JSON.stringify(data));
            }

            fs.writeBuffer(resumeFile, buffer);
            logger.info("Saved resume data for '" + alert.torrent.getTorrentInfo().name + "'");
        }
    }
}

function saveConfig() {
    // This will move the hadouken.json file we loaded *this*
    // session to hadouken.json.old, and save our current config
    // as hadouken.json. Safeguarding against failures.

    if(fs.fileExists(__CONFIG__)) {
        var old = fs.combine(__CONFIG_PATH__, "hadouken.json.old");
        fs.rename(__CONFIG__, old);
        logger.info("Configuration backed up.");
    }

    var data = JSON.stringify(config.get(), null, "  ");
    fs.writeText(__CONFIG__, data);
}

function unload() {
    rss.unload();

    saveState();
    saveTorrents();
    saveConfig();
}

exports.load = load;
exports.unload = unload;
<|endoftext|>


<commit_before>var validateInstance = require('./instance');

//Returns true if the params array contains the coorect parameters for the method
//(schema is the schema for the methods parameters)
//Params should either contain the parameters in order or it should contain one object describing the parameters by name
function validateParams(schema, resolveReferences, params) {
	var names = {};

	//Loop through all schema properties and list them by name
	for(x in schema) {
		if(schema[x].name) {
			names[schema[x].name] = schema[x];
		}
	}

	//Do we have to try validating by order?
	if(Object.prototype.toString.call(params) === '[object Array]') {
		return validateByOrder(params)
	} else {
		return validateByname(params);
	}

	//Validate our parameters assuming they are supplied in the correct order
	function validateByOrder(params) {
		var x;

		//Loop through all properties in schema and check if required ones exist
		for(x = 0; x < schema.length; x++) {
			if(schema[x].required) {
				if(!params.hasOwnProperty(x)) {
					return false
				}
			}
		}

		//Loop through all existing params and check if they are valid
		for(x = 0; x < params.length; x++) {
			if(schema.length >= x) {
				if(!validateInstance(schema[x], resolveReferences, params[x])) {
					return false;
				}
			} else {
				if(names.hasOwnProperty('*')) {
					if(!validateInstance(names['*'], resolveReferences, params[x])) {
						return false;
					}
				}
			}
		}

		return true;
	}

	//Validate our parameters assuming they are supplied as an object by name
	function validateByName(params) {
		var x;

		//Loop through all named params and see if they exist if required
		for(x in names) {
			if(names[x].required) {
				if(!params.hasOwnProperty(x)) {
					return false;
				}
			}
		}

		//Loop through all params to see if they are valid
		for(x in params) {
			if(names.hasOwnProperty(x)) {
				if(!validateInstance(names[x], resolveReferences, params[x])) {
					return false;
				}
			} else {
				if(names.hasOwnProperty('*')) {
					if(!validateInstance(names['*'], resolveReferences, params[x])) {
						return false;
					}
				}
			}
		}

		return true;
	}
}

module.exports = validateParams;<commit_msg>Fix Typo<commit_after>var validateInstance = require('./instance');

//Returns true if the params array contains the coorect parameters for the method
//(schema is the schema for the methods parameters)
//Params should either contain the parameters in order or it should contain one object describing the parameters by name
function validateParams(schema, resolveReferences, params) {
	var names = {};

	//Loop through all schema properties and list them by name
	for(x in schema) {
		if(schema[x].name) {
			names[schema[x].name] = schema[x];
		}
	}

	//Do we have to try validating by order?
	if(Object.prototype.toString.call(params) === '[object Array]') {
		return validateByOrder(params)
	} else {
		return validateByName(params);
	}

	//Validate our parameters assuming they are supplied in the correct order
	function validateByOrder(params) {
		var x;

		//Loop through all properties in schema and check if required ones exist
		for(x = 0; x < schema.length; x++) {
			if(schema[x].required) {
				if(!params.hasOwnProperty(x)) {
					return false
				}
			}
		}

		//Loop through all existing params and check if they are valid
		for(x = 0; x < params.length; x++) {
			if(schema.length >= x) {
				if(!validateInstance(schema[x], resolveReferences, params[x])) {
					return false;
				}
			} else {
				if(names.hasOwnProperty('*')) {
					if(!validateInstance(names['*'], resolveReferences, params[x])) {
						return false;
					}
				}
			}
		}

		return true;
	}

	//Validate our parameters assuming they are supplied as an object by name
	function validateByName(params) {
		var x;

		//Loop through all named params and see if they exist if required
		for(x in names) {
			if(names[x].required) {
				if(!params.hasOwnProperty(x)) {
					return false;
				}
			}
		}

		//Loop through all params to see if they are valid
		for(x in params) {
			if(names.hasOwnProperty(x)) {
				if(!validateInstance(names[x], resolveReferences, params[x])) {
					return false;
				}
			} else {
				if(names.hasOwnProperty('*')) {
					if(!validateInstance(names['*'], resolveReferences, params[x])) {
						return false;
					}
				}
			}
		}

		return true;
	}
}

module.exports = validateParams;<|endoftext|>


<commit_before>import { configure } from '@kadira/storybook';

function loadStories() {
  require('../stories');
}

configure(loadStories, module);

const injectTapEventPlugin = require("react-tap-event-plugin");
injectTapEventPlugin();
<commit_msg>Change loading stories order.<commit_after>import { configure } from '@kadira/storybook'

function loadStories() {
  require('../stories')
}

const injectTapEventPlugin = require("react-tap-event-plugin")
injectTapEventPlugin()

configure(loadStories, module);
<|endoftext|>


<commit_before>var bookmarks_page = 0;
var bookmarks_loading = false;

$(document).ready(function() {
	$('.bookmarks_addBtn').click(function(event){
		$('.bookmarks_add').slideToggle();
	});

	$('#bookmark_add_submit').click(addBookmark);
	$(window).scroll(updateOnBottom);

	$('.bookmarks_list').empty();
	getBookmarks();
});

function getBookmarks() {
	if(bookmarks_loading) {
		//have patience :)
		return;
	}
	$.ajax({
		url: 'ajax/updateList.php',
		data: 'tag=' + encodeURI($('#bookmarkFilterTag').val()) + '&page=' + bookmarks_page,
		success: function(bookmarks){
			bookmarks_page += 1;
			$('.bookmark_link').unbind('click', recordClick);
			$('.bookmark_delete').unbind('click', delBookmark);

			for(var i in bookmarks.data) {
				updateBookmarksList(bookmarks.data[i]);
			}
			$('.bookmark_link').click(recordClick);
			$('.bookmark_delete').click(delBookmark);
			bookmarks_loading = false;
		}
	});
}

function addBookmark(event) {
	var url = encodeEntities($('#bookmark_add_url').val())
	var title = encodeEntities($('#bookmark_add_title').val())
	var description = encodeEntities($('#bookmark_add_description').val())
	var tags = encodeEntities($('#bookmark_add_tags').val())
	var taglist = tags.split(' ')
	var tagshtml = '';
	for ( var i=0, len=taglist.length; i<len; ++i ){
		tagshtml += '' + taglist[i] + ' ';
	}
	$.ajax({
		url: 'ajax/addBookmark.php',
		data: 'url=' + encodeURI(url) + '&title=' + encodeURI(title) + '&description=' + encodeURI(description) + '&tags=' + encodeURI(tags),
		success: function(data){
			$('.bookmarks_add').slideToggle();
			$('.bookmarks_add').children('p').children('.bookmarks_input').val('');
			$('.bookmarks_list').prepend(
			'
' +
				'
' + title + '
' +
				'
' + url + '
' +
				'
' + description + '
' +
				'
' + tagshtml + '
' +
				'
Delete
' +
			'
'
			);
		}
	});
}

function delBookmark(event) {
	var record = $(this).parent().parent()
	$.ajax({
		url: 'ajax/delBookmark.php',
		data: 'url=' + encodeURI($(this).parent().parent().children('.bookmark_url:first').text()),
		success: function(data){ record.animate({ opacity: 'hide' }, 'fast'); }
	});
}

function updateBookmarksList(bookmark) {
	var tags = encodeEntities(bookmark.tags).split(' ');
	var taglist = '';
	for ( var i=0, len=tags.length; i<len; ++i ){
		taglist = taglist + '' + tags[i] + ' ';
	}
	$('.bookmarks_list').append(
		'
' +
			'
' + encodeEntities(bookmark.title) + '
' +
			'
' + encodeEntities(bookmark.url) + '
' +
			'
' + encodeEntities(bookmark.description) + '
' +
			'
' + taglist + '
' +
			'
Delete
' +
		'
'
	);
}

function updateOnBottom() {
	//check wether user is on bottom of the page
	if ($('body').height() <= ($(window).height() + $(window).scrollTop())) {
		getBookmarks();
	}
}

function recordClick(event) {
	$.ajax({
		url: 'ajax/recordClick.php',
		data: 'url=' + encodeURI($(this).attr('href')),
	});
}

function encodeEntities(s){
	try {
		return $('
').text(s).html();

	} catch (ex) {
		return "";
	}
}
<commit_msg>autoretrieve title and description after entering new url<commit_after>var bookmarks_page = 0;
var bookmarks_loading = false;

$(document).ready(function() {
	$('.bookmarks_addBtn').click(function(event){
		$('.bookmarks_add').slideToggle();
	});

	$('#bookmark_add_submit').click(addBookmark);
	$(window).scroll(updateOnBottom);

	$('#bookmark_add_url').focusout(getMetadata);

	$('.bookmarks_list').empty();
	getBookmarks();
});

function getBookmarks() {
	if(bookmarks_loading) {
		//have patience :)
		return;
	}
	$.ajax({
		url: 'ajax/updateList.php',
		data: 'tag=' + encodeURI($('#bookmarkFilterTag').val()) + '&page=' + bookmarks_page,
		success: function(bookmarks){
			bookmarks_page += 1;
			$('.bookmark_link').unbind('click', recordClick);
			$('.bookmark_delete').unbind('click', delBookmark);

			for(var i in bookmarks.data) {
				updateBookmarksList(bookmarks.data[i]);
			}
			$('.bookmark_link').click(recordClick);
			$('.bookmark_delete').click(delBookmark);
			bookmarks_loading = false;
		}
	});
}

function getMetadata() {
	var url = encodeEntities($('#bookmark_add_url').val())
	$.ajax({
		url: 'ajax/getMeta.php',
		data: 'url=' + encodeURI(url),
		success: function(pageinfo){
			$('#bookmark_add_description').val(pageinfo.data.description);
			$('#bookmark_add_title').val(pageinfo.data.title);
		}
	});
}

function addBookmark(event) {
	var url = encodeEntities($('#bookmark_add_url').val())
	var title = encodeEntities($('#bookmark_add_title').val())
	var description = encodeEntities($('#bookmark_add_description').val())
	var tags = encodeEntities($('#bookmark_add_tags').val())
	var taglist = tags.split(' ')
	var tagshtml = '';
	for ( var i=0, len=taglist.length; i<len; ++i ){
		tagshtml += '' + taglist[i] + ' ';
	}
	$.ajax({
		url: 'ajax/addBookmark.php',
		data: 'url=' + encodeURI(url) + '&title=' + encodeURI(title) + '&description=' + encodeURI(description) + '&tags=' + encodeURI(tags),
		success: function(data){
			$('.bookmarks_add').slideToggle();
			$('.bookmarks_add').children('p').children('.bookmarks_input').val('');
			$('.bookmarks_list').prepend(
			'
' +
				'
' + title + '
' +
				'
' + url + '
' +
				'
' + description + '
' +
				'
' + tagshtml + '
' +
				'
Delete
' +
			'
'
			);
		}
	});
}

function delBookmark(event) {
	var record = $(this).parent().parent()
	$.ajax({
		url: 'ajax/delBookmark.php',
		data: 'url=' + encodeURI($(this).parent().parent().children('.bookmark_url:first').text()),
		success: function(data){ record.animate({ opacity: 'hide' }, 'fast'); }
	});
}

function updateBookmarksList(bookmark) {
	var tags = encodeEntities(bookmark.tags).split(' ');
	var taglist = '';
	for ( var i=0, len=tags.length; i<len; ++i ){
		taglist = taglist + '' + tags[i] + ' ';
	}
	$('.bookmarks_list').append(
		'
' +
			'
' + encodeEntities(bookmark.title) + '
' +
			'
' + encodeEntities(bookmark.url) + '
' +
			'
' + encodeEntities(bookmark.description) + '
' +
			'
' + taglist + '
' +
			'
Delete
' +
		'
'
	);
}

function updateOnBottom() {
	//check wether user is on bottom of the page
	if ($('body').height() <= ($(window).height() + $(window).scrollTop())) {
		getBookmarks();
	}
}

function recordClick(event) {
	$.ajax({
		url: 'ajax/recordClick.php',
		data: 'url=' + encodeURI($(this).attr('href')),
	});
}

function encodeEntities(s){
	try {
		return $('
').text(s).html();

	} catch (ex) {
		return "";
	}
}
<|endoftext|>


<commit_before>'use strict';

function percent(value, max){
    return core_round({
      'number': (value / max) * 100,
    }) + '%';
}

function play(selected){
    core_storage_save();

    // Check how many games player wants to play.
    if(core_storage_data['repeat'] < 1
      || Number.isNaN(core_storage_data['repeat'])){
        return;
    }

    // Keep track of results.
    let opponent_plays = [
      0,
      0,
      0,
    ];
    let results = [
      0,
      0,
      0,
    ];
    total += core_storage_data['repeat'];

    // Loop through the games.
    let loop_counter = core_storage_data['repeat'] - 1;
    do{
        // Result is a loss by default.
        let result = 0;

        // Generate a random number (0, 1, or 2).
        let opponent_choice_int = core_random_integer({
          'max': 3,
        });
        opponent_choice = [
          'rock',
          'paper',
          'scissors',
        ][opponent_choice_int];
        opponent_plays[opponent_choice_int] += 1;

        // Check for ties.
        if(selected === opponent_choice){
            result = 2;

        // Check for wins.
        }else if((selected === 'rock' && opponent_choice === 'scissors')
          || (selected === 'paper' && opponent_choice === 'rock')
          || (selected === 'scissors' && opponent_choice === 'paper')){
            result = 1;
        }

        // Update loss/tie/win values and store them in a temporary array.
        results[result] += 1;
    }while(loop_counter--);

    losses += results[0];
    ties += results[2];
    wins += results[1];

    // Create result strings.
    let paper = core_number_format({
      'number': opponent_plays[1],
    }) + ' papers (';
    let rock = core_number_format({
      'number': opponent_plays[0],
    }) + ' rocks (';
    let scissors = core_number_format({
      'number': opponent_plays[2],
    }) + ' scissors (';

    if(selected === 'rock'){
        paper += 'losses) ' + percent(results[0], core_storage_data['repeat']);
        rock += 'ties) ' + percent(results[2], core_storage_data['repeat']);
        scissors += 'wins) ' + percent(results[1], core_storage_data['repeat']);

    }else if(selected === 'paper'){
        paper += 'ties) ' + percent(results[2], core_storage_data['repeat']);
        rock += 'wins) ' + percent(results[1], core_storage_data['repeat']);
        scissors += 'losses) ' + percent(results[0], core_storage_data['repeat']);

    }else{
        paper += 'wins) ' + percent(results[1], core_storage_data['repeat']);
        rock += 'losses) ' + percent(results[0], core_storage_data['repeat']);
        scissors += 'ties) ' + percent(results[2], core_storage_data['repeat']);
    }

    // Display game information.
    document.getElementById('results').innerHTML = 'You played '
      + selected
      + ' ' + core_number_format({
        'number': core_storage_data['repeat'],
      }) + ' times.
'
      + 'Your opponent played:
'
      + rock + '
'
      + paper + '
'
      + scissors + '
'
      + core_number_format({
        'number': total,
      }) + ' total games played
'
      + core_number_format({
        'number': losses,
      }) + ' losses (' + percent(losses, total) + ')
'
      + core_number_format({
        'number': ties,
      }) + ' ties (' + percent(ties, total) + ')
'
      + core_number_format({
        'number': wins,
      }) + ' wins (' + percent(wins, total) + ')';
}
<commit_msg>added decimals-min arg usage<commit_after>'use strict';

function percent(value, max){
    return core_number_format({
      'number': core_round({
        'number': (value / max) * 100,
      }),
    }) + '%';
}

function play(selected){
    core_storage_save();

    // Check how many games player wants to play.
    if(core_storage_data['repeat'] < 1
      || Number.isNaN(core_storage_data['repeat'])){
        return;
    }

    // Keep track of results.
    let opponent_plays = [
      0,
      0,
      0,
    ];
    let results = [
      0,
      0,
      0,
    ];
    total += core_storage_data['repeat'];

    // Loop through the games.
    let loop_counter = core_storage_data['repeat'] - 1;
    do{
        // Result is a loss by default.
        let result = 0;

        // Generate a random number (0, 1, or 2).
        let opponent_choice_int = core_random_integer({
          'max': 3,
        });
        opponent_choice = [
          'rock',
          'paper',
          'scissors',
        ][opponent_choice_int];
        opponent_plays[opponent_choice_int] += 1;

        // Check for ties.
        if(selected === opponent_choice){
            result = 2;

        // Check for wins.
        }else if((selected === 'rock' && opponent_choice === 'scissors')
          || (selected === 'paper' && opponent_choice === 'rock')
          || (selected === 'scissors' && opponent_choice === 'paper')){
            result = 1;
        }

        // Update loss/tie/win values and store them in a temporary array.
        results[result] += 1;
    }while(loop_counter--);

    losses += results[0];
    ties += results[2];
    wins += results[1];

    // Create result strings.
    let paper = core_number_format({
      'decimals-min': 0,
      'number': opponent_plays[1],
    }) + ' papers (';
    let rock = core_number_format({
      'decimals-min': 0,
      'number': opponent_plays[0],
    }) + ' rocks (';
    let scissors = core_number_format({
      'decimals-min': 0,
      'number': opponent_plays[2],
    }) + ' scissors (';

    if(selected === 'rock'){
        paper += 'losses) ' + percent(results[0], core_storage_data['repeat']);
        rock += 'ties) ' + percent(results[2], core_storage_data['repeat']);
        scissors += 'wins) ' + percent(results[1], core_storage_data['repeat']);

    }else if(selected === 'paper'){
        paper += 'ties) ' + percent(results[2], core_storage_data['repeat']);
        rock += 'wins) ' + percent(results[1], core_storage_data['repeat']);
        scissors += 'losses) ' + percent(results[0], core_storage_data['repeat']);

    }else{
        paper += 'wins) ' + percent(results[1], core_storage_data['repeat']);
        rock += 'losses) ' + percent(results[0], core_storage_data['repeat']);
        scissors += 'ties) ' + percent(results[2], core_storage_data['repeat']);
    }

    // Display game information.
    document.getElementById('results').innerHTML = 'You played '
      + selected
      + ' ' + core_number_format({
        'decimals-min': 0,
        'number': core_storage_data['repeat'],
      }) + ' times.
'
      + 'Your opponent played:
'
      + rock + '
'
      + paper + '
'
      + scissors + '
'
      + core_number_format({
        'decimals-min': 0,
        'number': total,
      }) + ' total games played
'
      + core_number_format({
        'decimals-min': 0,
        'number': losses,
      }) + ' losses (' + percent(losses, total) + ')
'
      + core_number_format({
        'decimals-min': 0,
        'number': ties,
      }) + ' ties (' + percent(ties, total) + ')
'
      + core_number_format({
        'decimals-min': 0,
        'number': wins,
      }) + ' wins (' + percent(wins, total) + ')';
}
<|endoftext|>


<commit_before>var bookmarks_page = 0;
var bookmarks_loading = false;

var bookmarks_sorting = 'bookmarks_sorting_recent';

var bookmark_view = 'image';

$(document).ready(function() {
	$('.centercontent').click(clickSideBar);
	$('#view_type input').click(clickSwitchView);
	$('#bookmark_add_submit').click(addBookmark);
	$(window).resize(function () {
		fillWindow($('.bookmarks_list'));
	});
	$(window).resize();
	$('.bookmarks_list').scroll(updateOnBottom).empty().width($('#rightcontent').width());
	$('#tag_filter input').tagit({
		allowSpaces: true,
		availableTags: fullTags,
		onTagRemoved: filterTagsChanged
	}).tagit('option', 'onTagAdded', filterTagsChanged);
	getBookmarks();

	if(init_sidebar != 'true')
		toggleSideBar();
	bookmark_view = init_view;
	switchView();
});

var formatString = (function() {
	var replacer = function(context) {
		return function(s, name) {
			return context[name];
		};
	};

	return function(input, context) {
		return input.replace(/\{(\w+)\}/g, replacer(context));
	};
})();

function clickSideBar() {
	$.post(OC.filePath('bookmarks', 'ajax', 'changescreen.php'), {sidebar: $('.right_img').is(':visible')});
	toggleSideBar();
}

function toggleSideBar(){
	var left_pan_visible = $('.right_img').is(':visible');
	anim_duration = 1000;
	if( left_pan_visible) { // then show the left panel
		$('#rightcontent').animate({'left':'32.5em'},{duration: anim_duration, queue: false });
		$('.bookmarks_list').animate({'width': '-=15em'},{duration: anim_duration, queue: false });
		$('#leftcontent').animate({'margin-left':'0', 'opacity': 1},{duration: anim_duration, queue: false, complete: function() { $(window).trigger('resize'); }});
		$('.right_img').hide();
		$('.left_img').show();

	} else { // hide the left panel
		$('#rightcontent').animate({'left':'15.5em'},{duration: anim_duration, queue: false });
		$('.bookmarks_list').animate({'width': '+=15em'},{duration: anim_duration, queue: false });
		$('#leftcontent').animate({'margin-left':'-17em', 'opacity': 0.5 },{duration: anim_duration, queue: false, complete: function() { $(window).trigger('resize'); } });
		$('.left_img').hide();
		$('.right_img').show();
	}
}

function clickSwitchView(){
	$.post(OC.filePath('bookmarks', 'ajax', 'changescreen.php'), {view:bookmark_view});
	switchView();
}

function switchView(){
	if(bookmark_view == 'list') { //Then switch to img
		$('.bookmarks_list').addClass('bm_view_img');
		$('.bookmarks_list').removeClass('bm_view_list');
		$('#view_type input.image').hide();
		$('#view_type input.list').show();
		bookmark_view = 'image';
	} else { // Then Image
		$('.bookmarks_list').addClass('bm_view_list');
		$('.bookmarks_list').removeClass('bm_view_img');
		$('#view_type input.list').hide();
		$('#view_type input.image').show();
		bookmark_view = 'list';
	}
	filterTagsChanged(); //Refresh the view
}
function addFilterTag(event) {
	event.preventDefault();
	$('#tag_filter input').tagit('createTag', $(this).text());
}

function updateTagsList(tag) {
	$('.tag_list').append('
'+tag['tag']+''+
		'
'+
			''+
				''+
			''+
			''+
				''+
			''+
		'
'+
		''+tag['nbr']+''+
	'
');

}

function filterTagsChanged()
{
	$('#bookmarkFilterTag').val($('#tag_filter input:hidden').val());
	$('.bookmarks_list').empty();
	bookmarks_page = 0;
	getBookmarks();
}
function getBookmarks() {
	if(bookmarks_loading) {
		//have patience :)
		return;
	}
	bookmarks_loading = true;
	//Update Rel Tags if first page
	if(bookmarks_page == 0) {

		$.ajax({
			type: 'GET',
			url: OC.filePath('bookmarks', 'ajax', 'updateList.php') + '&type=rel_tags',
			data: {tag: $('#bookmarkFilterTag').val(), page:bookmarks_page, sort:bookmarks_sorting },
			success: function(tags){
				$('.tag_list').empty();
				for(var i in tags.data) {
					updateTagsList(tags.data[i]);
				}
				$('.tag_list .tag_edit').click(renameTag);
				$('.tag_list .tag_delete').click(deleteTag);
				$('.tag_list a.tag').click(addFilterTag);

			}
		});
	}
	$.ajax({
		type: 'GET',
		url: OC.filePath('bookmarks', 'ajax', 'updateList.php') + '&type=bookmark',
		data: {tag: $('#bookmarkFilterTag').val(), page:bookmarks_page, sort:bookmarks_sorting },
		success: function(bookmarks){
			if (bookmarks.data.length) {
				bookmarks_page += 1;
			}
			$('.bookmark_link').unbind('click', recordClick);
			$('.bookmark_delete').unbind('click', delBookmark);
			$('.bookmark_edit').unbind('click', editBookmark);

			for(var i in bookmarks.data) {
				updateBookmarksList(bookmarks.data[i]);
				$("#firstrun").hide();
			}
			if($('.bookmarks_list').is(':empty')) {
				$("#firstrun").show();
			}

			$('.bookmark_link').click(recordClick);
			$('.bookmark_delete').click(delBookmark);
			$('.bookmark_edit').click(editBookmark);

			bookmarks_loading = false;
			if (bookmarks.data.length) {
				updateOnBottom()
			}
		}
	});
}

function createEditDialog(record){
	dialog_html = $('#edit_dialog').html();
	var dialog = $(dialog_html).dialog({
		width : 620,
		height: 400,
		title: t('bookmark', 'Edit bookmark'),
		modal: true,
		close : function(event, ui) {
			$(this).dialog('destroy').remove();
		}
	});

	$('.ui-dialog').bookmark_dialog({
		on_success: function(){
			dialog.dialog('destroy').remove();
			filterTagsChanged();
		},
		record: record
	});
}

function addBookmark(event) {
	createEditDialog();
}

function delBookmark(event) {
	var record = $(this).parent().parent();
	$.ajax({
		type: 'POST',
		url: OC.filePath('bookmarks', 'ajax', 'delBookmark.php'),
		data: 'id=' + record.data('id'),
		success: function(data){
			if (data.status == 'success') {
				record.remove();
				if($('.bookmarks_list').is(':empty')) {
					$("#firstrun").show();
				}
			}
		}
	});
}

function editBookmark(event) {
	var record = $(this).parent().parent();
	bookmark =  record.data('record');
	createEditDialog(bookmark);
}

function updateBookmarksList(bookmark) {
	var tags = bookmark.tags;
	var taglist = '';
	for ( var i=0, len=tags.length; i<len; ++i ){
		if(tags[i] != '')
			taglist = taglist + '' + encodeEntities(tags[i]) + ' ';
	}
	if(!hasProtocol(bookmark.url)) {
		bookmark.url = 'http://' + bookmark.url;
	}
	if(bookmark.title == '') bookmark.title = bookmark.url;

	bookmark.added_date = new Date();
	bookmark.added_date.setTime(parseInt(bookmark.added)*1000);
	if(bookmark_view == 'image') { //View in images
		service_url = formatString(shot_provider, {url: encodeEntities(bookmark.url), title: bookmark.title, width: 200});
		$('.bookmarks_list').append(
			'
' +
				'

'+
				'
' +
					'' +
						'' +
					'' +
					'' +
						'' +
					' ' +
				'
' +
				'
'+
					'' + encodeEntities(bookmark.title) + '' +
				'
' +
				'
'+ encodeEntities(bookmark.description) + '
' +
				'
' + encodeEntities(bookmark.url) + '
' +
			'
'
		);
		$('div[data-id="'+ bookmark.id +'"]').data('record', bookmark);
		if(taglist != '') {
			$('div[data-id="'+ bookmark.id +'"]').append('
' + taglist + '
');
		}
		$('div[data-id="'+ bookmark.id +'"] a.bookmark_tag').bind('click', addFilterTag);
	}
	else { // View in text
		$('.bookmarks_list').append(
			'
' +
				'
' +
					'' +
						'' +
					'' +
					'' +
						'' +
					' ' +
				'
' +
				'
'+
					'' + encodeEntities(bookmark.title) + '' +
				'
' +
				'
' + encodeEntities(bookmark.url) + '
' +
				'
' + formatDate(bookmark.added_date) + '
' +
				'
'+ encodeEntities(bookmark.description) + '
' +
			'
'
		);
		$('div[data-id="'+ bookmark.id +'"]').data('record', bookmark);
		if(taglist != '') {
			$('div[data-id="'+ bookmark.id +'"]').append('
' + taglist + '
');
		}
		$('div[data-id="'+ bookmark.id +'"] a.bookmark_tag').bind('click', addFilterTag);
	}

}

function updateOnBottom() {
	//check wether user is on bottom of the page
	var top = $('.bookmarks_list>:last-child').position().top;
	var height = $('.bookmarks_list').height();
	// use a bit of margin to begin loading before we are really at the
	// bottom
	if (top < height * 1.2) {
		getBookmarks();
	}
}

function recordClick(event) {
	$.ajax({
		type: 'POST',
		url: OC.filePath('bookmarks', 'ajax', 'recordClick.php'),
		data: 'url=' + encodeURIComponent($(this).attr('href')),
	});
}

function encodeEntities(s){
	try {
		return $('
').text(s).html();
	} catch (ex) {
		return "";
	}
}

function hasProtocol(url) {
    var regexp = /(ftp|http|https|sftp)/;
    return regexp.test(url);
}

function renameTag(event) {
	if($('input[name="tag_new_name"]').length) return; // Do nothing if a tag is currenlty edited
	tag_el = $(this).closest('li');
	tag_el.append('<form><input name="tag_new_name" type="text"></form>');
	var form = tag_el.find('form');
	tag_el.find('.tags_actions').hide();
	tag_name = tag_el.find('.tag').hide().text();
	tag_el.find('input').val(tag_name).focus().bind('blur',function() {
		form.trigger('submit');
	});
	form.bind('submit',submitTagName);
}

function submitTagName(event) {
	event.preventDefault();
	tag_el = $(this).closest('li')
	new_tag_name = tag_el.find('input').val();
	old_tag_name = tag_el.find('.tag').show().text();
	tag_el.find('.tag_edit').show();
	tag_el.find('.tags_actions').show();
	tag_el.find('input').unbind('blur');
	tag_el.find('form').unbind('submit').remove();

	if(new_tag_name != old_tag_name && new_tag_name != '') {
		//submit
		$.ajax({
			type: 'POST',
			url: OC.filePath('bookmarks', 'ajax', 'renameTag.php'),
			data: { old_name: old_tag_name, new_name:  new_tag_name},
			success: function(bookmarks){
				if (bookmarks.status =='success') {
					filterTagsChanged();
				}
			}
		});
	}
}

function deleteTag(event){
	tag_el = $(this).closest('li');
	var old_tag_name = tag_el.find('.tag').show().text();
	OC.dialogs.confirm(t('bookmarks', 'Are you sure you want to remove this tag fro every entry?'),
	 t('bookmarks', 'Warning'), function(answer) {
		if(answer) {
			$.ajax({
				type: 'POST',
				url: OC.filePath('bookmarks', 'ajax', 'delTag.php'),
				data: { old_name: old_tag_name},
				success: function(bookmarks){
					if (bookmarks.status =='success') {
						filterTagsChanged();
					}
				}
			});
		}
	});
}

<commit_msg>Correct string typo<commit_after>var bookmarks_page = 0;
var bookmarks_loading = false;

var bookmarks_sorting = 'bookmarks_sorting_recent';

var bookmark_view = 'image';

$(document).ready(function() {
	$('.centercontent').click(clickSideBar);
	$('#view_type input').click(clickSwitchView);
	$('#bookmark_add_submit').click(addBookmark);
	$(window).resize(function () {
		fillWindow($('.bookmarks_list'));
	});
	$(window).resize();
	$('.bookmarks_list').scroll(updateOnBottom).empty().width($('#rightcontent').width());
	$('#tag_filter input').tagit({
		allowSpaces: true,
		availableTags: fullTags,
		onTagRemoved: filterTagsChanged
	}).tagit('option', 'onTagAdded', filterTagsChanged);
	getBookmarks();

	if(init_sidebar != 'true')
		toggleSideBar();
	bookmark_view = init_view;
	switchView();
});

var formatString = (function() {
	var replacer = function(context) {
		return function(s, name) {
			return context[name];
		};
	};

	return function(input, context) {
		return input.replace(/\{(\w+)\}/g, replacer(context));
	};
})();

function clickSideBar() {
	$.post(OC.filePath('bookmarks', 'ajax', 'changescreen.php'), {sidebar: $('.right_img').is(':visible')});
	toggleSideBar();
}

function toggleSideBar(){
	var left_pan_visible = $('.right_img').is(':visible');
	anim_duration = 1000;
	if( left_pan_visible) { // then show the left panel
		$('#rightcontent').animate({'left':'32.5em'},{duration: anim_duration, queue: false });
		$('.bookmarks_list').animate({'width': '-=15em'},{duration: anim_duration, queue: false });
		$('#leftcontent').animate({'margin-left':'0', 'opacity': 1},{duration: anim_duration, queue: false, complete: function() { $(window).trigger('resize'); }});
		$('.right_img').hide();
		$('.left_img').show();

	} else { // hide the left panel
		$('#rightcontent').animate({'left':'15.5em'},{duration: anim_duration, queue: false });
		$('.bookmarks_list').animate({'width': '+=15em'},{duration: anim_duration, queue: false });
		$('#leftcontent').animate({'margin-left':'-17em', 'opacity': 0.5 },{duration: anim_duration, queue: false, complete: function() { $(window).trigger('resize'); } });
		$('.left_img').hide();
		$('.right_img').show();
	}
}

function clickSwitchView(){
	$.post(OC.filePath('bookmarks', 'ajax', 'changescreen.php'), {view:bookmark_view});
	switchView();
}

function switchView(){
	if(bookmark_view == 'list') { //Then switch to img
		$('.bookmarks_list').addClass('bm_view_img');
		$('.bookmarks_list').removeClass('bm_view_list');
		$('#view_type input.image').hide();
		$('#view_type input.list').show();
		bookmark_view = 'image';
	} else { // Then Image
		$('.bookmarks_list').addClass('bm_view_list');
		$('.bookmarks_list').removeClass('bm_view_img');
		$('#view_type input.list').hide();
		$('#view_type input.image').show();
		bookmark_view = 'list';
	}
	filterTagsChanged(); //Refresh the view
}
function addFilterTag(event) {
	event.preventDefault();
	$('#tag_filter input').tagit('createTag', $(this).text());
}

function updateTagsList(tag) {
	$('.tag_list').append('
'+tag['tag']+''+
		'
'+
			''+
				''+
			''+
			''+
				''+
			''+
		'
'+
		''+tag['nbr']+''+
	'
');

}

function filterTagsChanged()
{
	$('#bookmarkFilterTag').val($('#tag_filter input:hidden').val());
	$('.bookmarks_list').empty();
	bookmarks_page = 0;
	getBookmarks();
}
function getBookmarks() {
	if(bookmarks_loading) {
		//have patience :)
		return;
	}
	bookmarks_loading = true;
	//Update Rel Tags if first page
	if(bookmarks_page == 0) {

		$.ajax({
			type: 'GET',
			url: OC.filePath('bookmarks', 'ajax', 'updateList.php') + '&type=rel_tags',
			data: {tag: $('#bookmarkFilterTag').val(), page:bookmarks_page, sort:bookmarks_sorting },
			success: function(tags){
				$('.tag_list').empty();
				for(var i in tags.data) {
					updateTagsList(tags.data[i]);
				}
				$('.tag_list .tag_edit').click(renameTag);
				$('.tag_list .tag_delete').click(deleteTag);
				$('.tag_list a.tag').click(addFilterTag);

			}
		});
	}
	$.ajax({
		type: 'GET',
		url: OC.filePath('bookmarks', 'ajax', 'updateList.php') + '&type=bookmark',
		data: {tag: $('#bookmarkFilterTag').val(), page:bookmarks_page, sort:bookmarks_sorting },
		success: function(bookmarks){
			if (bookmarks.data.length) {
				bookmarks_page += 1;
			}
			$('.bookmark_link').unbind('click', recordClick);
			$('.bookmark_delete').unbind('click', delBookmark);
			$('.bookmark_edit').unbind('click', editBookmark);

			for(var i in bookmarks.data) {
				updateBookmarksList(bookmarks.data[i]);
				$("#firstrun").hide();
			}
			if($('.bookmarks_list').is(':empty')) {
				$("#firstrun").show();
			}

			$('.bookmark_link').click(recordClick);
			$('.bookmark_delete').click(delBookmark);
			$('.bookmark_edit').click(editBookmark);

			bookmarks_loading = false;
			if (bookmarks.data.length) {
				updateOnBottom()
			}
		}
	});
}

function createEditDialog(record){
	dialog_html = $('#edit_dialog').html();
	var dialog = $(dialog_html).dialog({
		width : 620,
		height: 400,
		title: t('bookmark', 'Edit bookmark'),
		modal: true,
		close : function(event, ui) {
			$(this).dialog('destroy').remove();
		}
	});

	$('.ui-dialog').bookmark_dialog({
		on_success: function(){
			dialog.dialog('destroy').remove();
			filterTagsChanged();
		},
		record: record
	});
}

function addBookmark(event) {
	createEditDialog();
}

function delBookmark(event) {
	var record = $(this).parent().parent();
	$.ajax({
		type: 'POST',
		url: OC.filePath('bookmarks', 'ajax', 'delBookmark.php'),
		data: 'id=' + record.data('id'),
		success: function(data){
			if (data.status == 'success') {
				record.remove();
				if($('.bookmarks_list').is(':empty')) {
					$("#firstrun").show();
				}
			}
		}
	});
}

function editBookmark(event) {
	var record = $(this).parent().parent();
	bookmark =  record.data('record');
	createEditDialog(bookmark);
}

function updateBookmarksList(bookmark) {
	var tags = bookmark.tags;
	var taglist = '';
	for ( var i=0, len=tags.length; i<len; ++i ){
		if(tags[i] != '')
			taglist = taglist + '' + encodeEntities(tags[i]) + ' ';
	}
	if(!hasProtocol(bookmark.url)) {
		bookmark.url = 'http://' + bookmark.url;
	}
	if(bookmark.title == '') bookmark.title = bookmark.url;

	bookmark.added_date = new Date();
	bookmark.added_date.setTime(parseInt(bookmark.added)*1000);
	if(bookmark_view == 'image') { //View in images
		service_url = formatString(shot_provider, {url: encodeEntities(bookmark.url), title: bookmark.title, width: 200});
		$('.bookmarks_list').append(
			'
' +
				'

'+
				'
' +
					'' +
						'' +
					'' +
					'' +
						'' +
					' ' +
				'
' +
				'
'+
					'' + encodeEntities(bookmark.title) + '' +
				'
' +
				'
'+ encodeEntities(bookmark.description) + '
' +
				'
' + encodeEntities(bookmark.url) + '
' +
			'
'
		);
		$('div[data-id="'+ bookmark.id +'"]').data('record', bookmark);
		if(taglist != '') {
			$('div[data-id="'+ bookmark.id +'"]').append('
' + taglist + '
');
		}
		$('div[data-id="'+ bookmark.id +'"] a.bookmark_tag').bind('click', addFilterTag);
	}
	else { // View in text
		$('.bookmarks_list').append(
			'
' +
				'
' +
					'' +
						'' +
					'' +
					'' +
						'' +
					' ' +
				'
' +
				'
'+
					'' + encodeEntities(bookmark.title) + '' +
				'
' +
				'
' + encodeEntities(bookmark.url) + '
' +
				'
' + formatDate(bookmark.added_date) + '
' +
				'
'+ encodeEntities(bookmark.description) + '
' +
			'
'
		);
		$('div[data-id="'+ bookmark.id +'"]').data('record', bookmark);
		if(taglist != '') {
			$('div[data-id="'+ bookmark.id +'"]').append('
' + taglist + '
');
		}
		$('div[data-id="'+ bookmark.id +'"] a.bookmark_tag').bind('click', addFilterTag);
	}

}

function updateOnBottom() {
	//check wether user is on bottom of the page
	var top = $('.bookmarks_list>:last-child').position().top;
	var height = $('.bookmarks_list').height();
	// use a bit of margin to begin loading before we are really at the
	// bottom
	if (top < height * 1.2) {
		getBookmarks();
	}
}

function recordClick(event) {
	$.ajax({
		type: 'POST',
		url: OC.filePath('bookmarks', 'ajax', 'recordClick.php'),
		data: 'url=' + encodeURIComponent($(this).attr('href')),
	});
}

function encodeEntities(s){
	try {
		return $('
').text(s).html();
	} catch (ex) {
		return "";
	}
}

function hasProtocol(url) {
    var regexp = /(ftp|http|https|sftp)/;
    return regexp.test(url);
}

function renameTag(event) {
	if($('input[name="tag_new_name"]').length) return; // Do nothing if a tag is currenlty edited
	tag_el = $(this).closest('li');
	tag_el.append('<form><input name="tag_new_name" type="text"></form>');
	var form = tag_el.find('form');
	tag_el.find('.tags_actions').hide();
	tag_name = tag_el.find('.tag').hide().text();
	tag_el.find('input').val(tag_name).focus().bind('blur',function() {
		form.trigger('submit');
	});
	form.bind('submit',submitTagName);
}

function submitTagName(event) {
	event.preventDefault();
	tag_el = $(this).closest('li')
	new_tag_name = tag_el.find('input').val();
	old_tag_name = tag_el.find('.tag').show().text();
	tag_el.find('.tag_edit').show();
	tag_el.find('.tags_actions').show();
	tag_el.find('input').unbind('blur');
	tag_el.find('form').unbind('submit').remove();

	if(new_tag_name != old_tag_name && new_tag_name != '') {
		//submit
		$.ajax({
			type: 'POST',
			url: OC.filePath('bookmarks', 'ajax', 'renameTag.php'),
			data: { old_name: old_tag_name, new_name:  new_tag_name},
			success: function(bookmarks){
				if (bookmarks.status =='success') {
					filterTagsChanged();
				}
			}
		});
	}
}

function deleteTag(event){
	tag_el = $(this).closest('li');
	var old_tag_name = tag_el.find('.tag').show().text();
	OC.dialogs.confirm(t('bookmarks', 'Are you sure you want to remove this tag from every entry?'),
	 t('bookmarks', 'Warning'), function(answer) {
		if(answer) {
			$.ajax({
				type: 'POST',
				url: OC.filePath('bookmarks', 'ajax', 'delTag.php'),
				data: { old_name: old_tag_name},
				success: function(bookmarks){
					if (bookmarks.status =='success') {
						filterTagsChanged();
					}
				}
			});
		}
	});
}

<|endoftext|>


<commit_before>'use strict';

function click_button(){
    // Add clicks-per-click to clicks.
    core_storage_data['clicks'] = core_round({
      'decimals': 2,
      'number': core_storage_data['clicks'] + core_storage_data['clicks-per-click'] * core_storage_data['multiplier-per-click'],
    });

    core_storage_update({
      'keys': [
        'clicks',
      ],
    });

    core_ui_update({
      'ids': {
        'ui-clicks': core_number_format({
          'decimals-min': 2,
          'number': core_storage_data['clicks'],
        }),
      },
    });

    // Force click-button to lose focus.
    document.getElementById('click-button').blur();
}

function purchase(upgrade, cost, target, free){
    free = free || false;

    if(!free
      && core_storage_data['clicks'] < core_storage_data['upgrade-' + upgrade + '-cost']){
        return;
    }

    if(!free){
        // If user can afford upgrade, subtract cost of upgrade from clicks...
        core_storage_data['clicks'] = core_round({
          'decimals': 2,
          'number': core_storage_data['clicks'] - core_storage_data['upgrade-' + upgrade + '-cost'],
        });
        core_ui_update({
          'ids': {
            'ui-clicks': core_number_format({
              'decimals-min': 2,
              'number': core_storage_data['clicks'],
            }),
          },
        });
    }

    // ...and increase upgrade/cost...
    core_storage_data['upgrade-' + upgrade] += 1;
    core_storage_data['upgrade-' + upgrade + '-cost'] *= upgrades[upgrade]['multiplier'];
    document.getElementById('ui-upgrade-' + upgrade + '-cost').innerHTML = core_number_format({
      'decimals-min': 0,
      'number': core_storage_data['upgrade-' + upgrade + '-cost'],
    });

    // ...and increase target value, either clicks-per-click or clicks-per-second.
    core_storage_data[target] = core_round({
      'decimals': 2,
      'number': core_storage_data[target] + upgrades[upgrade]['bonus'],
    });

    update_multiplied();
    core_storage_update();
}

function second(){
    core_storage_data['clicks'] = core_round({
      'decimals': 2,
      'number': core_storage_data['clicks'] + core_storage_data['clicks-per-second'] * core_storage_data['multiplier-per-second'],
    });

    core_storage_update({
      'keys': [
        'clicks',
      ],
    });

    let formatted = core_number_format({
      'decimals-min': 2,
      'number': core_storage_data['clicks'],
    });
    core_ui_update({
      'ids': {
        'ui-clicks': formatted,
      },
    });
    document.title = formatted;
}

function update_multiplied(){
    core_storage_data['clicks-per-click-multiplied'] = core_round({
      'decimals': 2,
      'number': core_storage_data['clicks-per-click'] * core_storage_data['multiplier-per-click'],
    });
    core_storage_data['clicks-per-second-multiplied'] = core_round({
      'decimals': 2,
      'number': core_storage_data['clicks-per-second'] * core_storage_data['multiplier-per-second'],
    });
}
<commit_msg>added saving of clicks every second<commit_after>'use strict';

function click_button(){
    // Add clicks-per-click to clicks.
    core_storage_data['clicks'] = core_round({
      'decimals': 2,
      'number': core_storage_data['clicks'] + core_storage_data['clicks-per-click'] * core_storage_data['multiplier-per-click'],
    });

    core_storage_update({
      'keys': [
        'clicks',
      ],
    });

    core_ui_update({
      'ids': {
        'ui-clicks': core_number_format({
          'decimals-min': 2,
          'number': core_storage_data['clicks'],
        }),
      },
    });

    // Force click-button to lose focus.
    document.getElementById('click-button').blur();
}

function purchase(upgrade, cost, target, free){
    free = free || false;

    if(!free
      && core_storage_data['clicks'] < core_storage_data['upgrade-' + upgrade + '-cost']){
        return;
    }

    if(!free){
        // If user can afford upgrade, subtract cost of upgrade from clicks...
        core_storage_data['clicks'] = core_round({
          'decimals': 2,
          'number': core_storage_data['clicks'] - core_storage_data['upgrade-' + upgrade + '-cost'],
        });
        core_ui_update({
          'ids': {
            'ui-clicks': core_number_format({
              'decimals-min': 2,
              'number': core_storage_data['clicks'],
            }),
          },
        });
    }

    // ...and increase upgrade/cost...
    core_storage_data['upgrade-' + upgrade] += 1;
    core_storage_data['upgrade-' + upgrade + '-cost'] *= upgrades[upgrade]['multiplier'];
    document.getElementById('ui-upgrade-' + upgrade + '-cost').innerHTML = core_number_format({
      'decimals-min': 0,
      'number': core_storage_data['upgrade-' + upgrade + '-cost'],
    });

    // ...and increase target value, either clicks-per-click or clicks-per-second.
    core_storage_data[target] = core_round({
      'decimals': 2,
      'number': core_storage_data[target] + upgrades[upgrade]['bonus'],
    });

    update_multiplied();
    core_storage_update();
}

function second(){
    core_storage_data['clicks'] = core_round({
      'decimals': 2,
      'number': core_storage_data['clicks'] + core_storage_data['clicks-per-second'] * core_storage_data['multiplier-per-second'],
    });

    core_storage_update({
      'keys': [
        'clicks',
      ],
    });
    core_storage_save({
      'keys': [
        'clicks',
      ],
    });

    let formatted = core_number_format({
      'decimals-min': 2,
      'number': core_storage_data['clicks'],
    });
    core_ui_update({
      'ids': {
        'ui-clicks': formatted,
      },
    });
    document.title = formatted;
}

function update_multiplied(){
    core_storage_data['clicks-per-click-multiplied'] = core_round({
      'decimals': 2,
      'number': core_storage_data['clicks-per-click'] * core_storage_data['multiplier-per-click'],
    });
    core_storage_data['clicks-per-second-multiplied'] = core_round({
      'decimals': 2,
      'number': core_storage_data['clicks-per-second'] * core_storage_data['multiplier-per-second'],
    });
}
<|endoftext|>


<commit_before>(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

function EventEmitter() {
  this._events = this._events || {};
  this._maxListeners = this._maxListeners || undefined;
}
module.exports = EventEmitter;

// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;

// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;

// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function(n) {
  if (!isNumber(n) || n < 0 || isNaN(n))
    throw TypeError('n must be a positive number');
  this._maxListeners = n;
  return this;
};

EventEmitter.prototype.emit = function(type) {
  var er, handler, len, args, i, listeners;

  if (!this._events)
    this._events = {};

  // If there is no 'error' event listener then throw.
  if (type === 'error') {
    if (!this._events.error ||
        (isObject(this._events.error) && !this._events.error.length)) {
      er = arguments[1];
      if (er instanceof Error) {
        throw er; // Unhandled 'error' event
      }
      throw TypeError('Uncaught, unspecified "error" event.');
    }
  }

  handler = this._events[type];

  if (isUndefined(handler))
    return false;

  if (isFunction(handler)) {
    switch (arguments.length) {
      // fast cases
      case 1:
        handler.call(this);
        break;
      case 2:
        handler.call(this, arguments[1]);
        break;
      case 3:
        handler.call(this, arguments[1], arguments[2]);
        break;
      // slower
      default:
        len = arguments.length;
        args = new Array(len - 1);
        for (i = 1; i < len; i++)
          args[i - 1] = arguments[i];
        handler.apply(this, args);
    }
  } else if (isObject(handler)) {
    len = arguments.length;
    args = new Array(len - 1);
    for (i = 1; i < len; i++)
      args[i - 1] = arguments[i];

    listeners = handler.slice();
    len = listeners.length;
    for (i = 0; i < len; i++)
      listeners[i].apply(this, args);
  }

  return true;
};

EventEmitter.prototype.addListener = function(type, listener) {
  var m;

  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  if (!this._events)
    this._events = {};

  // To avoid recursion in the case that type === "newListener"! Before
  // adding it to the listeners, first emit "newListener".
  if (this._events.newListener)
    this.emit('newListener', type,
              isFunction(listener.listener) ?
              listener.listener : listener);

  if (!this._events[type])
    // Optimize the case of one listener. Don't need the extra array object.
    this._events[type] = listener;
  else if (isObject(this._events[type]))
    // If we've already got an array, just append.
    this._events[type].push(listener);
  else
    // Adding the second element, need to change to array.
    this._events[type] = [this._events[type], listener];

  // Check for listener leak
  if (isObject(this._events[type]) && !this._events[type].warned) {
    var m;
    if (!isUndefined(this._maxListeners)) {
      m = this._maxListeners;
    } else {
      m = EventEmitter.defaultMaxListeners;
    }

    if (m && m > 0 && this._events[type].length > m) {
      this._events[type].warned = true;
      console.error('(node) warning: possible EventEmitter memory ' +
                    'leak detected. %d listeners added. ' +
                    'Use emitter.setMaxListeners() to increase limit.',
                    this._events[type].length);
      if (typeof console.trace === 'function') {
        // not supported in IE 10
        console.trace();
      }
    }
  }

  return this;
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

EventEmitter.prototype.once = function(type, listener) {
  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  var fired = false;

  function g() {
    this.removeListener(type, g);

    if (!fired) {
      fired = true;
      listener.apply(this, arguments);
    }
  }

  g.listener = listener;
  this.on(type, g);

  return this;
};

// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener = function(type, listener) {
  var list, position, length, i;

  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  if (!this._events || !this._events[type])
    return this;

  list = this._events[type];
  length = list.length;
  position = -1;

  if (list === listener ||
      (isFunction(list.listener) && list.listener === listener)) {
    delete this._events[type];
    if (this._events.removeListener)
      this.emit('removeListener', type, listener);

  } else if (isObject(list)) {
    for (i = length; i-- > 0;) {
      if (list[i] === listener ||
          (list[i].listener && list[i].listener === listener)) {
        position = i;
        break;
      }
    }

    if (position < 0)
      return this;

    if (list.length === 1) {
      list.length = 0;
      delete this._events[type];
    } else {
      list.splice(position, 1);
    }

    if (this._events.removeListener)
      this.emit('removeListener', type, listener);
  }

  return this;
};

EventEmitter.prototype.removeAllListeners = function(type) {
  var key, listeners;

  if (!this._events)
    return this;

  // not listening for removeListener, no need to emit
  if (!this._events.removeListener) {
    if (arguments.length === 0)
      this._events = {};
    else if (this._events[type])
      delete this._events[type];
    return this;
  }

  // emit removeListener for all listeners on all events
  if (arguments.length === 0) {
    for (key in this._events) {
      if (key === 'removeListener') continue;
      this.removeAllListeners(key);
    }
    this.removeAllListeners('removeListener');
    this._events = {};
    return this;
  }

  listeners = this._events[type];

  if (isFunction(listeners)) {
    this.removeListener(type, listeners);
  } else {
    // LIFO order
    while (listeners.length)
      this.removeListener(type, listeners[listeners.length - 1]);
  }
  delete this._events[type];

  return this;
};

EventEmitter.prototype.listeners = function(type) {
  var ret;
  if (!this._events || !this._events[type])
    ret = [];
  else if (isFunction(this._events[type]))
    ret = [this._events[type]];
  else
    ret = this._events[type].slice();
  return ret;
};

EventEmitter.listenerCount = function(emitter, type) {
  var ret;
  if (!emitter._events || !emitter._events[type])
    ret = 0;
  else if (isFunction(emitter._events[type]))
    ret = 1;
  else
    ret = emitter._events[type].length;
  return ret;
};

function isFunction(arg) {
  return typeof arg === 'function';
}

function isNumber(arg) {
  return typeof arg === 'number';
}

function isObject(arg) {
  return typeof arg === 'object' && arg !== null;
}

function isUndefined(arg) {
  return arg === void 0;
}

},{}],2:[function(require,module,exports){
var EventEmitter = require('events').EventEmitter;

//  This Player thing should be used inside gadget
//  as a convenience API over postMessage.
//
//  Supported events:
//  https://github.com/Versal/versal-gadget-launchers/tree/master/iframe-launcher
var PlayerAPI = function(options){
  EventEmitter.call(this);

  this.eventSource = (options && options.eventSource) || window.parent;

  // TODO: don't communicate assets in setAttributes event in the player
  this._assetAttributes = {};
  this._assetCallbacks = {};

  if(typeof window != 'undefined'){
    if(options && options.debug){
      window.addEventListener('message', function(evt){
        if(evt.data && evt.data.event) {
          console.log('Gadget received message from SDK:', evt.data.event, evt.data.data);
        }
      });
    }
    window.addEventListener('message', this.handleMessage.bind(this));
  }
};

PlayerAPI.prototype = Object.create(EventEmitter.prototype);

PlayerAPI.prototype.addEventListener = PlayerAPI.prototype.addListener;
PlayerAPI.prototype.removeEventListener = PlayerAPI.prototype.removeListener;

PlayerAPI.prototype.sendMessage = function(name, data) {
  var message = { event: name };
  if(data) { message.data = data; }

  this.eventSource.postMessage(message, '*');
};

PlayerAPI.prototype.handleMessage = function(evt) {
  var message = evt.data;

  if(message && message.event) {
    this.emit('message', message);

    // Inspect attributes for asset-related fields and extract asset json
    if(message.event == 'attributesChanged') {
      this._triggerAssetCallbacks(message.data);
    }

    this.emit(message.event, message.data);

    if(message.event == 'environmentChanged') {
      this.assetUrlTemplate = message.data.assetUrlTemplate;
    }
  }
};

// TODO: move implementation to the player
PlayerAPI.prototype._triggerAssetCallbacks = function(attrs){
  Object.keys(this._assetAttributes).forEach(function(name){
    if(this._assetAttributes[name] != true) { return; }

    if(attrs[name]) {
      var asset = attrs[name];
      this.emit('assetSelected', { name: name, asset: asset });
      this._assetAttributes[name] = null;

      if(this._assetCallbacks[name]) {
        this._assetCallbacks[name].call(this, asset);
      }
    }
  }.bind(this));
};

PlayerAPI.prototype.startListening = function(){
  this.sendMessage('startListening');
};

PlayerAPI.prototype.setHeight = function(px) {
  if (px === this._lastSetHeight) return;
  this._lastSetHeight = px;
  this.sendMessage('setHeight', { pixels: px });
};

PlayerAPI.prototype.setHeightToBodyHeight = function() {
  this.setHeight(document.body.offsetHeight);
};

PlayerAPI.prototype.watchBodyHeight = function(options) {
  this.unwatchBodyHeight();
  this.setHeightToBodyHeight();

  options = options || {};
  options.interval = options.interval || 32; // 2 frame refreshes at 60Hz, see https://github.com/davidjbradshaw/iframe-resizer/blob/v2.5.2/README.md#interval

  this._bodyHeightInterval = setInterval(this.setHeightToBodyHeight.bind(this), options.interval);
};

PlayerAPI.prototype.unwatchBodyHeight = function() {
  if (this._bodyHeightInterval === undefined) return;
  clearInterval(this._bodyHeightInterval);
  delete this._bodyHeightInterval;
};

PlayerAPI.prototype.setAttribute = function(name, value){
  var attr = {};
  attr[name] = value;
  this.setAttributes(attr);
};

PlayerAPI.prototype.setAttributes = function(attrs) {
  this.sendMessage('setAttributes', attrs);
};

/*
 * Deprecation Notice
 * We discourage the use of setLearnerAttribute and setLearnerAttributes
 * in favor of setLearnerState
*/
PlayerAPI.prototype.setLearnerAttribute = function(name, value){
  var attr = {};
  attr[name] = value;
  this.setLearnerState(attr);
};

/*
 * Deprecation Notice
 * We discourage the use of setLearnerAttribute and setLearnerAttributes
 * in favor of setLearnerState
*/
PlayerAPI.prototype.setLearnerAttributes = function(attrs) {
  this.sendMessage('setLearnerState', attrs);
};

PlayerAPI.prototype.setLearnerState = function(attrs) {
  this.sendMessage('setLearnerState', attrs);
};

PlayerAPI.prototype.setPropertySheetAttributes = function(attrs){
  this.sendMessage('setPropertySheetAttributes', attrs);
};

PlayerAPI.prototype.track = function(name, _data){
  var data = { '@type': name };
  Object.keys(_data).forEach(function(key){
    data[key] = _data[key];
  });
  this.sendMessage('track', data);
};

PlayerAPI.prototype.error = function(data){
  this.sendMessage('error', data);
};

PlayerAPI.prototype.requestAsset = function(data, callback){
  if(!data.attribute) {
    var seed = Math.random().toString(36).substring(7);
    data.attribute = '__asset__' + seed;
  }
  // TODO: remove this after assets are communicated from the player
  // in a dedicated event
  this._assetAttributes[data.attribute] = true;

  if(callback) {
    this._assetCallbacks[data.attribute] = callback;
  }
  this.sendMessage('requestAsset', data);
};

PlayerAPI.prototype.assetUrl = function(id){
  return this.assetUrlTemplate.replace(/<%= id %>/, id);
};

module.exports = PlayerAPI;

window.VersalPlayerAPI = PlayerAPI;

},{"events":1}]},{},[2]);
<commit_msg>Build for release<commit_after>(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

function EventEmitter() {
  this._events = this._events || {};
  this._maxListeners = this._maxListeners || undefined;
}
module.exports = EventEmitter;

// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;

// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;

// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function(n) {
  if (!isNumber(n) || n < 0 || isNaN(n))
    throw TypeError('n must be a positive number');
  this._maxListeners = n;
  return this;
};

EventEmitter.prototype.emit = function(type) {
  var er, handler, len, args, i, listeners;

  if (!this._events)
    this._events = {};

  // If there is no 'error' event listener then throw.
  if (type === 'error') {
    if (!this._events.error ||
        (isObject(this._events.error) && !this._events.error.length)) {
      er = arguments[1];
      if (er instanceof Error) {
        throw er; // Unhandled 'error' event
      }
      throw TypeError('Uncaught, unspecified "error" event.');
    }
  }

  handler = this._events[type];

  if (isUndefined(handler))
    return false;

  if (isFunction(handler)) {
    switch (arguments.length) {
      // fast cases
      case 1:
        handler.call(this);
        break;
      case 2:
        handler.call(this, arguments[1]);
        break;
      case 3:
        handler.call(this, arguments[1], arguments[2]);
        break;
      // slower
      default:
        len = arguments.length;
        args = new Array(len - 1);
        for (i = 1; i < len; i++)
          args[i - 1] = arguments[i];
        handler.apply(this, args);
    }
  } else if (isObject(handler)) {
    len = arguments.length;
    args = new Array(len - 1);
    for (i = 1; i < len; i++)
      args[i - 1] = arguments[i];

    listeners = handler.slice();
    len = listeners.length;
    for (i = 0; i < len; i++)
      listeners[i].apply(this, args);
  }

  return true;
};

EventEmitter.prototype.addListener = function(type, listener) {
  var m;

  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  if (!this._events)
    this._events = {};

  // To avoid recursion in the case that type === "newListener"! Before
  // adding it to the listeners, first emit "newListener".
  if (this._events.newListener)
    this.emit('newListener', type,
              isFunction(listener.listener) ?
              listener.listener : listener);

  if (!this._events[type])
    // Optimize the case of one listener. Don't need the extra array object.
    this._events[type] = listener;
  else if (isObject(this._events[type]))
    // If we've already got an array, just append.
    this._events[type].push(listener);
  else
    // Adding the second element, need to change to array.
    this._events[type] = [this._events[type], listener];

  // Check for listener leak
  if (isObject(this._events[type]) && !this._events[type].warned) {
    var m;
    if (!isUndefined(this._maxListeners)) {
      m = this._maxListeners;
    } else {
      m = EventEmitter.defaultMaxListeners;
    }

    if (m && m > 0 && this._events[type].length > m) {
      this._events[type].warned = true;
      console.error('(node) warning: possible EventEmitter memory ' +
                    'leak detected. %d listeners added. ' +
                    'Use emitter.setMaxListeners() to increase limit.',
                    this._events[type].length);
      if (typeof console.trace === 'function') {
        // not supported in IE 10
        console.trace();
      }
    }
  }

  return this;
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

EventEmitter.prototype.once = function(type, listener) {
  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  var fired = false;

  function g() {
    this.removeListener(type, g);

    if (!fired) {
      fired = true;
      listener.apply(this, arguments);
    }
  }

  g.listener = listener;
  this.on(type, g);

  return this;
};

// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener = function(type, listener) {
  var list, position, length, i;

  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  if (!this._events || !this._events[type])
    return this;

  list = this._events[type];
  length = list.length;
  position = -1;

  if (list === listener ||
      (isFunction(list.listener) && list.listener === listener)) {
    delete this._events[type];
    if (this._events.removeListener)
      this.emit('removeListener', type, listener);

  } else if (isObject(list)) {
    for (i = length; i-- > 0;) {
      if (list[i] === listener ||
          (list[i].listener && list[i].listener === listener)) {
        position = i;
        break;
      }
    }

    if (position < 0)
      return this;

    if (list.length === 1) {
      list.length = 0;
      delete this._events[type];
    } else {
      list.splice(position, 1);
    }

    if (this._events.removeListener)
      this.emit('removeListener', type, listener);
  }

  return this;
};

EventEmitter.prototype.removeAllListeners = function(type) {
  var key, listeners;

  if (!this._events)
    return this;

  // not listening for removeListener, no need to emit
  if (!this._events.removeListener) {
    if (arguments.length === 0)
      this._events = {};
    else if (this._events[type])
      delete this._events[type];
    return this;
  }

  // emit removeListener for all listeners on all events
  if (arguments.length === 0) {
    for (key in this._events) {
      if (key === 'removeListener') continue;
      this.removeAllListeners(key);
    }
    this.removeAllListeners('removeListener');
    this._events = {};
    return this;
  }

  listeners = this._events[type];

  if (isFunction(listeners)) {
    this.removeListener(type, listeners);
  } else {
    // LIFO order
    while (listeners.length)
      this.removeListener(type, listeners[listeners.length - 1]);
  }
  delete this._events[type];

  return this;
};

EventEmitter.prototype.listeners = function(type) {
  var ret;
  if (!this._events || !this._events[type])
    ret = [];
  else if (isFunction(this._events[type]))
    ret = [this._events[type]];
  else
    ret = this._events[type].slice();
  return ret;
};

EventEmitter.listenerCount = function(emitter, type) {
  var ret;
  if (!emitter._events || !emitter._events[type])
    ret = 0;
  else if (isFunction(emitter._events[type]))
    ret = 1;
  else
    ret = emitter._events[type].length;
  return ret;
};

function isFunction(arg) {
  return typeof arg === 'function';
}

function isNumber(arg) {
  return typeof arg === 'number';
}

function isObject(arg) {
  return typeof arg === 'object' && arg !== null;
}

function isUndefined(arg) {
  return arg === void 0;
}

},{}],2:[function(require,module,exports){
var EventEmitter = require('events').EventEmitter;

//  This Player thing should be used inside gadget
//  as a convenience API over postMessage.
//
//  Supported events:
//  https://github.com/Versal/versal-gadget-launchers/tree/master/iframe-launcher
var PlayerAPI = function(options){
  EventEmitter.call(this);

  this.eventSource = (options && options.eventSource) || window.parent;

  // TODO: don't communicate assets in setAttributes event in the player
  this._assetAttributes = {};
  this._assetCallbacks = {};

  if(typeof window != 'undefined'){
    if(options && options.debug){
      window.addEventListener('message', function(evt){
        if(evt.data && evt.data.event) {
          console.log('Gadget received message from SDK:', evt.data.event, evt.data.data);
        }
      });
    }
    window.addEventListener('message', this.handleMessage.bind(this));
  }
};

PlayerAPI.prototype = Object.create(EventEmitter.prototype);

PlayerAPI.prototype.addEventListener = PlayerAPI.prototype.addListener;
PlayerAPI.prototype.removeEventListener = PlayerAPI.prototype.removeListener;

PlayerAPI.prototype.sendMessage = function(name, data) {
  var message = { event: name };
  if(data) { message.data = data; }

  this.eventSource.postMessage(message, '*');
};

PlayerAPI.prototype.handleMessage = function(evt) {
  var message = evt.data;

  if(message && message.event) {
    this.emit('message', message);

    // Inspect attributes for asset-related fields and extract asset json
    if(message.event == 'attributesChanged') {
      this._triggerAssetCallbacks(message.data);
    }

    this.emit(message.event, message.data);

    if(message.event == 'environmentChanged') {
      this.assetUrlTemplate = message.data.assetUrlTemplate;
      this.userId = message.data.userId;
      this.sessionId = message.data.sessionId;
    }
  }
};

// TODO: move implementation to the player
PlayerAPI.prototype._triggerAssetCallbacks = function(attrs){
  Object.keys(this._assetAttributes).forEach(function(name){
    if(this._assetAttributes[name] != true) { return; }

    if(attrs[name]) {
      var asset = attrs[name];
      this.emit('assetSelected', { name: name, asset: asset });
      this._assetAttributes[name] = null;

      if(this._assetCallbacks[name]) {
        this._assetCallbacks[name].call(this, asset);
      }
    }
  }.bind(this));
};

PlayerAPI.prototype.startListening = function(){
  this.sendMessage('startListening');
};

PlayerAPI.prototype.setHeight = function(px) {
  if (px === this._lastSetHeight) return;
  this._lastSetHeight = px;
  this.sendMessage('setHeight', { pixels: px });
};

PlayerAPI.prototype.setHeightToBodyHeight = function() {
  this.setHeight(document.body.offsetHeight);
};

PlayerAPI.prototype.watchBodyHeight = function(options) {
  this.unwatchBodyHeight();
  this.setHeightToBodyHeight();

  options = options || {};
  options.interval = options.interval || 32; // 2 frame refreshes at 60Hz, see https://github.com/davidjbradshaw/iframe-resizer/blob/v2.5.2/README.md#interval

  this._bodyHeightInterval = setInterval(this.setHeightToBodyHeight.bind(this), options.interval);
};

PlayerAPI.prototype.unwatchBodyHeight = function() {
  if (this._bodyHeightInterval === undefined) return;
  clearInterval(this._bodyHeightInterval);
  delete this._bodyHeightInterval;
};

PlayerAPI.prototype.setAttribute = function(name, value){
  var attr = {};
  attr[name] = value;
  this.setAttributes(attr);
};

PlayerAPI.prototype.setAttributes = function(attrs) {
  this.sendMessage('setAttributes', attrs);
};

/*
 * Deprecation Notice
 * We discourage the use of setLearnerAttribute and setLearnerAttributes
 * in favor of setLearnerState
*/
PlayerAPI.prototype.setLearnerAttribute = function(name, value){
  var attr = {};
  attr[name] = value;
  this.setLearnerState(attr);
};

/*
 * Deprecation Notice
 * We discourage the use of setLearnerAttribute and setLearnerAttributes
 * in favor of setLearnerState
*/
PlayerAPI.prototype.setLearnerAttributes = function(attrs) {
  this.sendMessage('setLearnerState', attrs);
};

PlayerAPI.prototype.setLearnerState = function(attrs) {
  this.sendMessage('setLearnerState', attrs);
};

PlayerAPI.prototype.setPropertySheetAttributes = function(attrs){
  this.sendMessage('setPropertySheetAttributes', attrs);
};

PlayerAPI.prototype.track = function(name, _data){
  var data = { '@type': name };
  Object.keys(_data).forEach(function(key){
    data[key] = _data[key];
  });
  this.sendMessage('track', data);
};

PlayerAPI.prototype.error = function(data){
  this.sendMessage('error', data);
};

PlayerAPI.prototype.requestAsset = function(data, callback){
  if(!data.attribute) {
    var seed = Math.random().toString(36).substring(7);
    data.attribute = '__asset__' + seed;
  }
  // TODO: remove this after assets are communicated from the player
  // in a dedicated event
  this._assetAttributes[data.attribute] = true;

  if(callback) {
    this._assetCallbacks[data.attribute] = callback;
  }
  this.sendMessage('requestAsset', data);
};

PlayerAPI.prototype.assetUrl = function(id){
  return this.assetUrlTemplate.replace(/<%= id %>/, id);
};

module.exports = PlayerAPI;

window.VersalPlayerAPI = PlayerAPI;

},{"events":1}]},{},[2]);
<|endoftext|>