<commit_before>/*
 * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-lwm2m-lib
 *
 * iotagent-lwm2m-lib is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * iotagent-lwm2m-lib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with iotagent-lwm2m-lib.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::[contacto@tid.es]
 */

'use strict';

var async = require('async'),
    apply = async.apply,
    objectRegistry = require('./objectRegistry'),
    errors = require('../../errors'),
    logger = require('logops'),
    _ = require('underscore'),
    observers = {};

/**
 * Extract Object type and id from the request URI, returning it using the callback.
 *
 * @param {Object} req          Arriving COAP Request to be handled.
 * @param {Object} res          Outgoing COAP Response.
 */
function extractUriInfo(req, res, callback) {
    if (req.urlObj.pathname.match(/\/\d+\/\d+\/\d+/)) {
        var resourceIndex = req.urlObj.pathname.lastIndexOf('/'),
            resourceId = req.urlObj.pathname.substring(resourceIndex + 1),
            objectUri = req.urlObj.pathname.substring(0, resourceIndex);

        callback(null, objectUri, resourceId);
    } else if (req.urlObj.pathname.match(/\/\d+(\/\d+)?/)) {
        callback(null, req.urlObj.pathname, null);
    } else {
        callback(new errors.WrongObjectUri(req.urlObj.pathname));
    }
}

/**
 * Invoke the user handler for this operation, with all the information from the query parameters as its arguments.
 * This method handling gives the client an opportunity to change or overwrite the values before returning them to
 * the client. The handler must call the callback with the overwritten value it wants to return. If the callback is
 * called without parameters, the original value is returned instead.
 *
 * @param {Object} queryParams      Object containing all the query parameters.
 * @param {Function} handler        User handler to be invoked.
 */
function applyHandler(resourceId, handler, storedObject, callback) {
    if (storedObject.attributes[resourceId]) {
        handler(
            storedObject.objectType,
            storedObject.objectId,
            resourceId,
            storedObject.attributes[resourceId],

            function handleReadOverwrite(error, result) {
                if (result) {
                    callback(error, result);
                } else {
                    callback(error, storedObject.attributes[resourceId]);
                }
            });
    } else {
        callback(new errors.ResourceNotFound(storedObject.objectType, storedObject.objectId, resourceId));
    }
}

/**
 * Generates the end of request handler that will generate the final response to the COAP Client.
 *
 * @param {Object} req          Arriving COAP Request to be handled.
 * @param {Object} res          Outgoing COAP Response.
 * @returns {Function}          Request handler, receiving an optional error and the read operation result.
 */
function endRead(req, res) {
    return function (error, result) {
        var body;

        if (error) {
            res.code = error.code;
            body = '';
        } else {
            res.code = '2.05';
            body = result.toString();
        }

        if (_.some(req.options, _.matches({name: 'Observe'}))) {
            res.setOption('Observe', 1);
            res.write(body);
        } else {
            res.end(body);
        }
    };
}

/**
 * Constructs the resource observer. The observer listens in the object registry bus for attribute modifications,
 * sending a new information update to the server each time the selected attribute changes its value.
 *
 * @param {Object} stream           Stream where the new data will be written to the server.
 * @param {Number} resourceId       Resource to read from.
 * @param {Object} storedObject     Object instance to observe.
 * @returns {{uri: (object.objectUri|*|string), stream: *, observation: number}}
 * @constructor
 */
function ResourceObserver(stream, resourceId, storedObject) {
    var data = {
        id: storedObject.objectUri + '/' + resourceId,
        uri: storedObject.objectUri,
        stream: stream,
        observation: 1,
        listener: function newModification(method, id, value) {
            if (method === 'setResource' && resourceId === id.toString()) {
                try {
                    stream.setOption('Observe', data.observation);
                    stream.write(value.toString());
                    data.observation++;
                    data.lastObservation = Date.now();
                } catch(error) {
                    logger.error('Tried to write to a closed stream. Cleaning the handler.');
                    clearInterval(data.scheduler);
                }
            }
        }
    };

    function getLastMeasure() {
        objectRegistry.get(storedObject.objectUri, function (error, obj) {
            if (error) {
                logger.error('Error retrieving the last resource value');
            } else {
                data.listener('setResource', resourceId, obj.attributes[resourceId]);
            }
        });
    }

    objectRegistry.getAttributes(storedObject.objectUri + '/' + resourceId, function(error, attributes) {
        if (error) {
            logger.error('Couldn\'t find attributes for the [%s] resource URI. Falling back to value changed');
        }

        if (attributes) {
            if (attributes.pmax) {
                data.scheduler = setInterval(function handleMaxPeriod() {
                    getLastMeasure(storedObject.objectUri, resourceId);
                }, attributes.pmax);
            }
        } else {
            objectRegistry.bus.on(storedObject.objectUri, data.listener);
        }
    });

    return data;
}

/**
 * If the Read request comes with the observation option, apart from resolving the information request, a subscription
 * has to be created, so every time the selected resource changes in value (or in a timely basis, depending on the
 * configuration), the server is updated on the new value.
 *
 * @param {Object} req              Arriving COAP Request to be handled.
 * @param {Object} res              Outgoing COAP Response.
 * @param {Number} resourceId       ID of the resource to be observed.
 * @param {Object} storedObject     Object instance to be read.
 */
function createObservers(req, res, resourceId, storedObject, callback) {
    if (_.some(req.options, _.matches({name: 'Observe'}))) {
        var observer = new ResourceObserver(res, resourceId, storedObject);
        observers[observer.id] = observer;
    }

    callback(null, storedObject);
}

function addAttribute(previous, value) {
    return previous + ';' + value[0] + '=' + value[1];
}

function discoverResourceAttributes(objectUri, resourceId, callback) {
    objectRegistry.getAttributes(objectUri + '/' + resourceId, function (error, attributes) {
        if (error) {
            callback(error);
        } else {
            var payload = '<' + objectUri + '/' + resourceId + '>';

            if (attributes) {
                payload = _.pairs(attributes).reduce(addAttribute, payload);
            }

            callback(null, payload);
        }
    });
}

function discoverObjectResources(objectUri, callback) {
    async.series([
        apply(objectRegistry.get, objectUri),
        apply(objectRegistry.getAttributes, objectUri)
    ], function handleGetObject(error, results) {
        var obj = results[0],
            attributes = results[1];

        if (error) {
            callback(error);
        } else {
            var payload = '<' + objectUri + '>',
                resources = _.keys(obj.attributes);

            if (attributes) {
                payload += _.pairs(attributes).reduce(addAttribute, '');
            }

            for (var i = 0; i < resources.length; i++) {
                payload += ',<' + objectUri + '/' + resources[i] + '>';
            }

            callback(null, payload);
        }
    });
}

function discoverObjectTypeInstances(objectTypeUri, callback) {
    async.series([
        objectRegistry.list,
        apply(objectRegistry.getAttributes, objectTypeUri)
    ], function handleListResult(error, results) {
        var objList = results[0],
            attributes = results[1];

        if (error) {
            callback(error);
        } else {
            var payload = '<' + objectTypeUri + '>',
                objectType = objectTypeUri.substring(1);

            if (attributes) {
                payload += _.pairs(attributes).reduce(addAttribute, '');
            }

            for (var i = 0; i < objList.length; i++) {
                if (objList[i].objectType === objectType) {
                    payload += ',<' + objList[i].objectUri + '>';
                }
            }

            callback(null, payload);
        }
    });
}

/**
 * Handle the read operation.
 *
 * @param {Object} req          Arriving COAP Request to be handled.
 * @param {Object} res          Outgoing COAP Response.
 * @param {Function} handler    User handler to be executed if everything goes ok.
 */
function handleRead(req, res, handler) {
    extractUriInfo(req, res, function (error, objectUri, resourceId) {
        if (error) {
            endRead(req, res)(error);
        } else {
            if (_.some(req.options, _.matches({name: 'Accept', value: 'application/link-format'}))) {
                if (resourceId) {
                    discoverResourceAttributes(objectUri, resourceId, endRead(req, res));
                } else if (objectUri.match(/\/\d+\/\d+/)) {
                    discoverObjectResources(objectUri, endRead(req, res));
                } else {
                    discoverObjectTypeInstances(objectUri, endRead(req, res));
                }
            } else {
                async.waterfall([
                    apply(objectRegistry.get, objectUri),
                    apply(createObservers, req, res, resourceId),
                    apply(applyHandler, resourceId, handler)
                ], endRead(req, res));
            }
        }
    });
}

function cancelObservation(objUri, resourceId, callback) {
    if (observers[objUri + '/' + resourceId]) {
        if (observers[objUri + '/' + resourceId].stream) {
            observers[objUri + '/' + resourceId].stream.end();
        }

        if (observers[objUri + '/' + resourceId]) {
            clearInterval(observers[objUri + '/' + resourceId].scheduler);
        }

        objectRegistry.bus.removeListener(objUri, observers[objUri + '/' + resourceId].listener);
        delete observers[objUri + '/' + resourceId];
    } else {
        logger.error('Tried to remove an unexistant observation on obj URI [%s] and resourceId [%s]',
            objUri, resourceId);
    }

    callback(null);
}

function listObservers(callback) {
    callback(null, _.values(observers));
}

function cancelAllObservers(callback) {
    var observers = _.values(observers),
        cancellations = [];

    for (var i = 0; i < observers.length; i++) {
        cancellations.push(apply(cancelObservation, observers[i].objUri, observers[i].resourceId));
    }

    async.series(cancellations, callback);
}

exports.handle = handleRead;
exports.cancel = cancelObservation;
exports.list = listObservers;
exports.cancelAll = cancelAllObservers;<commit_msg>ADD Log entries to improve debugging with the client<commit_after>/*
 * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-lwm2m-lib
 *
 * iotagent-lwm2m-lib is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * iotagent-lwm2m-lib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with iotagent-lwm2m-lib.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::[contacto@tid.es]
 */

'use strict';

var async = require('async'),
    apply = async.apply,
    objectRegistry = require('./objectRegistry'),
    errors = require('../../errors'),
    logger = require('logops'),
    _ = require('underscore'),
    observers = {};

/**
 * Extract Object type and id from the request URI, returning it using the callback.
 *
 * @param {Object} req          Arriving COAP Request to be handled.
 * @param {Object} res          Outgoing COAP Response.
 */
function extractUriInfo(req, res, callback) {
    if (req.urlObj.pathname.match(/\/\d+\/\d+\/\d+/)) {
        var resourceIndex = req.urlObj.pathname.lastIndexOf('/'),
            resourceId = req.urlObj.pathname.substring(resourceIndex + 1),
            objectUri = req.urlObj.pathname.substring(0, resourceIndex);

        callback(null, objectUri, resourceId);
    } else if (req.urlObj.pathname.match(/\/\d+(\/\d+)?/)) {
        callback(null, req.urlObj.pathname, null);
    } else {
        callback(new errors.WrongObjectUri(req.urlObj.pathname));
    }
}

/**
 * Invoke the user handler for this operation, with all the information from the query parameters as its arguments.
 * This method handling gives the client an opportunity to change or overwrite the values before returning them to
 * the client. The handler must call the callback with the overwritten value it wants to return. If the callback is
 * called without parameters, the original value is returned instead.
 *
 * @param {Object} queryParams      Object containing all the query parameters.
 * @param {Function} handler        User handler to be invoked.
 */
function applyHandler(resourceId, handler, storedObject, callback) {
    if (storedObject.attributes[resourceId]) {
        handler(
            storedObject.objectType,
            storedObject.objectId,
            resourceId,
            storedObject.attributes[resourceId],

            function handleReadOverwrite(error, result) {
                if (result) {
                    callback(error, result);
                } else {
                    callback(error, storedObject.attributes[resourceId]);
                }
            });
    } else {
        callback(new errors.ResourceNotFound(storedObject.objectType, storedObject.objectId, resourceId));
    }
}

/**
 * Generates the end of request handler that will generate the final response to the COAP Client.
 *
 * @param {Object} req          Arriving COAP Request to be handled.
 * @param {Object} res          Outgoing COAP Response.
 * @returns {Function}          Request handler, receiving an optional error and the read operation result.
 */
function endRead(req, res) {
    return function (error, result) {
        var body;

        if (error) {
            res.code = error.code;
            body = '';
        } else {
            res.code = '2.05';
            body = result.toString();
        }

        if (_.some(req.options, _.matches({name: 'Observe'}))) {
            res.setOption('Observe', 1);
            res.write(body);
        } else {
            res.end(body);
        }
    };
}

/**
 * Constructs the resource observer. The observer listens in the object registry bus for attribute modifications,
 * sending a new information update to the server each time the selected attribute changes its value.
 *
 * @param {Object} stream           Stream where the new data will be written to the server.
 * @param {Number} resourceId       Resource to read from.
 * @param {Object} storedObject     Object instance to observe.
 * @returns {{uri: (object.objectUri|*|string), stream: *, observation: number}}
 * @constructor
 */
function ResourceObserver(stream, resourceId, storedObject) {
    var data = {
        id: storedObject.objectUri + '/' + resourceId,
        uri: storedObject.objectUri,
        stream: stream,
        observation: 1,
        listener: function newModification(method, id, value) {
            if (method === 'setResource' && resourceId === id.toString()) {
                logger.debug('Sending data update to the server for object [%s]', data.objectUri);

                try {
                    stream.setOption('Observe', data.observation);
                    stream.write(value.toString());
                    data.observation++;
                    data.lastObservation = Date.now();
                } catch(error) {
                    logger.error('Tried to write to a closed stream. Cleaning the handler.');
                    clearInterval(data.scheduler);
                }
            }
        }
    };

    function getLastMeasure() {
        objectRegistry.get(storedObject.objectUri, function (error, obj) {
            if (error) {
                logger.error('Error retrieving the last resource value');
            } else {
                data.listener('setResource', resourceId, obj.attributes[resourceId]);
            }
        });
    }

    objectRegistry.getAttributes(storedObject.objectUri + '/' + resourceId, function(error, attributes) {
        if (error) {
            logger.error('Couldn\'t find attributes for the [%s] resource URI. Falling back to value changed');
        }

        if (attributes) {
            if (attributes.pmax) {
                logger.debug('Creating a scheduled observation for [%d] miliseconds for object [%s]',
                    attributes.pmax, storedObject.objectUri);

                data.scheduler = setInterval(function handleMaxPeriod() {
                    getLastMeasure(storedObject.objectUri, resourceId);
                }, attributes.pmax);
            }
        } else {
            logger.debug('Creating an onchange subscription for object [%s]', storedObject.objectUri);
            objectRegistry.bus.on(storedObject.objectUri, data.listener);
        }
    });

    return data;
}

/**
 * If the Read request comes with the observation option, apart from resolving the information request, a subscription
 * has to be created, so every time the selected resource changes in value (or in a timely basis, depending on the
 * configuration), the server is updated on the new value.
 *
 * @param {Object} req              Arriving COAP Request to be handled.
 * @param {Object} res              Outgoing COAP Response.
 * @param {Number} resourceId       ID of the resource to be observed.
 * @param {Object} storedObject     Object instance to be read.
 */
function createObservers(req, res, resourceId, storedObject, callback) {
    if (_.some(req.options, _.matches({name: 'Observe'}))) {
        var observer = new ResourceObserver(res, resourceId, storedObject);
        observers[observer.id] = observer;
    }

    callback(null, storedObject);
}

function addAttribute(previous, value) {
    return previous + ';' + value[0] + '=' + value[1];
}

function discoverResourceAttributes(objectUri, resourceId, callback) {
    objectRegistry.getAttributes(objectUri + '/' + resourceId, function (error, attributes) {
        if (error) {
            callback(error);
        } else {
            var payload = '<' + objectUri + '/' + resourceId + '>';

            if (attributes) {
                payload = _.pairs(attributes).reduce(addAttribute, payload);
            }

            callback(null, payload);
        }
    });
}

function discoverObjectResources(objectUri, callback) {
    async.series([
        apply(objectRegistry.get, objectUri),
        apply(objectRegistry.getAttributes, objectUri)
    ], function handleGetObject(error, results) {
        var obj = results[0],
            attributes = results[1];

        if (error) {
            callback(error);
        } else {
            var payload = '<' + objectUri + '>',
                resources = _.keys(obj.attributes);

            if (attributes) {
                payload += _.pairs(attributes).reduce(addAttribute, '');
            }

            for (var i = 0; i < resources.length; i++) {
                payload += ',<' + objectUri + '/' + resources[i] + '>';
            }

            callback(null, payload);
        }
    });
}

function discoverObjectTypeInstances(objectTypeUri, callback) {
    async.series([
        objectRegistry.list,
        apply(objectRegistry.getAttributes, objectTypeUri)
    ], function handleListResult(error, results) {
        var objList = results[0],
            attributes = results[1];

        if (error) {
            callback(error);
        } else {
            var payload = '<' + objectTypeUri + '>',
                objectType = objectTypeUri.substring(1);

            if (attributes) {
                payload += _.pairs(attributes).reduce(addAttribute, '');
            }

            for (var i = 0; i < objList.length; i++) {
                if (objList[i].objectType === objectType) {
                    payload += ',<' + objList[i].objectUri + '>';
                }
            }

            callback(null, payload);
        }
    });
}

/**
 * Handle the read operation.
 *
 * @param {Object} req          Arriving COAP Request to be handled.
 * @param {Object} res          Outgoing COAP Response.
 * @param {Function} handler    User handler to be executed if everything goes ok.
 */
function handleRead(req, res, handler) {
    logger.debug('Handling incoming read request');

    extractUriInfo(req, res, function (error, objectUri, resourceId) {
        if (error) {
            endRead(req, res)(error);
        } else {
            if (_.some(req.options, _.matches({name: 'Accept', value: 'application/link-format'}))) {
                logger.debug('Retrieving simple response with static information for object: %s', objectUri);
                if (resourceId) {
                    discoverResourceAttributes(objectUri, resourceId, endRead(req, res));
                } else if (objectUri.match(/\/\d+\/\d+/)) {
                    discoverObjectResources(objectUri, endRead(req, res));
                } else {
                    discoverObjectTypeInstances(objectUri, endRead(req, res));
                }
            } else {
                logger.debug('Creating observers for object URI: %s', objectUri);
                async.waterfall([
                    apply(objectRegistry.get, objectUri),
                    apply(createObservers, req, res, resourceId),
                    apply(applyHandler, resourceId, handler)
                ], endRead(req, res));
            }
        }
    });
}

function cancelObservation(objUri, resourceId, callback) {
    if (observers[objUri + '/' + resourceId]) {
        if (observers[objUri + '/' + resourceId].stream) {
            observers[objUri + '/' + resourceId].stream.end();
        }

        if (observers[objUri + '/' + resourceId]) {
            clearInterval(observers[objUri + '/' + resourceId].scheduler);
        }

        objectRegistry.bus.removeListener(objUri, observers[objUri + '/' + resourceId].listener);
        delete observers[objUri + '/' + resourceId];
    } else {
        logger.error('Tried to remove an unexistant observation on obj URI [%s] and resourceId [%s]',
            objUri, resourceId);
    }

    callback(null);
}

function listObservers(callback) {
    callback(null, _.values(observers));
}

function cancelAllObservers(callback) {
    var observers = _.values(observers),
        cancellations = [];

    for (var i = 0; i < observers.length; i++) {
        cancellations.push(apply(cancelObservation, observers[i].objUri, observers[i].resourceId));
    }

    async.series(cancellations, callback);
}

exports.handle = handleRead;
exports.cancel = cancelObservation;
exports.list = listObservers;
exports.cancelAll = cancelAllObservers;<|endoftext|>


<commit_before>const { URL } = require('url');

const { InvalidClientMetadata } = require('./errors');

module.exports = ({ sectorIdentifierUri, redirectUris, subjectType }) => {
  if (subjectType === 'pairwise' && !sectorIdentifierUri) {
    const hosts = redirectUris
      .map(uri => new URL(uri).host)
      .filter((value, index, self) => self.indexOf(value) === index);

    if (hosts.length !== 1) {
      throw new InvalidClientMetadata('sector_identifier_uri is required when using multiple hosts in your redirect_uris');
    }

    return hosts[0];
  }

  if (sectorIdentifierUri) {
    return new URL(sectorIdentifierUri).host;
  }

  return undefined;
};
<commit_msg>refactor: lib/helpers/sector_identifier.js<commit_after>const { URL } = require('url');

const { InvalidClientMetadata } = require('./errors');

module.exports = ({ sectorIdentifierUri, redirectUris, subjectType }) => {
  if (subjectType === 'pairwise' && !sectorIdentifierUri) {
    const { 0: host, length } = redirectUris
      .map(uri => new URL(uri).host)
      .filter((value, index, self) => self.indexOf(value) === index);

    if (length !== 1) {
      throw new InvalidClientMetadata('sector_identifier_uri is required when using multiple hosts in your redirect_uris');
    }

    return host;
  }

  if (sectorIdentifierUri) {
    return new URL(sectorIdentifierUri).host;
  }

  return undefined;
};
<|endoftext|>


<commit_before>// @flow
import {getBuildingHours} from './get-building-hours'
import type momentT from 'moment'
import type {BuildingStatusType, BuildingInfoType} from './types'

export function isBuildingOpen(hoursInfo: BuildingInfoType, now: momentT): BuildingStatusType {
  let hours = getBuildingHours(hoursInfo, now)
  if (!hours) {
    return 'Closed'
  }

  let {open, close} = hours

  if (now.isBetween(open, close)) {
    if (now.clone().add(30, 'min').isAfter(close)) {
      return 'Almost Closed'
    }
    return 'Open'
  }
  return 'Closed'
}
<commit_msg>Bugfix: Allow isBuildingOpen to return 'Almost Closed'<commit_after>// @flow
import {getBuildingHours} from './get-building-hours'
import type momentT from 'moment'
import type {BuildingStatusType, BuildingInfoType} from './types'

export function isBuildingOpen(hoursInfo: BuildingInfoType, now: momentT): BuildingStatusType {
  let hours = getBuildingHours(hoursInfo, now)
  if (!hours) {
    return 'Closed'
  }

  let {open, close} = hours

  if (now.isBetween(open, close)) {
    if (now.clone().add(30, 'minutes').isAfter(close)) {
      return 'Almost Closed'
    }
    return 'Open'
  }
  return 'Closed'
}
<|endoftext|>


<commit_before>'use strict';

const TelegramBot = require('node-telegram-bot-api'),
      rp = require('request-promise'),
      Redis = require('ioredis'),
      config = require('./config.json'),
      npm = require('./package.json'),
      raven = require('raven');

if (process.env.SENTRY_DSN) {
    const sentry = new raven.Client(process.env.SENTRY_DSN);
    sentry.patchGlobal();
}

if (process.env.TELEGRAM_TOKEN) config.token = process.env.TELEGRAM_TOKEN;

const request = rp.defaults({headers: {
    'User-Agent': `Teleboyarin/${npm.version}`,
    Accept: 'application/json'
}});

const redis = process.env.REDIS_PORT ? new Redis(process.env.REDIS_PORT) : (config.redis ? new Redis(config.redis) : new Redis());

var workers = {};

function getWorker(process, userId) {
    if (!(process.id in workers)) workers[process.id] = {};
    if (userId in workers[process.id]) return new Promise((resolve, reject) => resolve(workers[process.id][userId]));
    const workerGetReq = `${config.apiURL}/processes/${process.id}/workers/tagged/telegram${userId}`,
          workerPostReq = `${config.apiURL}/processes/${process.id}/workers`;
    return new Promise((resolve, reject) =>
        request.get(workerGetReq).
            then((worker) => resolve(workers[process.id][userId] = JSON.parse(worker))).
            catch((err) => err.statusCode !== 404 ? reject(err) : request.post(workerPostReq, {form: {tags: `telegram${userId}`}})).
                then((worker) => resolve(workers[process.id][userId] = JSON.parse(worker))).
                catch((err) => reject(err))
    );
}

const bot = new TelegramBot(config.token, {
    polling: true
});

bot.on('text', onText);

function onText(msg) {
    const text = msg.text.trim();

    if (!!config.disabled) {
        bot.sendMessage(msg.chat.id, 'I am relaxing.');
        return;
    }

    redis.get(msg.from.id).then((raw) => {
        const state = raw ? JSON.parse(raw) : undefined;

        if (text == '/cancel') {
            const reply = state ? `Cancelling the ${state.text} operation.` : 'Cancelling nothing.';
            const markup = JSON.stringify({hide_keyboard: true});
            redis.del(msg.from.id).then(() => bot.sendMessage(msg.chat.id, reply, {reply_markup: markup}));
        } else {
            switch (!!state ? state.text : undefined) {
                case '/annotate':        stateAnnotate(text, msg, state);       break;
                case '/annotate/answer': stateAnnotateAnswer(text, msg, state); break;
                case '/process':         stateProcess(text, msg, state);        break;
                default:                 stateInitial(text, msg, state);        break;
            }
        }
    });
}

function stateInitial(text, msg, state) {
    switch (text) {
        case '/start':
            bot.sendMessage(msg.chat.id, 'Hi, ' + msg.from.first_name + '!');
        break;
        case '/version':
            request.get(`${config.apiURL}/version`).then((body) => {
                const reply = `Mechanical Tsar v${body}`;
                bot.sendMessage(msg.chat.id, reply);
            });
        break;
        case '/processes':
            request.get(`${config.apiURL}/processes`).then((body) => {
                const processes = JSON.parse(body);
                const reply = processes.map((process) => `*${process.id}*: ${process.description}`).join("\n");
                bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown'});
            });
        break;
        case '/process':
        case '/annotate':
            request.get(`${config.apiURL}/processes`).then((body) => {
                const processes = JSON.parse(body);
                const reply = 'Which one?';
                const markup = JSON.stringify({
                      keyboard: processes.map((process) => [process.id]),
                      one_time_keyboard: true
                });
                bot.sendMessage(msg.chat.id, reply, {reply_markup: markup}).then(() => {
                    redis.set(msg.from.id, JSON.stringify({text: text, processes: processes}));
                });
            });
        break;
        default:
        break;
    }
}

function stateProcess(text, msg, state) {
    const processReq = request.get(`${config.apiURL}/processes/${text}`),
          workersReq = request.get(`${config.apiURL}/processes/${text}/workers`),
          tasksReq   = request.get(`${config.apiURL}/processes/${text}/tasks`),
          answersReq = request.get(`${config.apiURL}/processes/${text}/answers`);
    Promise.all([processReq, workersReq, tasksReq, answersReq]).then((responses) => {
        const process = JSON.parse(responses[0]),
              workers = JSON.parse(responses[1]).length,
              tasks   = JSON.parse(responses[2]).length,
              answers = JSON.parse(responses[3]).length;
        const reply = `[${process.description}](${processReq.uri.href})\n*Workers:* ${workers}.\n*Tasks:* ${tasks}.\n*Answers:* ${answers}.`;
        const markup = JSON.stringify({hide_keyboard: true});
        redis.del(msg.from.id).then(() => bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown', reply_markup: markup}));
    });
}

function stateAnnotate(text, msg, state) {
    const process = state.processes.find((p) => p.id == text);
    if (process) {
        getWorker(process, msg.from.id).then((worker) => {
            request.get(`${config.apiURL}/processes/${process.id}/workers/${worker.id}/task`).then((response) => {
                if (!response || 0 === response.length) {
                    const reply = 'Thank you, but this process has already been finished.';
                    const markup = JSON.stringify({hide_keyboard: true});
                    redis.del(msg.from.id).then(() => bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown', reply_markup: markup}));
                } else {
                    const allocation = JSON.parse(response);
                    const task = allocation.tasks[0];
                    const reply = task.description;
                    const markup = JSON.stringify({
                          keyboard: task.answers.map((answer) => [answer]).concat([['/stop']]),
                          one_time_keyboard: true
                    });
                    bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown', reply_markup: markup}).then(() => {
                        redis.set(msg.from.id, JSON.stringify({text: '/annotate/answer', process: process, worker: worker, task: task}));
                    });
                }
            });
        });
    }
}

function stateAnnotateAnswer(text, msg, state) {
    switch (text) {
        case '/stop': {
            const reply = 'Thank you for your service!';
            const markup = JSON.stringify({hide_keyboard: true});
            redis.del(msg.from.id).then(() => bot.sendMessage(msg.chat.id, reply, {reply_markup: markup}));
        }
        break;
        default:
            if (state.task.answers.indexOf(text) > -1) {
                const process = state.process, worker = state.worker, task = state.task;
                const answers = {}; answers[`answers[${task.id}]`] = text;
                request.patch(`${config.apiURL}/processes/${process.id}/workers/${worker.id}/answers`, {form: answers}).then((response) => {
                    const reply = 'Your answer has been recorded!';
                    const markup = JSON.stringify({hide_keyboard: true});
                    bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown', reply_markup: markup}).then(() => {
                        const stateNew = {text: process.id, processes: [process]};
                        redis.set(msg.from.id, JSON.stringify(stateNew)).then(() => {
                            stateAnnotate(stateNew.text, msg, stateNew);
                        });
                    });
                });
            }
        break;
    }
}
<commit_msg>Thank you for your help!<commit_after>'use strict';

const TelegramBot = require('node-telegram-bot-api'),
      rp = require('request-promise'),
      Redis = require('ioredis'),
      config = require('./config.json'),
      npm = require('./package.json'),
      raven = require('raven');

if (process.env.SENTRY_DSN) {
    const sentry = new raven.Client(process.env.SENTRY_DSN);
    sentry.patchGlobal();
}

if (process.env.TELEGRAM_TOKEN) config.token = process.env.TELEGRAM_TOKEN;

const request = rp.defaults({headers: {
    'User-Agent': `Teleboyarin/${npm.version}`,
    Accept: 'application/json'
}});

const redis = process.env.REDIS_PORT ? new Redis(process.env.REDIS_PORT) : (config.redis ? new Redis(config.redis) : new Redis());

var workers = {};

function getWorker(process, userId) {
    if (!(process.id in workers)) workers[process.id] = {};
    if (userId in workers[process.id]) return new Promise((resolve, reject) => resolve(workers[process.id][userId]));
    const workerGetReq = `${config.apiURL}/processes/${process.id}/workers/tagged/telegram${userId}`,
          workerPostReq = `${config.apiURL}/processes/${process.id}/workers`;
    return new Promise((resolve, reject) =>
        request.get(workerGetReq).
            then((worker) => resolve(workers[process.id][userId] = JSON.parse(worker))).
            catch((err) => err.statusCode !== 404 ? reject(err) : request.post(workerPostReq, {form: {tags: `telegram${userId}`}})).
                then((worker) => resolve(workers[process.id][userId] = JSON.parse(worker))).
                catch((err) => reject(err))
    );
}

const bot = new TelegramBot(config.token, {
    polling: true
});

bot.on('text', onText);

function onText(msg) {
    const text = msg.text.trim();

    if (!!config.disabled) {
        bot.sendMessage(msg.chat.id, 'I am relaxing.');
        return;
    }

    redis.get(msg.from.id).then((raw) => {
        const state = raw ? JSON.parse(raw) : undefined;

        if (text == '/cancel') {
            const reply = state ? `Cancelling the ${state.text} operation.` : 'Cancelling nothing.';
            const markup = JSON.stringify({hide_keyboard: true});
            redis.del(msg.from.id).then(() => bot.sendMessage(msg.chat.id, reply, {reply_markup: markup}));
        } else {
            switch (!!state ? state.text : undefined) {
                case '/annotate':        stateAnnotate(text, msg, state);       break;
                case '/annotate/answer': stateAnnotateAnswer(text, msg, state); break;
                case '/process':         stateProcess(text, msg, state);        break;
                default:                 stateInitial(text, msg, state);        break;
            }
        }
    });
}

function stateInitial(text, msg, state) {
    switch (text) {
        case '/start':
            bot.sendMessage(msg.chat.id, 'Hi, ' + msg.from.first_name + '!');
        break;
        case '/version':
            request.get(`${config.apiURL}/version`).then((body) => {
                const reply = `Mechanical Tsar v${body}`;
                bot.sendMessage(msg.chat.id, reply);
            });
        break;
        case '/processes':
            request.get(`${config.apiURL}/processes`).then((body) => {
                const processes = JSON.parse(body);
                const reply = processes.map((process) => `*${process.id}*: ${process.description}`).join("\n");
                bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown'});
            });
        break;
        case '/process':
        case '/annotate':
            request.get(`${config.apiURL}/processes`).then((body) => {
                const processes = JSON.parse(body);
                const reply = 'Which one?';
                const markup = JSON.stringify({
                      keyboard: processes.map((process) => [process.id]),
                      one_time_keyboard: true
                });
                bot.sendMessage(msg.chat.id, reply, {reply_markup: markup}).then(() => {
                    redis.set(msg.from.id, JSON.stringify({text: text, processes: processes}));
                });
            });
        break;
        default:
        break;
    }
}

function stateProcess(text, msg, state) {
    const processReq = request.get(`${config.apiURL}/processes/${text}`),
          workersReq = request.get(`${config.apiURL}/processes/${text}/workers`),
          tasksReq   = request.get(`${config.apiURL}/processes/${text}/tasks`),
          answersReq = request.get(`${config.apiURL}/processes/${text}/answers`);
    Promise.all([processReq, workersReq, tasksReq, answersReq]).then((responses) => {
        const process = JSON.parse(responses[0]),
              workers = JSON.parse(responses[1]).length,
              tasks   = JSON.parse(responses[2]).length,
              answers = JSON.parse(responses[3]).length;
        const reply = `[${process.description}](${processReq.uri.href})\n*Workers:* ${workers}.\n*Tasks:* ${tasks}.\n*Answers:* ${answers}.`;
        const markup = JSON.stringify({hide_keyboard: true});
        redis.del(msg.from.id).then(() => bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown', reply_markup: markup}));
    });
}

function stateAnnotate(text, msg, state) {
    const process = state.processes.find((p) => p.id == text);
    if (process) {
        getWorker(process, msg.from.id).then((worker) => {
            request.get(`${config.apiURL}/processes/${process.id}/workers/${worker.id}/task`).then((response) => {
                if (!response || 0 === response.length) {
                    const reply = 'Thank you, but this process has already been finished.';
                    const markup = JSON.stringify({hide_keyboard: true});
                    redis.del(msg.from.id).then(() => bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown', reply_markup: markup}));
                } else {
                    const allocation = JSON.parse(response);
                    const task = allocation.tasks[0];
                    const reply = task.description;
                    const markup = JSON.stringify({
                          keyboard: task.answers.map((answer) => [answer]).concat([['/stop']]),
                          one_time_keyboard: true
                    });
                    bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown', reply_markup: markup}).then(() => {
                        redis.set(msg.from.id, JSON.stringify({text: '/annotate/answer', process: process, worker: worker, task: task}));
                    });
                }
            });
        });
    }
}

function stateAnnotateAnswer(text, msg, state) {
    switch (text) {
        case '/stop': {
            const reply = 'Thank you for your help!';
            const markup = JSON.stringify({hide_keyboard: true});
            redis.del(msg.from.id).then(() => bot.sendMessage(msg.chat.id, reply, {reply_markup: markup}));
        }
        break;
        default:
            if (state.task.answers.indexOf(text) > -1) {
                const process = state.process, worker = state.worker, task = state.task;
                const answers = {}; answers[`answers[${task.id}]`] = text;
                request.patch(`${config.apiURL}/processes/${process.id}/workers/${worker.id}/answers`, {form: answers}).then((response) => {
                    const reply = 'Your answer has been recorded!';
                    const markup = JSON.stringify({hide_keyboard: true});
                    bot.sendMessage(msg.chat.id, reply, {parse_mode: 'Markdown', reply_markup: markup}).then(() => {
                        const stateNew = {text: process.id, processes: [process]};
                        redis.set(msg.from.id, JSON.stringify(stateNew)).then(() => {
                            stateAnnotate(stateNew.text, msg, stateNew);
                        });
                    });
                });
            }
        break;
    }
}
<|endoftext|>


<commit_before>import React, { useRef, useState, useEffect, forwardRef } from 'react';
import { getParams } from './get-params';
import { initSwiper, mountSwiper } from './init-swiper';
import { needsScrollbar, needsNavigation, needsPagination, uniqueClasses, extend } from './utils';
import { renderLoop, calcLoopedSlides } from './loop';
import { getChangedParams } from './get-changed-params';
import { getChildren } from './get-children';
import { updateSwiper } from './update-swiper';
import { renderVirtual, updateOnVirtualData } from './virtual';
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';

const Swiper = forwardRef(
  (
    {
      className,
      tag: Tag = 'div',
      wrapperTag: WrapperTag = 'div',
      children,
      onSwiper,
      ...rest
    } = {},
    externalElRef,
  ) => {
    const [containerClasses, setContainerClasses] = useState('swiper-container');
    const [virtualData, setVirtualData] = useState(null);
    const [breakpointChanged, setBreakpointChanged] = useState(false);
    const initializedRef = useRef(false);
    const swiperElRef = useRef(null);
    const swiperRef = useRef(null);
    const oldPassedParamsRef = useRef(null);
    const oldSlides = useRef(null);

    const nextElRef = useRef(null);
    const prevElRef = useRef(null);
    const paginationElRef = useRef(null);
    const scrollbarElRef = useRef(null);

    const { params: swiperParams, passedParams, rest: restProps } = getParams(rest);

    const { slides, slots } = getChildren(children);

    const changedParams = getChangedParams(
      passedParams,
      oldPassedParamsRef.current,
      slides,
      oldSlides.current,
    );

    oldPassedParamsRef.current = passedParams;
    oldSlides.current = slides;

    const onBeforeBreakpoint = () => {
      setBreakpointChanged(!breakpointChanged);
    };

    Object.assign(swiperParams.on, {
      _containerClasses(swiper, classes) {
        setContainerClasses(classes);
      },
    });

    if (!swiperElRef.current) {
      // init swiper
      swiperRef.current = initSwiper(swiperParams);
      swiperRef.current.loopCreate = () => {};
      swiperRef.current.loopDestroy = () => {};
      if (swiperParams.loop) {
        swiperRef.current.loopedSlides = calcLoopedSlides(slides, swiperParams);
      }
      if (swiperRef.current.virtual && swiperRef.current.params.virtual.enabled) {
        swiperRef.current.virtual.slides = slides;
        const extendWith = {
          cache: false,
          renderExternal: setVirtualData,
          renderExternalUpdate: false,
        };
        extend(swiperRef.current.params.virtual, extendWith);
        extend(swiperRef.current.originalParams.virtual, extendWith);
      }
    }

    // Listen for breakpoints change
    if (swiperRef.current) {
      swiperRef.current.on('_beforeBreakpoint', onBeforeBreakpoint);
    }
    useEffect(() => {
      return () => {
        if (swiperRef.current) swiperRef.current.off('_beforeBreakpoint', onBeforeBreakpoint);
      };
    });

    // set initialized flag
    useEffect(() => {
      if (!initializedRef.current && swiperRef.current) {
        swiperRef.current.emitSlidesClasses();
        initializedRef.current = true;
      }
    });

    // watch for params change
    useIsomorphicLayoutEffect(() => {
      if (changedParams.length && swiperRef.current && !swiperRef.current.destroyed) {
        updateSwiper(swiperRef.current, slides, passedParams, changedParams);
      }
    });

    // update on virtual update
    useIsomorphicLayoutEffect(() => {
      updateOnVirtualData(swiperRef.current);
    }, [virtualData]);

    // mount swiper
    useIsomorphicLayoutEffect(() => {
      if (externalElRef) {
        externalElRef.current = swiperElRef.current;
      }
      if (!swiperElRef.current) return;

      mountSwiper(
        {
          el: swiperElRef.current,
          nextEl: nextElRef.current,
          prevEl: prevElRef.current,
          paginationEl: paginationElRef.current,
          scrollbarEl: scrollbarElRef.current,
          swiper: swiperRef.current,
        },
        swiperParams,
      );

      if (onSwiper) onSwiper(swiperRef.current);
      // eslint-disable-next-line
      return () => {
        if (swiperRef.current && !swiperRef.current.destroyed) {
          swiperRef.current.destroy(true, false);
        }
      };
    }, []);

    // bypass swiper instance to slides
    function renderSlides() {
      if (swiperParams.virtual) {
        return renderVirtual(swiperRef.current, slides, virtualData);
      }
      if (!swiperParams.loop || (swiperRef.current && swiperRef.current.destroyed)) {
        return slides.map((child) => {
          return React.cloneElement(child, { swiper: swiperRef.current });
        });
      }
      return renderLoop(swiperRef.current, slides, swiperParams);
    }

    return (
      <Tag
        ref={swiperElRef}
        className={uniqueClasses(`${containerClasses}${className ? ` ${className}` : ''}`)}
        {...restProps}
      >
        {slots['container-start']}
        {needsNavigation(swiperParams) && (
          <>

          </>
        )}
        {needsScrollbar(swiperParams) &&
}
        {needsPagination(swiperParams) && (

        )}
        <WrapperTag className="swiper-wrapper">
          {slots['wrapper-start']}
          {renderSlides()}
          {slots['wrapper-end']}
        </WrapperTag>
        {slots['container-end']}
      </Tag>
    );
  },
);

Swiper.displayName = 'Swiper';

export { Swiper };
<commit_msg>fix(react): fixed issut with Virtual Slides not working correctly<commit_after>import React, { useRef, useState, useEffect, forwardRef } from 'react';
import { getParams } from './get-params';
import { initSwiper, mountSwiper } from './init-swiper';
import { needsScrollbar, needsNavigation, needsPagination, uniqueClasses, extend } from './utils';
import { renderLoop, calcLoopedSlides } from './loop';
import { getChangedParams } from './get-changed-params';
import { getChildren } from './get-children';
import { updateSwiper } from './update-swiper';
import { renderVirtual, updateOnVirtualData } from './virtual';
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';

const Swiper = forwardRef(
  (
    {
      className,
      tag: Tag = 'div',
      wrapperTag: WrapperTag = 'div',
      children,
      onSwiper,
      ...rest
    } = {},
    externalElRef,
  ) => {
    const [containerClasses, setContainerClasses] = useState('swiper-container');
    const [virtualData, setVirtualData] = useState(null);
    const [breakpointChanged, setBreakpointChanged] = useState(false);
    const initializedRef = useRef(false);
    const swiperElRef = useRef(null);
    const swiperRef = useRef(null);
    const oldPassedParamsRef = useRef(null);
    const oldSlides = useRef(null);

    const nextElRef = useRef(null);
    const prevElRef = useRef(null);
    const paginationElRef = useRef(null);
    const scrollbarElRef = useRef(null);

    const { params: swiperParams, passedParams, rest: restProps } = getParams(rest);

    const { slides, slots } = getChildren(children);

    const changedParams = getChangedParams(
      passedParams,
      oldPassedParamsRef.current,
      slides,
      oldSlides.current,
    );

    oldPassedParamsRef.current = passedParams;
    oldSlides.current = slides;

    const onBeforeBreakpoint = () => {
      setBreakpointChanged(!breakpointChanged);
    };

    Object.assign(swiperParams.on, {
      _containerClasses(swiper, classes) {
        setContainerClasses(classes);
      },
    });

    if (!swiperElRef.current) {
      // init swiper
      swiperRef.current = initSwiper(swiperParams);
      swiperRef.current.loopCreate = () => {};
      swiperRef.current.loopDestroy = () => {};
      if (swiperParams.loop) {
        swiperRef.current.loopedSlides = calcLoopedSlides(slides, swiperParams);
      }
      if (swiperRef.current.virtual && swiperRef.current.params.virtual.enabled) {
        swiperRef.current.virtual.slides = slides;
        const extendWith = {
          cache: false,
          renderExternal: setVirtualData,
          renderExternalUpdate: false,
        };
        extend(swiperRef.current.params.virtual, extendWith);
        extend(swiperRef.current.originalParams.virtual, extendWith);
      }
    }

    // Listen for breakpoints change
    if (swiperRef.current) {
      swiperRef.current.on('_beforeBreakpoint', onBeforeBreakpoint);
    }
    useEffect(() => {
      return () => {
        if (swiperRef.current) swiperRef.current.off('_beforeBreakpoint', onBeforeBreakpoint);
      };
    });

    // set initialized flag
    useEffect(() => {
      if (!initializedRef.current && swiperRef.current) {
        swiperRef.current.emitSlidesClasses();
        initializedRef.current = true;
      }
    });

    // mount swiper
    useIsomorphicLayoutEffect(() => {
      if (externalElRef) {
        externalElRef.current = swiperElRef.current;
      }
      if (!swiperElRef.current) return;

      mountSwiper(
        {
          el: swiperElRef.current,
          nextEl: nextElRef.current,
          prevEl: prevElRef.current,
          paginationEl: paginationElRef.current,
          scrollbarEl: scrollbarElRef.current,
          swiper: swiperRef.current,
        },
        swiperParams,
      );

      if (onSwiper) onSwiper(swiperRef.current);
      // eslint-disable-next-line
      return () => {
        if (swiperRef.current && !swiperRef.current.destroyed) {
          swiperRef.current.destroy(true, false);
        }
      };
    }, []);

    // watch for params change
    useIsomorphicLayoutEffect(() => {
      if (changedParams.length && swiperRef.current && !swiperRef.current.destroyed) {
        updateSwiper(swiperRef.current, slides, passedParams, changedParams);
      }
    });

    // update on virtual update
    useIsomorphicLayoutEffect(() => {
      updateOnVirtualData(swiperRef.current);
    }, [virtualData]);

    // bypass swiper instance to slides
    function renderSlides() {
      if (swiperParams.virtual) {
        return renderVirtual(swiperRef.current, slides, virtualData);
      }
      if (!swiperParams.loop || (swiperRef.current && swiperRef.current.destroyed)) {
        return slides.map((child) => {
          return React.cloneElement(child, { swiper: swiperRef.current });
        });
      }
      return renderLoop(swiperRef.current, slides, swiperParams);
    }

    return (
      <Tag
        ref={swiperElRef}
        className={uniqueClasses(`${containerClasses}${className ? ` ${className}` : ''}`)}
        {...restProps}
      >
        {slots['container-start']}
        {needsNavigation(swiperParams) && (
          <>

          </>
        )}
        {needsScrollbar(swiperParams) &&
}
        {needsPagination(swiperParams) && (

        )}
        <WrapperTag className="swiper-wrapper">
          {slots['wrapper-start']}
          {renderSlides()}
          {slots['wrapper-end']}
        </WrapperTag>
        {slots['container-end']}
      </Tag>
    );
  },
);

Swiper.displayName = 'Swiper';

export { Swiper };
<|endoftext|>


<commit_before>import {
	Template
} from 'meteor/templating';
import {
	Features,
	Currencies,
	GraphData,
	Redflags,
	LocalCurrencies,
} from '/imports/api/indexDB.js'

import Cookies from 'js-cookie'
import typeahead from 'corejs-typeahead' //maintained typeahead

import '/imports/ui/stylesheets/typeahead.css'

import {
	FlowRouter
} from 'meteor/staringatlights:flow-router'

import './compareCurrencies.css'
import './compareCurrencies.html'

Template.compareCurrencies.onCreated(function () {
	this.autorun(() => {
		SubsCache.subscribe('graphdata')
		SubsCache.subscribe('approvedcurrencies')
		SubsCache.subscribe('features')
		SubsCache.subscribe('redflags')
	})

	let currencies = FlowRouter.getParam('currencies') && FlowRouter.getParam('currencies').split('-') || []

	this.compared = new ReactiveVar(currencies)
	this.colors = new ReactiveDict()

	currencies.forEach(i => {
		let color = '#' + (Math.random() * 0xFFFFFF << 0).toString(16)
		let rgb = parseInt(color.substring(1), 16)

		this.colors.set(i, color)
	})

	//logic for receiving benefits of fast-render and yet using nonreactive data from method
	if (!LocalCurrencies.find().count()) {
		this.TransitoryCollection = Currencies
		// this.transitioning = new ReactiveVar(true)
		Meteor.call('fetchCurrencies', (err, res) => {
			res.forEach(x => {
				LocalCurrencies.insert(x)
			})
			this.TransitoryCollection = LocalCurrencies
		})
	} else {
		this.TransitoryCollection = LocalCurrencies
		// this.transitioning = new ReactiveVar(false)
	}
})

Template.compareCurrencies.onRendered(function () {
	const radar = document.getElementById('radar').getContext('2d')
	radar.canvas.width = 800
	radar.canvas.height = 600

	this.radarchart = new Chart(radar, {
		type: 'radar',
		data: {
			labels: ['Ongoing Development', 'Code Quality', 'Community', 'Hash Power', 'Settlement Speed', 'Ease of Use', 'Coin Distribution', 'Transactions', 'Decentralization'],
			datasets: [{
				label: '2',
				fill: false,
				backgroundColor: '#fff',
				borderColor: '#ccc',
				pointBorderColor: '#fff',
				borderWidth: 4,
				pointRadius: 0,
				pointBackgroundColor: '#fff',
				data: [10, 10, 10, 10, 10, 10, 10, 10, 10]
			}, {
				label: '3',
				fill: false,
				backgroundColor: '#fff',
				borderColor: '#fff',
				borderWidth: 1,
				pointBorderColor: '#fff',
				pointBackgroundColor: '#fff',
				data: [0, 0, 0, 0, 0, 0, 0, 0, 0]
			}]
		},
		options: {
			responsive: false,
			defaultFontColor: 'red',
			tooltips: {
				enabled: false
			},
			maintainAspectRatio: false,
			title: {
				display: false
			},
			legend: {
				display: false,
				position: 'bottom',
				labels: {
					fontColor: 'red',
					display: true,
				}
			},
			scale: {
				pointLabels: {
					fontSize: 14
				},

				// Hides the scale
				display: true
			}
		}
	})

	function init() {
		var option1 = {
			hint: true,
			highlight: true,
			minLength: 0,
		}
		var option2 = {
			name: 'states',
			display: (x) => x.currencyName,
			limit: 15,
			source: currySearch(Template.instance())
		}

		//binding for updating autocomplete source on deletion of items
		this.option1 = option1
		this.option2 = option2

		function currySearch(template) {
			return function typeAheadSearch(entry, CB) {
				CB(
					template.TransitoryCollection.find({
						$or: [{
							currencyName: new RegExp(entry, 'ig')
						}, {
							currencySymbol: new RegExp(entry, 'ig')
						}],
						currencySymbol: {
							$nin: template.compared.get()
						}
					}, {
						limit: 15,
						sort: {
							currencyName: 1
						}
					}).fetch()
				)
			}
		}

		function curryEvent(template) {
			return function addSelection(event, value) {
				var templateInstance = template
				cmpArr = templateInstance.compared.get()

				// don't add a new currency if it's already on the graph
				cmpArr.push(value.currencySymbol)
				templateInstance.compared.set(cmpArr)

				let path = `/compareCurrencies/${_.uniq(templateInstance.compared.get()).toString().replace(/,/g, '-')}`
				history.replaceState({
					path: path
				}, 'compareCurrencies', path) // replace the url field in the browser without reloading the page

				//this whole dance is necessery because the datasource doesn't update if not reinitialized
				//and because it can't be focued if opened, and it can't be opened if already focused on select event
				$('.typeahead').typeahead('destroy')
				$('.typeahead').blur()
				$('.typeahead').typeahead(option1, option2)
				$('.typeahead').typeahead('val', '');
				$('.typeahead').focus()
				// $('.typeahead').typeahead('open');

				// a way to randomly generate a color
				let color = '#' + (Math.random() * 0xFFFFFF << 0).toString(16)
				let rgb = parseInt(color.substring(1), 16)

				templateInstance.colors.set(value.currencySymbol, color)

				let currency = templateInstance.TransitoryCollection.findOne({
					_id: value._id
				}) || {}

				let graphdata = GraphData.findOne({
					_id: 'elodata'
				}) || {}

				const {
					codebaseMaxElo,
					codebaseMinElo,
					communityMaxElo,
					communityMinElo,
					walletMinElo,
					walletMaxElo
				} = graphdata

				var wallet = ((currency.walletRanking - walletMinElo) / ((walletMaxElo - walletMinElo) || 1)) * 10;
				var community = (((currency.communityRanking || communityMinElo) - communityMinElo) / ((communityMaxElo - communityMinElo) || 1)) * 10;
				let codebase = (((currency.codebaseRanking || codebaseMinElo) - codebaseMinElo) / ((codebaseMaxElo - codebaseMinElo) || 1)) * 10

				let maxD = graphdata.decentralizationMaxElo
				let minD = graphdata.decentralizationMinElo

				let decentralization = (((currency.decentralizationRanking || minD) - minD) / ((maxD - minD) || 1)) * 10

				let minDev = graphdata.developmentMinElo
				let maxDev = graphdata.developmentMaxElo

				let development = (((currency.gitCommits || minDev) - minDev) / ((maxDev - minDev) || 1)) * 10

				let nums = [development, codebase, community, 2, 7, wallet, 1, 3, decentralization]

				// push the new data to the chart
				templateInstance.radarchart.data.datasets.push({
					label: value._id,
					fill: true,
					backgroundColor: `rgba(${(rgb >> 16) & 255}, ${(rgb >> 8) & 255}, ${rgb & 255}, 0.2)`, // a way to convert color from hex to rgb
					borderColor: color,
					pointBorderColor: '#fff',
					pointStyle: 'dot',
					pointBackgroundColor: color,
					data: nums
				})

				// update the chart to reflect new data
				templateInstance.radarchart.update()
			}
		}

		//adds first found entry in autocomplete on enter keypress
		$('.typeahead').typeahead(option1, option2).on('keyup', {
			templ: Template.instance()
		}, function (event) {
			if (event.keyCode == 13) {
				var a = event.data.templ.TransitoryCollection.findOne({
					$or: [{
						currencyName: new RegExp(event.target.value, 'ig')
					}, {
						currencySymbol: new RegExp(event.target.value, 'ig')
					}],
					currencySymbol: {
						$nin: event.data.templ.compared.get()
					}
				}, {
					sort: {
						currencyName: 1
					}
				})
				if (a) {
					$('.typeahead').typeahead('val', '');
					$('.typeahead').focus()
					curryEvent(event.data.templ)(null, a)
				}
			}
		});

		$('.typeahead').focus()

		$('.typeahead').bind('typeahead:select', curryEvent(Template.instance()))

		$('.typeahead').bind('typeahead:autocomplete', curryEvent(Template.instance()))
	}

	init()

	//couldn't tell if this is necessery
	// this.autorun((comp) => {
	// 	if (LocalCurrencies.find().count() && this.transitioning.get() == true) {
	// 		this.TransitoryCollection = LocalCurrencies
	// 		this.transitioning.set(false)
	// 		// $('.typeahead').typeahead('destroy')
	// 		// init()
	// 		comp.stop()
	// 	} else if (this.transitioning.get() == false) {
	// 		comp.stop()
	// 	}
	// })

	Template.instance().compared.get().forEach(i => {
		curryEvent(Template.instance())(null, TransitoryCollection.findOne({
			currencySymbol: i
		}) || {})
	})
})

Template.compareCurrencies.events({
	'click .js-delete': function (event, templateInstance) {
		event.preventDefault()

		cmpArr = cmpArr.filter(i => i !== this.currencySymbol)
		templateInstance.compared.set(cmpArr)

		//this whole dance is necessery because the datasource doesn't update if not reinitialized
		$('.typeahead').typeahead('destroy')
		$('.typeahead').typeahead(templateInstance.option1, templateInstance.option2)

		// remove data from the chart and update it accordingly
		templateInstance.radarchart.data.datasets = templateInstance.radarchart.data.datasets.filter(i => i.label !== this._id)
		templateInstance.radarchart.update()

		let path = `/compareCurrencies/${_.uniq(templateInstance.compared.get()).toString().replace(/,/g, '-')}`
		history.replaceState({
			path: path
		}, 'compareCurrencies', path) // replace the url field in the browser without reloading the page
	},
})

Template.compareCurrencies.helpers({
	// get all currencies currently on the list
	comparedCurrencies: () => {
		let cur = Currencies.find({
			currencySymbol: {
				$in: Template.instance().compared.get()
			}
		}, {
			fields: {
				currencyName: 1,
				currencySymbol: 1,
				circulating: 1,
				marketCap: 1,
				maxCoins: 1,
				hashpower: 1,
				slug: 1,
				price: 1
			}
		}).fetch()

		// add the color field
		cur.forEach(i => i.color = Template.instance().colors.get(i.currencySymbol))

		return cur
	},
	colspan: () => Template.instance().compared.get().length + 2,
	top3: () => [1, 2, 3],
	topFeature: function (num) {
		return (Features.find({
			currencySlug: this.slug
		}, {
			sort: {
				rating: -1
			},
			fields: {
				featureName: 1
			}
		}).fetch()[num - 1] || {}).featureName || '-'
	},
	topFlag: function (num) {
		return (Redflags.find({
			currencyId: this._id
		}, {
			sort: {
				rating: -1
			},
			fields: {
				name: 1
			}
		}).fetch()[num - 1] || {}).name || '-'
	},
	hashpower: function () {
		return this.hashpower ? Math.round(this.hashpower).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : 'N\\A'
	},
	finalValue: function () {
		return Math.round(this.marketCap / this.maxCoins).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
	},
	martketCap: function () {
		return Math.round(this.marketCap).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
	},
	circulating: function () {
		return Math.round(this.circulating).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
	},
	founder: () => "No data" // currently, there's no data about founders
})

Template.compareCurrencies.onDestroyed(function () {
	$(".typeahead").typeahead("destroy")
	$(".typeahead").off("keyup")
})<commit_msg>Problem: typeahead callback won't switch to local collection<commit_after>import {
	Template
} from 'meteor/templating';
import {
	Features,
	Currencies,
	GraphData,
	Redflags,
	LocalCurrencies,
} from '/imports/api/indexDB.js'

import Cookies from 'js-cookie'
import typeahead from 'corejs-typeahead' //maintained typeahead

import '/imports/ui/stylesheets/typeahead.css'

import {
	FlowRouter
} from 'meteor/staringatlights:flow-router'

import './compareCurrencies.css'
import './compareCurrencies.html'

Template.compareCurrencies.onCreated(function () {
	this.autorun(() => {
		SubsCache.subscribe('graphdata')
		SubsCache.subscribe('approvedcurrencies')
		SubsCache.subscribe('features')
		SubsCache.subscribe('redflags')
	})

	let currencies = FlowRouter.getParam('currencies') && FlowRouter.getParam('currencies').split('-') || []

	this.compared = new ReactiveVar(currencies)
	this.colors = new ReactiveDict()
	this.methodReady = new ReactiveVar(false)

	currencies.forEach(i => {
		let color = '#' + (Math.random() * 0xFFFFFF << 0).toString(16)
		let rgb = parseInt(color.substring(1), 16)

		this.colors.set(i, color)
	})
	this.init = function () {
		var option1 = {
			hint: true,
			highlight: true,
			minLength: 0,
		}
		var option2 = {
			name: 'states',
			display: (x) => x.currencyName,
			limit: 15,
			source: currySearch(Template.instance())
		}

		//binding for updating autocomplete source on deletion of items
		this.option1 = option1
		this.option2 = option2

		function currySearch(template) {
			return function typeAheadSearch(entry, CB) {
				CB(
					template.TransitoryCollection.find({
						$or: [{
							currencyName: new RegExp(entry, 'ig')
						}, {
							currencySymbol: new RegExp(entry, 'ig')
						}],
						currencySymbol: {
							$nin: template.compared.get()
						}
					}, {
						limit: 15,
						sort: {
							currencyName: 1
						}
					}).fetch()
				)
			}
		}

		function curryEvent(template) {
			return function addSelection(event, value) {
				var templateInstance = template
				cmpArr = templateInstance.compared.get()

				// don't add a new currency if it's already on the graph
				cmpArr.push(value.currencySymbol)
				templateInstance.compared.set(cmpArr)

				let path = `/compareCurrencies/${_.uniq(templateInstance.compared.get()).toString().replace(/,/g, '-')}`
				history.replaceState({
					path: path
				}, 'compareCurrencies', path) // replace the url field in the browser without reloading the page

				//this whole dance is necessery because the datasource doesn't update if not reinitialized
				//and because it can't be focued if opened, and it can't be opened if already focused on select event
				$('.typeahead').typeahead('destroy')
				$('.typeahead').blur()
				$('.typeahead').typeahead(option1, option2)
				$('.typeahead').typeahead('val', '');
				$('.typeahead').focus()
				// $('.typeahead').typeahead('open');

				// a way to randomly generate a color
				let color = '#' + (Math.random() * 0xFFFFFF << 0).toString(16)
				let rgb = parseInt(color.substring(1), 16)

				templateInstance.colors.set(value.currencySymbol, color)

				let currency = templateInstance.TransitoryCollection.findOne({
					_id: value._id
				}) || {}

				let graphdata = GraphData.findOne({
					_id: 'elodata'
				}) || {}

				const {
					codebaseMaxElo,
					codebaseMinElo,
					communityMaxElo,
					communityMinElo,
					walletMinElo,
					walletMaxElo
				} = graphdata

				var wallet = ((currency.walletRanking - walletMinElo) / ((walletMaxElo - walletMinElo) || 1)) * 10;
				var community = (((currency.communityRanking || communityMinElo) - communityMinElo) / ((communityMaxElo - communityMinElo) || 1)) * 10;
				let codebase = (((currency.codebaseRanking || codebaseMinElo) - codebaseMinElo) / ((codebaseMaxElo - codebaseMinElo) || 1)) * 10

				let maxD = graphdata.decentralizationMaxElo
				let minD = graphdata.decentralizationMinElo

				let decentralization = (((currency.decentralizationRanking || minD) - minD) / ((maxD - minD) || 1)) * 10

				let minDev = graphdata.developmentMinElo
				let maxDev = graphdata.developmentMaxElo

				let development = (((currency.gitCommits || minDev) - minDev) / ((maxDev - minDev) || 1)) * 10

				let nums = [development, codebase, community, 2, 7, wallet, 1, 3, decentralization]

				// push the new data to the chart
				templateInstance.radarchart.data.datasets.push({
					label: value._id,
					fill: true,
					backgroundColor: `rgba(${(rgb >> 16) & 255}, ${(rgb >> 8) & 255}, ${rgb & 255}, 0.2)`, // a way to convert color from hex to rgb
					borderColor: color,
					pointBorderColor: '#fff',
					pointStyle: 'dot',
					pointBackgroundColor: color,
					data: nums
				})

				// update the chart to reflect new data
				templateInstance.radarchart.update()
			}
		}

		//adds first found entry in autocomplete on enter keypress
		$('.typeahead').typeahead(option1, option2).on('keyup', {
			templ: Template.instance()
		}, function (event) {
			if (event.keyCode == 13) {
				var a = event.data.templ.TransitoryCollection.findOne({
					$or: [{
						currencyName: new RegExp(event.target.value, 'ig')
					}, {
						currencySymbol: new RegExp(event.target.value, 'ig')
					}],
					currencySymbol: {
						$nin: event.data.templ.compared.get()
					}
				}, {
					sort: {
						currencyName: 1
					}
				})
				if (a) {
					$('.typeahead').typeahead('val', '');
					$('.typeahead').focus()
					curryEvent(event.data.templ)(null, a)
				}
			}
		});

		$('.typeahead').focus()

		$('.typeahead').bind('typeahead:select', curryEvent(Template.instance()))

		$('.typeahead').bind('typeahead:autocomplete', curryEvent(Template.instance()))
	}
	//logic for receiving benefits of fast-render and yet using nonreactive data from method
	if (!LocalCurrencies.find().count()) {
		this.TransitoryCollection = Currencies
		// this.transitioning = new ReactiveVar(true)
		Meteor.call('fetchCurrencies', (err, res) => {
			res.forEach(x => {
				LocalCurrencies.insert(x)
			})
			this.TransitoryCollection = LocalCurrencies
			$('.typeahead').typeahead('destroy')
			this.methodReady.set(true)
			console.log(this.TransitoryCollection.find().fetch())
		})
	} else {
		this.TransitoryCollection = LocalCurrencies
		// this.transitioning = new ReactiveVar(false)
	}
})

Template.compareCurrencies.onRendered(function () {
	const radar = document.getElementById('radar').getContext('2d')
	radar.canvas.width = 800
	radar.canvas.height = 600

	this.radarchart = new Chart(radar, {
		type: 'radar',
		data: {
			labels: ['Ongoing Development', 'Code Quality', 'Community', 'Hash Power', 'Settlement Speed', 'Ease of Use', 'Coin Distribution', 'Transactions', 'Decentralization'],
			datasets: [{
				label: '2',
				fill: false,
				backgroundColor: '#fff',
				borderColor: '#ccc',
				pointBorderColor: '#fff',
				borderWidth: 4,
				pointRadius: 0,
				pointBackgroundColor: '#fff',
				data: [10, 10, 10, 10, 10, 10, 10, 10, 10]
			}, {
				label: '3',
				fill: false,
				backgroundColor: '#fff',
				borderColor: '#fff',
				borderWidth: 1,
				pointBorderColor: '#fff',
				pointBackgroundColor: '#fff',
				data: [0, 0, 0, 0, 0, 0, 0, 0, 0]
			}]
		},
		options: {
			responsive: false,
			defaultFontColor: 'red',
			tooltips: {
				enabled: false
			},
			maintainAspectRatio: false,
			title: {
				display: false
			},
			legend: {
				display: false,
				position: 'bottom',
				labels: {
					fontColor: 'red',
					display: true,
				}
			},
			scale: {
				pointLabels: {
					fontSize: 14
				},

				// Hides the scale
				display: true
			}
		}
	})

	this.autorun((comp)=>{
		if (this.methodReady.get()) {
			this.init()
			comp.stop()
		}
	})
	this.init()

	Template.instance().compared.get().forEach(i => {
		curryEvent(Template.instance())(null, TransitoryCollection.findOne({
			currencySymbol: i
		}) || {})
	})
})

Template.compareCurrencies.events({
	'click .js-delete': function (event, templateInstance) {
		event.preventDefault()

		cmpArr = cmpArr.filter(i => i !== this.currencySymbol)
		templateInstance.compared.set(cmpArr)

		//this whole dance is necessery because the datasource doesn't update if not reinitialized
		$('.typeahead').typeahead('destroy')
		$('.typeahead').typeahead(templateInstance.option1, templateInstance.option2)

		// remove data from the chart and update it accordingly
		templateInstance.radarchart.data.datasets = templateInstance.radarchart.data.datasets.filter(i => i.label !== this._id)
		templateInstance.radarchart.update()

		let path = `/compareCurrencies/${_.uniq(templateInstance.compared.get()).toString().replace(/,/g, '-')}`
		history.replaceState({
			path: path
		}, 'compareCurrencies', path) // replace the url field in the browser without reloading the page
	},
})

Template.compareCurrencies.helpers({
	// get all currencies currently on the list
	comparedCurrencies: () => {
		let cur = Currencies.find({
			currencySymbol: {
				$in: Template.instance().compared.get()
			}
		}, {
			fields: {
				currencyName: 1,
				currencySymbol: 1,
				circulating: 1,
				marketCap: 1,
				maxCoins: 1,
				hashpower: 1,
				slug: 1,
				price: 1
			}
		}).fetch()

		// add the color field
		cur.forEach(i => i.color = Template.instance().colors.get(i.currencySymbol))

		return cur
	},
	colspan: () => Template.instance().compared.get().length + 2,
	top3: () => [1, 2, 3],
	topFeature: function (num) {
		return (Features.find({
			currencySlug: this.slug
		}, {
			sort: {
				rating: -1
			},
			fields: {
				featureName: 1
			}
		}).fetch()[num - 1] || {}).featureName || '-'
	},
	topFlag: function (num) {
		return (Redflags.find({
			currencyId: this._id
		}, {
			sort: {
				rating: -1
			},
			fields: {
				name: 1
			}
		}).fetch()[num - 1] || {}).name || '-'
	},
	hashpower: function () {
		return this.hashpower ? Math.round(this.hashpower).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : 'N\\A'
	},
	finalValue: function () {
		return Math.round(this.marketCap / this.maxCoins).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
	},
	martketCap: function () {
		return Math.round(this.marketCap).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
	},
	circulating: function () {
		return Math.round(this.circulating).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
	},
	founder: () => "No data" // currently, there's no data about founders
})

Template.compareCurrencies.onDestroyed(function () {
	$(".typeahead").typeahead("destroy")
	$(".typeahead").off("keyup")
})<|endoftext|>