<commit_before>/*

*/

var server = require('./server');

var userArgs = process.argv.slice(2);
var htmlFile = userArgs[1]

if (htmlFile[0] !== '/'){
	htmlFile = '/' + htmlFile;
}

var serverInstance = server.start({
	rootPath: userArgs[0]
});

var phantom = require('phantom');
phantom.create(function(ph){
    ph.createPage(function(page){
        page.open('http://localhost:3000' + htmlFile, function(){
            console.log('opened')
            setTimeout(function(){
                // Todo: don't just pray and hope that the results are in...
                process.exit(code=0)
            }, 500)
        })
    })
})

<commit_msg>Instruction for cmd tool.<commit_after>/*
	Add annotations to Javascript files based on example html files that use them.

	Example use:

		 node lib/vAnnotate site test.html

	The first argument is the folder containing the Html and Javascript code,
	the second argument specifies the the path within the folder.
	(The example above would open the file ./site/test.html)
*/

var server = require('./server');

var userArgs = process.argv.slice(2);
var htmlFile = userArgs[1]

if (htmlFile[0] !== '/'){
	htmlFile = '/' + htmlFile;
}

var serverInstance = server.start({
	rootPath: userArgs[0]
});

var phantom = require('phantom');
phantom.create(function(ph){
    ph.createPage(function(page){
        page.open('http://localhost:3000' + htmlFile, function(){
            console.log('opened')
            setTimeout(function(){
                // Todo: don't just pray and hope that the results are in...
                process.exit(code=0)
            }, 500)
        })
    })
})

<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var fs = require('fs');
var configHelpers = require('./config_helpers');
var helpers = require('./helpers');
var validate = require('./validate');
var parser = require('fleek-parser');
var genit = require('genit');
const cwd = process.cwd();

var pathSplit = module.parent.filename.split('/');
pathSplit.pop();
const relPath = pathSplit.join('/');

var validator =  function (app, config) {
  config = config || {};

  // if no app is passed in, just return the middleware
  var shouldBindApp = true;
  if (!_.isFunction(app.use)) {
    config = app || {};
    shouldBindApp = false;
  }

  // docs
  const docs = configHelpers.parseSwaggerDocs(relPath, config.swagger);

  // make sure the docs are valid
  if (!docs) { throw new Error('No swagger documentation file recovered. Check the configuration'); }

  // parser
  var swagger = parser.parse(docs);
  if (!swagger) { throw new Error('Fail to parser swagger documentation'); }

  var routeValidationMap = swagger.routeValidationMap;
  var tracePath = helpers.templateTracer(routeValidationMap);

  var valMiddleware = function *(next) {
    this.fleek             = this.fleek || {};
    this.fleek.routeConfig = this.fleek.routeConfig || {};
    this.fleek.validated   = false;

    validate.isErr   = validate.isError = validate.isValError = validate.isValidationError = validate.isValErr = helpers.isValidationError;

    this.fleek.validate    = validate;
    var method       = this.request.method.toLowerCase();
    var path         = null;

    // get the path form the route config
    if (this.fleek.routeConfig.path) {
      path = this.fleek.routeConfig.path

    // if the route config doesnt exist, trace it
    } else {
      path = tracePath(this);
    }

    // unsupported route. return
    if (!path && config.strict) { return; }

    // get the parameters array fro the route
    var parameters = (routeValidationMap[path] || {})[method];
    if (_.isArray(parameters)) {
      this.fleek.validated = true;
      var result = this.fleek.validate.ctx(this, parameters);

      if (this.fleek.validate.isErr(result)) {

        if (genit.isGenerator(config.catch)) {
          this.fleek.validationError
          yield config.catch.call(this, result, next);

        } else {
          this.status = 400;
          this.body   = result;
        }

        // short circuit on failure
        return;

      } else {
        if (_.isFunction(config.success)) {
          yield config.success().call(this, next);
        }

        yield next;
      }
    }

    yield next;
  }

  if (shouldBindApp) {
    app.use(valMiddleware);
  } else {
    return valMiddleware;
  }
}

validator.validate = validate;
module.exports = validator;
<commit_msg>Fixed validator<commit_after>'use strict';

var _ = require('lodash');
var fs = require('fs');
var configHelpers = require('./config_helpers');
var helpers = require('./helpers');
var validate = require('./validate');
var parser = require('fleek-parser');
var genit = require('genit');
const cwd = process.cwd();

var pathSplit = module.parent.filename.split('/');
pathSplit.pop();
const relPath = pathSplit.join('/');

var validator =  function (app, config) {
  config = config || {};

  // if no app is passed in, just return the middleware
  var shouldBindApp = true;
  if (!genit.isGenerator(app.use)) {
    config = app || {};
    shouldBindApp = false;
  }

  // docs
  const docs = configHelpers.parseSwaggerDocs(relPath, config.swagger);

  // make sure the docs are valid
  if (!docs) { throw new Error('No swagger documentation file recovered. Check the configuration'); }

  // parser
  var swagger = parser.parse(docs);
  if (!swagger) { throw new Error('Fail to parser swagger documentation'); }

  var routeValidationMap = swagger.routeValidationMap;
  var tracePath = helpers.templateTracer(routeValidationMap);

  var valMiddleware = function *(next) {
    this.fleek             = this.fleek || {};
    this.fleek.routeConfig = this.fleek.routeConfig || {};
    this.fleek.validated   = false;

    validate.isErr   = validate.isError = validate.isValError = validate.isValidationError = validate.isValErr = helpers.isValidationError;

    this.fleek.validate    = validate;
    var method       = this.request.method.toLowerCase();
    var path         = null;

    // get the path form the route config
    if (this.fleek.routeConfig.path) {
      path = this.fleek.routeConfig.path

    // if the route config doesnt exist, trace it
    } else {
      path = tracePath(this);
    }

    // unsupported route. return
    if (!path && config.strict) { return; }

    // get the parameters array fro the route
    var parameters = (routeValidationMap[path] || {})[method];
    if (_.isArray(parameters)) {
      this.fleek.validated = true;
      var result = this.fleek.validate.ctx(this, parameters);

      if (this.fleek.validate.isErr(result)) {

        if (genit.isGenerator(config.catch)) {
          this.fleek.validationError
          yield config.catch.call(this, result, next);

        } else {
          this.status = 400;
          this.body   = result;
        }

        // short circuit on failure
        return;

      } else {
        if (_.isFunction(config.success)) {
          yield config.success().call(this, next);
        }

        yield next;
      }
    }

    yield next;
  }

  if (shouldBindApp) {
    app.use(valMiddleware);
  } else {
    return valMiddleware;
  }
}

validator.validate = validate;
module.exports = validator;
<|endoftext|>


<commit_before>var validators = require('./validators');
var defaultError = require('./defaultError');

var Validator = exports.Validator = function() {}

Validator.prototype.check = function(str, fail_msg) {
    this.str = (str == null || (isNaN(str) && str.length == undefined)) ? '' : str;
    // Convert numbers to strings but keep arrays/objects
    if (typeof this.str == 'number') {
      this.str += '';
    }
    this.msg = fail_msg;
    this._errors = this._errors || [];
    return this;
}

Object.keys(validators).forEach(function(key) {
    Validator.prototype[key] = function() {
        args = Array.prototype.slice.call(arguments);
        args.unshift(this.str);
        if(!validators[key].apply(this, args)) {
            throw new Error(this.msg || defaultError[key]);
        }
        return this;
    };
});

//Create some aliases - may help code readability
Validator.prototype.validate = Validator.prototype.check;
Validator.prototype.assert = Validator.prototype.check;
Validator.prototype.isFloat = Validator.prototype.isDecimal;
Validator.prototype.is = Validator.prototype.regex;
Validator.prototype.not = Validator.prototype.notRegex;
<commit_msg>correctly indented<commit_after>var validators = require('./validators');
var defaultError = require('./defaultError');

var Validator = exports.Validator = function() {}

Validator.prototype.check = function(str, fail_msg) {
    this.str = (str == null || (isNaN(str) && str.length == undefined)) ? '' : str;
    // Convert numbers to strings but keep arrays/objects
    if (typeof this.str == 'number') {
        this.str += '';
    }
    this.msg = fail_msg;
    this._errors = this._errors || [];
    return this;
}

Object.keys(validators).forEach(function(key) {
    Validator.prototype[key] = function() {
        args = Array.prototype.slice.call(arguments);
        args.unshift(this.str);
        if(!validators[key].apply(this, args)) {
            throw new Error(this.msg || defaultError[key]);
        }
        return this;
    };
});

//Create some aliases - may help code readability
Validator.prototype.validate = Validator.prototype.check;
Validator.prototype.assert = Validator.prototype.check;
Validator.prototype.isFloat = Validator.prototype.isDecimal;
Validator.prototype.is = Validator.prototype.regex;
Validator.prototype.not = Validator.prototype.notRegex;
<|endoftext|>


<commit_before>/* eslint-disable import/no-extraneous-dependencies, no-console, import/no-unresolved */

/*
  S3Proxy Express Framework Example

  Passes HTTP GET requests to s3proxy
  Start: PORT=3000 node express
  Test: mocha test.js

  Author: George Moon <george.moon@gmail.com>
*/
const fs = require('fs');
const helmet = require('helmet');
const express = require('express');
const debug = require('debug')('s3proxy');
const bodyParser = require('body-parser');
const morgan = require('morgan');
const addRequestId = require('express-request-id')({ headerName: 'x-request-id' });
const S3Proxy = require('s3proxy');

const port = process.env.PORT;
const bucket = process.env.BUCKET;
const app = express();
app.set('view engine', 'pug');
app.use(addRequestId);
app.use(bodyParser.json());
app.use(helmet());

function handleError(req, res, err) {
  // sending xml because the AWS SDK sets content-type: application/xml for non-200 responses
  res.end(`<?xml version="1.0"?>\n<error time="${err.time}" code="${err.code}" statusCode="${err.statusCode}" url="${req.url}" method="${req.method}">${err.message}</error>
  `);
}

// In non-production environments, if a credentials file exists, return the credentials
// To create a temporary credentials file:
//     aws sts get-session-token --duration 900 > credentials.json
//
function getCredentials() {
  const file = './credentials.json';
  var contents = undefined;
  try {
    const credentials = JSON.parse(fs.readFileSync(file)).Credentials;
    if (process.env.NODE_ENV.match(/^prod/i)) {
      throw new Error('will not use a credentials file in production');
    }
    contents = {
      accessKeyId: credentials.AccessKeyId,
      secretAccessKey: credentials.SecretAccessKey,
      sessionToken: credentials.SessionToken
    };
    debug(`using credentials from ${file}`);
  } catch (e) {
    debug(`using sdk credential chain`);
  }
  return contents;
}

// Use morgan for request logging except during test execution
if (process.env.NODE_ENV !== 'test') {
  app.use(morgan(
    'request :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] '
    + '":referrer" ":user-agent" ":response-time ms" :res[x-request-id] :res[x-amz-request-id]',
  ));
}

// initialize the s3proxy
const credentials = getCredentials();
const proxy = new S3Proxy({ bucket, logger: console, credentials });
proxy.init();

proxy.on('error', (err) => {
  console.log(`error initializing s3proxy for bucket ${bucket}: ${err.statusCode} ${err.code}`);
});

// basic health check
app.get('/health', (req, res) => {
  res.writeHead(200);
  res.end();
});

// health check s3
app.get('/health/s3', (req, res) => {
  proxy.healthCheckStream(res).on('error', () => {
    // just end the request and let the HTTP status code convey the error
    res.end();
  }).pipe(res);
});

// route all get requests to s3proxy
app.get('/', (req, res) => {
  res.redirect('/index.html');
});

app.route('/*')
  .head(async (req, res) => {
    await proxy.head(req, res);
    res.end();
  })
  .get((req, res) => {
    proxy.get(req, res).on('error', (err) => {
      handleError(req, res, err);
    }).pipe(res);
  });

proxy.on('init', () => {
  if (port > 0) {
    app.listen(port, () => {
      debug(`listening on port ${port}`);
      if (process.send) { // test to determine if this is running in a child process
        process.send('ready'); // for pm2-runtime wait_ready option
      }
    });
  }
});

module.exports = app;
<commit_msg>added s3proxy version number to debug output on init<commit_after>/* eslint-disable import/no-extraneous-dependencies, no-console, import/no-unresolved */

/*
  S3Proxy Express Framework Example

  Passes HTTP GET requests to s3proxy
  Start: PORT=3000 node express
  Test: mocha test.js

  Author: George Moon <george.moon@gmail.com>
*/
const fs = require('fs');
const helmet = require('helmet');
const express = require('express');
const debug = require('debug')('s3proxy');
const bodyParser = require('body-parser');
const morgan = require('morgan');
const addRequestId = require('express-request-id')({ headerName: 'x-request-id' });
const S3Proxy = require('s3proxy');

const port = process.env.PORT;
const bucket = process.env.BUCKET;
const app = express();
app.set('view engine', 'pug');
app.use(addRequestId);
app.use(bodyParser.json());
app.use(helmet());

function handleError(req, res, err) {
  // sending xml because the AWS SDK sets content-type: application/xml for non-200 responses
  res.end(`<?xml version="1.0"?>\n<error time="${err.time}" code="${err.code}" statusCode="${err.statusCode}" url="${req.url}" method="${req.method}">${err.message}</error>
  `);
}

// In non-production environments, if a credentials file exists, return the credentials
// To create a temporary credentials file:
//     aws sts get-session-token --duration 900 > credentials.json
//
function getCredentials() {
  const file = './credentials.json';
  var contents = undefined;
  try {
    const credentials = JSON.parse(fs.readFileSync(file)).Credentials;
    if (process.env.NODE_ENV.match(/^prod/i)) {
      throw new Error('will not use a credentials file in production');
    }
    contents = {
      accessKeyId: credentials.AccessKeyId,
      secretAccessKey: credentials.SecretAccessKey,
      sessionToken: credentials.SessionToken
    };
    debug(`using credentials from ${file}`);
  } catch (e) {
    debug(`using sdk credential chain`);
  }
  return contents;
}

// Use morgan for request logging except during test execution
if (process.env.NODE_ENV !== 'test') {
  app.use(morgan(
    'request :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] '
    + '":referrer" ":user-agent" ":response-time ms" :res[x-request-id] :res[x-amz-request-id]',
  ));
}

// initialize the s3proxy
const credentials = getCredentials();
const proxy = new S3Proxy({ bucket, logger: console, credentials });
proxy.init();

proxy.on('error', (err) => {
  console.log(`error initializing s3proxy for bucket ${bucket}: ${err.statusCode} ${err.code}`);
});

// basic health check
app.get('/health', (req, res) => {
  res.writeHead(200);
  res.end();
});

// health check s3
app.get('/health/s3', (req, res) => {
  proxy.healthCheckStream(res).on('error', () => {
    // just end the request and let the HTTP status code convey the error
    res.end();
  }).pipe(res);
});

// route all get requests to s3proxy
app.get('/', (req, res) => {
  res.redirect('/index.html');
});

app.route('/*')
  .head(async (req, res) => {
    await proxy.head(req, res);
    res.end();
  })
  .get((req, res) => {
    proxy.get(req, res).on('error', (err) => {
      handleError(req, res, err);
    }).pipe(res);
  });

proxy.on('init', () => {
  if (port > 0) {
    app.listen(port, () => {
      debug(`[version=${S3Proxy.version()}] listening on port ${port}`);
      if (process.send) { // test to determine if this is running in a child process
        process.send('ready'); // for pm2-runtime wait_ready option
      }
    });
  }
});

module.exports = app;
<|endoftext|>


<commit_before>"use strict";

const _ = require('underscore');
const util = require('util');
const EventEmitter = require('events');

const NoopLogger = require(__dirname + '/noop-logger');
const EventConsts = require(__dirname + '/event-consts');
const ViberClient = require(__dirname + '/viber-client');
const Middleware = require(__dirname + '/middleware');
const RegexMatcherRouter = require(__dirname + '/regex-matcher-router');

const MessageFactory = require(__dirname + '/message/message-factory');
const MessageValidator = require(__dirname + '/message/message-validator');
const Message = require(__dirname + '/message/message');
const TextMessage = require(__dirname + '/message/text-message');

const UserProfile = require(__dirname + '/user-profile');
const Response = require(__dirname + '/response');

const REQUIRED_CONFIGURATION_FIELDS = ["authToken", "name", "avatar"];
const SUBSCRIBED_EVENTS = ["subscribed", "unsubscribed", "conversation_started", "message", "delivered", "seen"];
const API_URL = "https://chatapi.viber.com/pa";

function ViberBot(loggerOrConfiguration, configuration) {

	// backward compatibility: we are still allowing ctor as (logger, configuration);
	// newer should use (configuration) with logger property in it.
	let logger;
	if (!configuration) {
		// no logger, treat loggerOrConfiguration as configuration
		configuration = loggerOrConfiguration;
		logger = configuration.logger || NoopLogger;
	} else {
		logger = loggerOrConfiguration || NoopLogger;
	}

	if (!configuration) {
		throw new Error(`Invalid configuration`);
	}

	const missingFields = this._getMissingFieldsInConfiguration(configuration);
	if (!_.isEmpty(missingFields)) {
		throw new Error(`Invalid configuration ${configuration}. Missing fields: ${missingFields}`);
	}

	this.authToken = configuration.authToken;
	this.name = configuration.name;
	this.avatar = configuration.avatar;

	this._logger = logger;
	this._client = new ViberClient(this._logger, this, API_URL, configuration.registerToEvents || SUBSCRIBED_EVENTS);
	this._middleware = new Middleware(this._logger, new MessageValidator(this._logger, this.authToken));
	this._messageFactory = new MessageFactory(this._logger);
	this._regexMatcherRouter = new RegexMatcherRouter(this._logger);
	this._callbacks = { [EventConsts.CONVERSATION_STARTED]: [] };

	this._registerStreamAndHandleEvents(this._middleware.getStream());
	this._setupTextMessageReceivedHandler();
	this._setupConversationStartedHandler();
}
util.inherits(ViberBot, EventEmitter);

ViberBot.prototype.getBotProfile = function() {
	return this._client.getAccountInfo();
};

ViberBot.prototype.getUserDetails = function(userProfile) {
	return this._client.getUserDetails(userProfile.id).then(response => Promise.resolve(response.user));
};

ViberBot.prototype.getOnlineStatus = function(viberUserIds) {
	return this._client.getOnlineStatus(viberUserIds);
};

ViberBot.prototype.setWebhook = function(url) {
	return this._client.setWebhook(url);
};

ViberBot.prototype.sendMessage = function(userProfile, messages, optionalTrackingData) {
	if (messages == null) return Promise.resolve();
	messages = _.isArray(messages) ? messages : [messages];

	const self = this;
	const lastMessage = messages.pop();
	const tokens = [];

	const resolveCallback = (response, message, resolve, reject) => {
		if (response.status != 0) reject(response);
		tokens.push(response.message_token);
		self.emit(EventConsts.MESSAGE_SENT, message, userProfile);
		resolve();
	};

	let promise = Promise.resolve();
	_.each(messages, message => {
		promise = promise.then(() => new Promise((resolve, reject) =>
			self._sendMessageFromClient(userProfile, message).then(response => resolveCallback(response, message, resolve, reject))));
	});

	return promise.then(() => new Promise((resolve, reject) =>
		self._sendMessageFromClient(userProfile, lastMessage, optionalTrackingData, lastMessage.keyboard)
			.then(response => resolveCallback(response, lastMessage, resolve, reject))))
		.then(() => Promise.resolve(tokens));
};

ViberBot.prototype.middleware = function() {
	return this._middleware.getIncoming();
};

ViberBot.prototype.onTextMessage = function(regex, callback) {
	this._regexMatcherRouter.newMatcher(regex, callback);
};

ViberBot.prototype.onError = function(callback) {
	this.on(EventConsts.ERROR, callback);
};

ViberBot.prototype.onConversationStarted = function(callback) {
	this._callbacks[EventConsts.CONVERSATION_STARTED].push(callback);
};

ViberBot.prototype.onSubscribe = function(callback) {
	this.on(EventConsts.SUBSCRIBED, callback);
};

ViberBot.prototype.onUnsubscribe = function(callback) {
	this.on(EventConsts.UNSUBSCRIBED, callback);
};

ViberBot.prototype._setupTextMessageReceivedHandler = function() {
	const self = this;
	this.on(EventConsts.MESSAGE_RECEIVED, (message, response) => {
		if (!(message instanceof TextMessage)) return;

		const callback = this._regexMatcherRouter.tryGetCallback(message.text);
		if (!callback) {
			return self._logger.debug(`Could not find regex matcher for ${message.text}`);
		}
		callback(message, response);
	});
};

ViberBot.prototype._setupConversationStartedHandler = function() {
	const self = this;
	this.on(EventConsts.CONVERSATION_STARTED, response => {
		_.each(self._callbacks[EventConsts.CONVERSATION_STARTED], callback => {
			callback(response.context, response.userProfile, (responseMessage, optionalTrackingData) => {
				if (!responseMessage) return;
				if (!(responseMessage instanceof Message)) {
					throw new Error("Response from conversation started callback must be message or null");
				}

				const jsonMessage = responseMessage.toJson();
				if (responseMessage.keyboard) jsonMessage.keyboard = responseMessage.keyboard;
				if (responseMessage.trackingData) {
					jsonMessage["tracking_data"] = self._client._serializeTrackingData(optionalTrackingData || {});
				}

				self._logger.debug("Sending conversation started callback", jsonMessage);
				self._middleware.getStream().write(JSON.stringify(jsonMessage));
			});
		});
	});
};

ViberBot.prototype._getMissingFieldsInConfiguration = function(configuration) {
	return _.difference(REQUIRED_CONFIGURATION_FIELDS, Object.keys(configuration));
};

ViberBot.prototype._sendMessageFromClient = function(userProfile, message, optionalTrackingData, optionalKeyboard) {
	const jsonMessage = message.toJson();
	return this._client.sendMessage(userProfile.id, jsonMessage.type, jsonMessage, optionalTrackingData, optionalKeyboard);
};

ViberBot.prototype._registerStreamAndHandleEvents = function(stream) {
	const self = this;
	stream.on("data", data => {
		try {
			const parsedData = JSON.parse(data.toString());
			self._handleEventReceived(parsedData);
		}
		catch (err) {
			self._logger.error(err);
			self.emit(EventConsts.ERROR, err);
		}
	});
};

ViberBot.prototype._handleEventReceived = function(data) {
	const userProfile = this._getUserProfile(data);
	switch (data.event) {
		case EventConsts.MESSAGE_RECEIVED: {
			const message = this._messageFactory.createMessageFromJson(data);
			const response = new Response(this, userProfile);
			this.emit(data.event, message, response);
			break;
		}
		case EventConsts.SUBSCRIBED:
		case EventConsts.CONVERSATION_STARTED: {
			const response = new Response(this, userProfile, data.context);
			this.emit(data.event, response);
			break;
		}
		case EventConsts.UNSUBSCRIBED: {
			this.emit(data.event, data.user_id);
			break;
		}
		case EventConsts.FAILED: {
			this.emit(EventConsts.ERROR, data);
			break;
		}
		default: {
			this.emit(data.event, data);
			break;
		}
	}
};

ViberBot.prototype._getUserProfile = function(data) {
	if (_.has(data, "sender")) return UserProfile.fromJson(data.sender);
	else if (_.has(data, "user")) return UserProfile.fromJson(data.user);
	return null;
};

module.exports = ViberBot;

<commit_msg>Update viber-bot.js<commit_after>"use strict";

const _ = require('underscore');
const util = require('util');
const EventEmitter = require('events');

const NoopLogger = require(__dirname + '/noop-logger');
const EventConsts = require(__dirname + '/event-consts');
const ViberClient = require(__dirname + '/viber-client');
const Middleware = require(__dirname + '/middleware');
const RegexMatcherRouter = require(__dirname + '/regex-matcher-router');

const MessageFactory = require(__dirname + '/message/message-factory');
const MessageValidator = require(__dirname + '/message/message-validator');
const Message = require(__dirname + '/message/message');
const TextMessage = require(__dirname + '/message/text-message');

const UserProfile = require(__dirname + '/user-profile');
const Response = require(__dirname + '/response');

const REQUIRED_CONFIGURATION_FIELDS = ["authToken", "name", "avatar"];
const SUBSCRIBED_EVENTS = ["subscribed", "unsubscribed", "conversation_started", "message", "delivered", "seen"];
const API_URL = "https://chatapi.viber.com/pa";

function ViberBot(loggerOrConfiguration, configuration) {

	// backward compatibility: we are still allowing ctor as (logger, configuration);
	// newer should use (configuration) with logger property in it.
	let logger;
	if (!configuration) {
		// no logger, treat loggerOrConfiguration as configuration
		configuration = loggerOrConfiguration;
		logger = configuration.logger || NoopLogger;
	} else {
		logger = loggerOrConfiguration || NoopLogger;
	}

	if (!configuration) {
		throw new Error(`Invalid configuration`);
	}

	const missingFields = this._getMissingFieldsInConfiguration(configuration);
	if (!_.isEmpty(missingFields)) {
		throw new Error(`Invalid configuration ${configuration}. Missing fields: ${missingFields}`);
	}

	this.authToken = configuration.authToken;
	this.name = configuration.name;
	this.avatar = configuration.avatar;

	this._logger = logger;
	this._client = new ViberClient(this._logger, this, API_URL, configuration.registerToEvents || SUBSCRIBED_EVENTS);
	this._middleware = new Middleware(this._logger, new MessageValidator(this._logger, this.authToken));
	this._messageFactory = new MessageFactory(this._logger);
	this._regexMatcherRouter = new RegexMatcherRouter(this._logger);
	this._callbacks = { [EventConsts.CONVERSATION_STARTED]: [] };

	this._registerStreamAndHandleEvents(this._middleware.getStream());
	this._setupTextMessageReceivedHandler();
	this._setupConversationStartedHandler();
}
util.inherits(ViberBot, EventEmitter);

ViberBot.prototype.getBotProfile = function() {
	return this._client.getAccountInfo();
};

ViberBot.prototype.getUserDetails = function(userProfile) {
	return this._client.getUserDetails(userProfile.id).then(response => Promise.resolve(response.user));
};

ViberBot.prototype.getOnlineStatus = function(viberUserIds) {
	return this._client.getOnlineStatus(viberUserIds);
};

ViberBot.prototype.setWebhook = function(url) {
	return this._client.setWebhook(url);
};

ViberBot.prototype.sendMessage = function(userProfile, messages, optionalTrackingData) {
	if (messages == null) return Promise.resolve();
	messages = _.isArray(messages) ? messages : [messages];

	const self = this;
	const lastMessage = messages.pop();
	const tokens = [];

	const resolveCallback = (response, message, resolve, reject) => {
		if (response.status != 0) reject(response);
		tokens.push(response.message_token);
		self.emit(EventConsts.MESSAGE_SENT, message, userProfile);
		resolve();
	};

	let promise = Promise.resolve();
	_.each(messages, message => {
		promise = promise.then(() => new Promise((resolve, reject) =>
			self._sendMessageFromClient(userProfile, message).then(response => resolveCallback(response, message, resolve, reject))));
	});

	return promise.then(() => new Promise((resolve, reject) =>
		self._sendMessageFromClient(userProfile, lastMessage, optionalTrackingData, lastMessage.keyboard)
			.then(response => resolveCallback(response, lastMessage, resolve, reject))))
		.then(() => Promise.resolve(tokens));
};

ViberBot.prototype.middleware = function() {
	return this._middleware.getIncoming();
};

ViberBot.prototype.onTextMessage = function(regex, callback) {
	this._regexMatcherRouter.newMatcher(regex, callback);
};

ViberBot.prototype.onError = function(callback) {
	this.on(EventConsts.ERROR, callback);
};

ViberBot.prototype.onConversationStarted = function(callback) {
	this._callbacks[EventConsts.CONVERSATION_STARTED].push(callback);
};

ViberBot.prototype.onSubscribe = function(callback) {
	this.on(EventConsts.SUBSCRIBED, callback);
};

ViberBot.prototype.onUnsubscribe = function(callback) {
	this.on(EventConsts.UNSUBSCRIBED, callback);
};

ViberBot.prototype._setupTextMessageReceivedHandler = function() {
	const self = this;
	this.on(EventConsts.MESSAGE_RECEIVED, (message, response) => {
		if (!(message instanceof TextMessage)) return;

		const callback = this._regexMatcherRouter.tryGetCallback(message.text);
		if (!callback) {
			return self._logger.debug(`Could not find regex matcher for ${message.text}`);
		}
		callback(message, response);
	});
};

ViberBot.prototype._setupConversationStartedHandler = function() {
	const self = this;
	this.on(EventConsts.CONVERSATION_STARTED, response => {
		_.each(self._callbacks[EventConsts.CONVERSATION_STARTED], callback => {
			callback(response.context, response.userProfile, (responseMessage, optionalTrackingData) => {
				if (!responseMessage) return;
				if (!(responseMessage instanceof Message)) {
					throw new Error("Response from conversation started callback must be message or null");
				}

				const jsonMessage = responseMessage.toJson();
				if (responseMessage.keyboard) jsonMessage.keyboard = responseMessage.keyboard;
				if (responseMessage.trackingData) {
					jsonMessage["tracking_data"] = self._client._serializeTrackingData(optionalTrackingData || {});
				}

				self._logger.debug("Sending conversation started callback", jsonMessage);
				self._middleware.getStream().write(JSON.stringify(jsonMessage));
			});
		});
	});
};

ViberBot.prototype._getMissingFieldsInConfiguration = function(configuration) {
	return _.difference(REQUIRED_CONFIGURATION_FIELDS, Object.keys(configuration));
};

ViberBot.prototype._sendMessageFromClient = function(userProfile, message, optionalTrackingData, optionalKeyboard) {
	const jsonMessage = message.toJson();
	return this._client.sendMessage(userProfile.id, jsonMessage.type, jsonMessage, optionalTrackingData, optionalKeyboard);
};

ViberBot.prototype._registerStreamAndHandleEvents = function(stream) {
	const self = this;
	stream.on("data", data => {
		try {
			const parsedData = JSON.parse(data.toString());
			self._handleEventReceived(parsedData);
		}
		catch (err) {
			self._logger.error(err);
			self.emit(EventConsts.ERROR, err);
		}
	});
};

ViberBot.prototype._handleEventReceived = function(data) {
	const userProfile = this._getUserProfile(data);
	switch (data.event) {
		case EventConsts.MESSAGE_RECEIVED: {
			const message = this._messageFactory.createMessageFromJson(data);
			const response = new Response(this, userProfile);
			this.emit(data.event, message, response);
			break;
		}
		case EventConsts.SUBSCRIBED:
		case EventConsts.CONVERSATION_STARTED: {
			const response = new Response(this, userProfile);
			this.emit(data.event, response);
			break;
		}
		case EventConsts.UNSUBSCRIBED: {
			this.emit(data.event, data.user_id);
			break;
		}
		case EventConsts.FAILED: {
			this.emit(EventConsts.ERROR, data);
			break;
		}
		default: {
			this.emit(data.event, data);
			break;
		}
	}
};

ViberBot.prototype._getUserProfile = function(data) {
	if (_.has(data, "sender")) return UserProfile.fromJson(data.sender);
	else if (_.has(data, "user")) return UserProfile.fromJson(data.user);
	return null;
};

module.exports = ViberBot;

<|endoftext|>


<commit_before>;(function(){

var _ = require("underscore");
var $ = require('jquery');
var BackBone = require("backbone");

var ViewStack = module.exports = BackBone.View.extend({

	_stack: null,
	_stackReplace: null,
	_current: null,

	initialize: function initialize(options){
		if ( typeof this.setShared !== "undefined" )
			this.setShared( options.shared );
		this._stack   = [];
		this._stackReplace = [];
		this._current = null;
		this.$el.empty();
	},

	render: function render(){
		return this;
	},

	getView: function getView(){
		return this._stack.length > 0 ? this._stack[ this._stack.length - 1 ] : null;
	},

	getViewActive: function getViewActive(){
		return this._current;
	},

	// pushView
	// Aggiunge una view all'applicazione con z-Index più alto
	pushView: function pushView( newView, options ){

		// Controllo se è la stessa view che sto cercando di pushare
		if ( this._current === newView ) return this;

		if ( options && options.replace == true ){

			if ( this._current ){

				var popped = this._stack.pop();

				this._stackReplace.push( popped );

				popped.$el.detach();

				// Calcolo il nuovo z-Index che è incrementale
				var zIndex = (this._stack.length*100) + 100;
				// Setto lo z-index
				if ( newView.setZindex )
					newView.setZindex( zIndex );
				else
					newView.$el.css("z-index", zIndex);

				this._stack.push( newView );

				this.$el.append( newView.el );

				// setto la view Corrente
				this._current = newView;
				//
				newView.render();

			}

		}else{

			if ( this._current && this._current.onActivate )
				this._current.onActivate();

			// Calcolo il nuovo z-Index che è incrementale
			var zIndex = (this._stack.length*100) + 100;
			// Setto lo z-index
			if ( newView.setZindex )
				newView.setZindex( zIndex );
			else
				newView.$el.css("z-index", zIndex);
			// aggiungo allo stack
			this._stack.push( newView );
			// Appendo alla view dell'applicazione
			this.$el.append( newView.el );
			// setto la view Corrente
			this._current = newView;
			//
			newView.render();

		}

		// Scateno l'evento active per dire alla view che è attiva
		if ( this._current )
        	this._current.trigger("active");

		this
			.refreshUrl()
			.trigger('pushed', newView);
		return this;
	},

	// popView
	// Rimuove l'ultima view dello stack
	popView: function popView(){

		var self 	   = this,
			poppedView = self._current;

		if ( !poppedView || !poppedView.onDeactivate )
			return self._popView();

		poppedView.onDeactivate(function(){
			self._popView();
		});
		return poppedView;

	},

	// refresh
	// Aggiorna l'url se la view corrette ce l'ha settata
	refreshUrl: function refreshUrl(){
		var shared = this.getShared ? this.getShared() : null;
		if ( typeof shared !== "undefined" && shared && this._current && this._current.url ){
			shared.app.navigate(_.result(this._current, 'url'));
		}
		return this;
	},

	// clearStack
	// Pulisce tutto lo stack dalle view
	clearStack: function clearStack(){

		while( this._stack.length > 0 ){
			this.popView();
		}

		return this;
	},

	//
	// Private
	//

	_popView: function _popView(){
		var popped = this._stack.pop();
		if (popped) {
            popped.destroy();
            popped.trigger("deactive");
        }

        // Assegno la nuova view corrente
        this._current = this._stack[ this._stack.length-1 ];

        if ( this._stackReplace.length > 0 ){
        	this.pushView( this._stackReplace.pop() );
        }

        this
			.refreshUrl()
			.trigger('popped', popped);

		return popped;
	}

});

ViewStack.middleware = function middleware(options){
	return function(shared, next){
		var viewStack = new ViewStack(options);
		viewStack.clearStack(); // Pulisco dalle eventuali view appese
		if ( viewStack.setShared )
			viewStack.setShared( shared ); // setto lo shared object
		viewStack.render(); // Renderizzo la view base application
		shared.viewstack = viewStack;
		next();
	}

}

})();<commit_msg>Bbugfix event onActivate current view<commit_after>;(function(){

var _ = require("underscore");
var $ = require('jquery');
var BackBone = require("backbone");

var ViewStack = module.exports = BackBone.View.extend({

	_stack: null,
	_stackReplace: null,
	_current: null,

	initialize: function initialize(options){
		if ( typeof this.setShared !== "undefined" )
			this.setShared( options.shared );
		this._stack   = [];
		this._stackReplace = [];
		this._current = null;
		this.$el.empty();
	},

	render: function render(){
		return this;
	},

	getView: function getView(){
		return this._stack.length > 0 ? this._stack[ this._stack.length - 1 ] : null;
	},

	getViewActive: function getViewActive(){
		return this._current;
	},

	// pushView
	// Aggiunge una view all'applicazione con z-Index più alto
	pushView: function pushView( newView, options ){

		// Controllo se è la stessa view che sto cercando di pushare
		if ( this._current === newView ) return this;

		if ( options && options.replace == true ){

			if ( this._current ){

				var popped = this._stack.pop();

				this._stackReplace.push( popped );

				popped.$el.detach();

				// Calcolo il nuovo z-Index che è incrementale
				var zIndex = (this._stack.length*100) + 100;
				// Setto lo z-index
				if ( newView.setZindex )
					newView.setZindex( zIndex );
				else
					newView.$el.css("z-index", zIndex);

				this._stack.push( newView );

				this.$el.append( newView.el );

				// setto la view Corrente
				this._current = newView;
				//
				newView.render();

			}

		}else{

			// Calcolo il nuovo z-Index che è incrementale
			var zIndex = (this._stack.length*100) + 100;
			// Setto lo z-index
			if ( newView.setZindex )
				newView.setZindex( zIndex );
			else
				newView.$el.css("z-index", zIndex);
			// aggiungo allo stack
			this._stack.push( newView );
			// Appendo alla view dell'applicazione
			this.$el.append( newView.el );
			// setto la view Corrente
			this._current = newView;
			//
			newView.render();

			if ( this._current && this._current.onActivate )
				this._current.onActivate();

		}

		// Scateno l'evento active per dire alla view che è attiva
		if ( this._current )
        	this._current.trigger("active");

		this
			.refreshUrl()
			.trigger('pushed', newView);
		return this;
	},

	// popView
	// Rimuove l'ultima view dello stack
	popView: function popView(){

		var self 	   = this,
			poppedView = self._current;

		if ( !poppedView || !poppedView.onDeactivate )
			return self._popView();

		poppedView.onDeactivate(function(){
			self._popView();
		});
		return poppedView;

	},

	// refresh
	// Aggiorna l'url se la view corrette ce l'ha settata
	refreshUrl: function refreshUrl(){
		var shared = this.getShared ? this.getShared() : null;
		if ( typeof shared !== "undefined" && shared && this._current && this._current.url ){
			shared.app.navigate(_.result(this._current, 'url'));
		}
		return this;
	},

	// clearStack
	// Pulisce tutto lo stack dalle view
	clearStack: function clearStack(){

		while( this._stack.length > 0 ){
			this.popView();
		}

		return this;
	},

	//
	// Private
	//

	_popView: function _popView(){
		var popped = this._stack.pop();
		if (popped) {
            popped.destroy();
            popped.trigger("deactive");
        }

        // Assegno la nuova view corrente
        this._current = this._stack[ this._stack.length-1 ];

        if ( this._stackReplace.length > 0 ){
        	this.pushView( this._stackReplace.pop() );
        }

        this
			.refreshUrl()
			.trigger('popped', popped);

		return popped;
	}

});

ViewStack.middleware = function middleware(options){
	return function(shared, next){
		var viewStack = new ViewStack(options);
		viewStack.clearStack(); // Pulisco dalle eventuali view appese
		if ( viewStack.setShared )
			viewStack.setShared( shared ); // setto lo shared object
		viewStack.render(); // Renderizzo la view base application
		shared.viewstack = viewStack;
		next();
	}

}

})();<|endoftext|>


<commit_before>/**
 * @author dmarcos / https://github.com/dmarcos
 * @author mrdoob / http://mrdoob.com
 */

THREE.VRControls = function ( object, onError ) {

	var scope = this;

	var vrInputs = [];

	function filterInvalidDevices( devices ) {

		var
			OculusDeviceName = 'VR Position Device (oculus)',
			CardboardDeviceName = 'VR Position Device (cardboard)';

		// Exclude Cardboard position sensor if Oculus exists.
		var oculusDevices = devices.filter( function ( device ) {

			return device.deviceName === OculusDeviceName;

		} );

		if ( oculusDevices.length >= 1 ) {

			return devices.filter( function ( device ) {

				return device.deviceName !== CardboardDeviceName;

			} );

		} else {

			return devices;

		}

	}

	function gotVRDevices( devices ) {

		devices = filterInvalidDevices( devices );

		for ( var i = 0; i < devices.length; i ++ ) {

			if ( devices[ i ] instanceof PositionSensorVRDevice ) {

				vrInputs.push( devices[ i ] );

			}

		}

		if ( onError ) onError( 'HMD not available' );

	}

	if ( navigator.getVRDevices ) {

		navigator.getVRDevices().then( gotVRDevices );

	}

	// the Rift SDK returns the position in meters
	// this scale factor allows the user to define how meters
	// are converted to scene units.

	this.scale = 1;

	this.update = function () {

		for ( var i = 0; i < vrInputs.length; i ++ ) {

			var vrInput = vrInputs[ i ];

			var state = vrInput.getState();

			if ( state.orientation !== null ) {

				object.quaternion.copy( state.orientation );

			}

			if ( state.position !== null ) {

				object.position.copy( state.position ).multiplyScalar( scope.scale );

			}

		}

	};

	this.resetSensor = function () {

		for ( var i = 0; i < vrInputs.length; i ++ ) {

			var vrInput = vrInputs[ i ];

			if ( vrInput.resetSensor !== undefined ) {

				vrInput.resetSensor();

			} else if ( vrInput.zeroSensor !== undefined ) {

				vrInput.zeroSensor();

			}

		}

	};

	this.zeroSensor = function () {

		THREE.warn( 'THREE.VRControls: .zeroSensor() is now .resetSensor().' );
		this.resetSensor();

	};

};
<commit_msg>Fix for Firefox WebVR (again)<commit_after>/**
 * @author dmarcos / https://github.com/dmarcos
 * @author mrdoob / http://mrdoob.com
 */

THREE.VRControls = function ( object, onError ) {

	var scope = this;

	var vrInputs = [];

	function filterInvalidDevices( devices ) {

		// Exclude Cardboard position sensor if Oculus exists.

		var oculusDevices = devices.filter( function ( device ) {

			return device.deviceName.toLowerCase().indexOf('oculus') !== -1;

		} );

		if ( oculusDevices.length >= 1 ) {

			return devices.filter( function ( device ) {

				return device.deviceName.toLowerCase().indexOf('cardboard') === -1;

			} );

		} else {

			return devices;

		}

	}

	function gotVRDevices( devices ) {

		devices = filterInvalidDevices( devices );

		for ( var i = 0; i < devices.length; i ++ ) {

			if ( devices[ i ] instanceof PositionSensorVRDevice ) {

				vrInputs.push( devices[ i ] );

			}

		}

		if ( onError ) onError( 'HMD not available' );

	}

	if ( navigator.getVRDevices ) {

		navigator.getVRDevices().then( gotVRDevices );

	}

	// the Rift SDK returns the position in meters
	// this scale factor allows the user to define how meters
	// are converted to scene units.

	this.scale = 1;

	this.update = function () {

		for ( var i = 0; i < vrInputs.length; i ++ ) {

			var vrInput = vrInputs[ i ];

			var state = vrInput.getState();

			if ( state.orientation !== null ) {

				object.quaternion.copy( state.orientation );

			}

			if ( state.position !== null ) {

				object.position.copy( state.position ).multiplyScalar( scope.scale );

			}

		}

	};

	this.resetSensor = function () {

		for ( var i = 0; i < vrInputs.length; i ++ ) {

			var vrInput = vrInputs[ i ];

			if ( vrInput.resetSensor !== undefined ) {

				vrInput.resetSensor();

			} else if ( vrInput.zeroSensor !== undefined ) {

				vrInput.zeroSensor();

			}

		}

	};

	this.zeroSensor = function () {

		THREE.warn( 'THREE.VRControls: .zeroSensor() is now .resetSensor().' );
		this.resetSensor();

	};

};
<|endoftext|>


<commit_before>
function websocket (env, server, entries, treatments) {
"use strict";
// CONSTANTS
var ONE_HOUR = 3600000,
    ONE_MINUTE = 60000,
    FIVE_MINUTES = 300000,
    FORTY_MINUTES = 2400000,
    TWO_DAYS = 172800000;

var dir2Char = {
  'NONE': '⇼',
  'DoubleUp': '⇈',
  'SingleUp': '↑',
  'FortyFiveUp': '↗',
  'Flat': '→',
  'FortyFiveDown': '↘',
  'SingleDown': '↓',
  'DoubleDown': '⇊',
  'NOT COMPUTABLE': '-',
  'RATE OUT OF RANGE': '↮'
};

  var io;
  var watchers = 0;
  var now = new Date().getTime();

  var cgmData = [],
    treatmentData = [],
    mbgData = [],
    calData = [],
    patientData = [];

  function start ( ) {
    io = require('socket.io').listen(server);
  }
  // get data from database and setup to update every minute
  function kickstart (fn) {
    //TODO: test server to see how data is stored (timestamps, entry values, etc)
    //TODO: check server settings to configure alerts, entry units, etc
    console.log(now, new Date(now), fn.name);
    fn( );
    return fn;
  }

  function emitData ( ) {
    console.log('running emitData', now, patientData && patientData.length);
    if (patientData.length > 0) {
      io.sockets.emit("sgv", patientData);
    }
  }

  function configure ( ) {
    // reduce logging
    io.set('log level', 0);

    //TODO: make websockets support an option
    io.configure(function () {
        io.set('transports', ['xhr-polling']);
    });
  }

  function listeners ( ) {
    io.sockets.on('connection', function (socket) {
        io.sockets.emit("sgv", patientData);
        io.sockets.emit("clients", ++watchers);
        socket.on('ack', function(alarmType, silenceTime) {
            ackAlarm(alarmType, silenceTime);
            if (alarmType == "urgent_alarm") {
              //also clean normal alarm so we don't get a double alarm as BG comes back into range
              ackAlarm("alarm", silenceTime);
            }
            io.sockets.emit("clear_alarm", true);
            console.log("alarm cleared");
        });
        socket.on('disconnect', function () {
            io.sockets.emit("clients", --watchers);
        });
    });
  }

///////////////////////////////////////////////////
// data handling functions
///////////////////////////////////////////////////

function directionToChar(direction) {
  return dir2Char[direction] || '-';
}

var Alarm = function(_typeName, _threshold) {
    this.typeName = _typeName;
    this.silenceTime = FORTY_MINUTES;
    this.lastAckTime = 0;
    this.threshold = _threshold;
};

// list of alarms with their thresholds
var alarms = {
    "alarm" : new Alarm("Regular", 0.05),
    "urgent_alarm": new Alarm("Urgent", 0.10),
    "LowLowAlarm": new Alarm("Low_Low_Alarm", 60),
    "LowAlarm": new Alarm("Low_Alarm", 90),
    "LowFallingAlarm": new Alarm("Low_Falling_Alarm", 180),
    "HighAlarmDay": new Alarm("High_Alarm_Day", 230),
    "HighAlarmNight": new Alarm("High_Alarm_Night", 260),
    "HighRiseAlarm": new Alarm("High_Rise_Alarm", 300)
};

function ackAlarm(alarmType, silenceTime) {
    alarms[alarmType].lastAckTime = new Date().getTime();
    alarms[alarmType].silenceTime = silenceTime ? silenceTime : FORTY_MINUTES;
    delete alarms[alarmType].lastEmitTime;
}

//should only be used when auto acking the alarms after going back in range or when an error corrects
//setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes
//since this wasn't ack'd by a user action
function autoAckAlarms() {
    var sendClear = false;
    for (var alarmType in alarms) {
        if (alarms.hasOwnProperty(alarmType)) {
            var alarm = alarms[alarmType];
            if (alarm.lastEmitTime) {
                console.info("auto acking " + alarmType);
                ackAlarm(alarmType, 1);
                sendClear = true;
            }
        }
    }
    if (sendClear) {
        io.sockets.emit("clear_alarm", true);
        console.info("emitted clear_alarm to all clients");
    }
}

function emitAlarm(alarmType) {
    var alarm = alarms[alarmType];
    if (now > alarm.lastAckTime + alarm.silenceTime) {
        io.sockets.emit(alarmType);
        alarm.lastEmitTime = now;
        console.info("emitted " + alarmType + " to all clients");
    } else {
        console.log(alarm.typeName + " alarm is silenced for " + Math.floor((alarm.silenceTime - (now - alarm.lastAckTime)) / 60000) + " minutes more");
    }
}

function update() {

    console.log('running update');
    now = Date.now();

    cgmData = [];
    treatmentData = [];
    mbgData = [];
    var earliest_data = now - TWO_DAYS;
    var q = { find: {"date": {"$gte": earliest_data}} };
    entries.list(q, function (err, results) {
      results.forEach(function(element, index, array) {
          if (element) {
              if (element.mbg) {
                  var obj = {};
                  obj.y = element.mbg;
                  obj.x = element.date;
                  obj.d = element.dateString;
                  obj.device = element.device;
                  mbgData.push(obj);
              } else if (element.sgv) {
                  var obj = {};
                  obj.y = element.sgv;
                  obj.x = element.date;
                  obj.d = element.dateString;
                  obj.device = element.device;
                  obj.direction = directionToChar(element.direction);
                  obj.filtered = element.filtered;
                  obj.unfiltered = element.unfiltered;
                  obj.noise = element.noise;
                  obj.rssi = element.rssi;
                  cgmData.push(obj);
              } else if (element.slope) {
                  var obj = {};
                  obj.x = element.date;
                  obj.d = element.dateString;
                  obj.scale = element.scale;
                  obj.intercept = element.intercept;
                  obj.slope = element.slope;
                  calData.push(obj);
              }
          }
      });
      var tq = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} };
      treatments.list(tq, function (err, results) {
          treatmentData = results.map(function(treatment) {
              var timestamp = new Date(treatment.timestamp || treatment.created_at);
              treatment.x = timestamp.getTime();
              return treatment;
          });
          // all done, do loadData
          loadData( );
      });
    });

    return update;
}

function loadData() {

    console.log('running loadData');

    var actual = [],
        actualCurrent,
        treatment = [],
        mbg = [],
        cal = [],
        errorCode;

    if (cgmData) {
        actual = cgmData.slice();
        actual.sort(function(a, b) {
            return a.x - b.x;
        });

        actualCurrent = actual.length > 0 ? actual[actual.length - 1].y : null;
    }

    if (treatmentData) {
        treatment = treatmentData.slice();
        treatment.sort(function(a, b) {
            return a.x - b.x;
        });
    }

    if (mbgData) {
        mbg = mbgData.slice();
        mbg.sort(function(a, b) {
            return a.x - b.x;
        });
    }

    if (calData) {
        cal = calData.slice(calData.length-200, calData.length);
        cal.sort(function(a, b) {
            return a.x - b.x;
        });
    }

    if (actualCurrent && actualCurrent < 39) errorCode = actualCurrent;

    var actualLength = actual.length - 1;

    if (actualLength > 1) {
        // predict using AR model
        var predicted = [];
        var lastValidReadingTime = actual[actualLength].x;
        var elapsedMins = (actual[actualLength].x - actual[actualLength - 1].x) / ONE_MINUTE;
        var BG_REF = 140;
        var BG_MIN = 36;
        var BG_MAX = 400;
        var y = Math.log(actual[actualLength].y / BG_REF);
        if (elapsedMins < 5.1) {
            y = [Math.log(actual[actualLength - 1].y / BG_REF), y];
        } else {
            y = [y, y];
        }
        var n = Math.ceil(12 * (1 / 2 + (now - lastValidReadingTime) / ONE_HOUR));
        var AR = [-0.723, 1.716];
        var dt = actual[actualLength].x;
        for (var i = 0; i <= n; i++) {
            y = [y[1], AR[0] * y[0] + AR[1] * y[1]];
            dt = dt + FIVE_MINUTES;
            predicted[i] = {
                x: dt,
                y: Math.max(BG_MIN, Math.min(BG_MAX, Math.round(BG_REF * Math.exp(y[1]))))
            };
        }

        //TODO: need to consider when data being sent has less than the 2 day minimum

        // consolidate and send the data to the client
        var shouldEmit = is_different(actual, predicted, mbg, treatment, cal);
        patientData = [actual, predicted, mbg, treatment, cal];
        console.log('patientData', patientData.length);
        if (shouldEmit) {
          emitData( );
        }

        //var emitAlarmType = null;

        //if (env.alarm_types.indexOf("simple") > -1) {
        //    var lastBG = actual[actualLength].y;

        //    if (lastBG > env.thresholds.bg_high) {
        //        emitAlarmType = 'urgent_alarm';
        //        console.info(lastBG + " > " + env.thresholds.bg_high + " will emmit " + emitAlarmType);
        //    } else if (lastBG > env.thresholds.bg_target_top) {
        //        emitAlarmType = 'alarm';
        //        console.info(lastBG + " > " + env.thresholds.bg_target_top + " will emmit " + emitAlarmType);
        //    } else if (lastBG < env.thresholds.bg_low) {
        //        emitAlarmType = 'urgent_alarm';
        //        console.info(lastBG + " < " + env.thresholds.bg_low + " will emmit " + emitAlarmType);
        //    } else if (lastBG < env.thresholds.bg_target_bottom) {
        //        emitAlarmType = 'alarm';
        //        console.info(lastBG + " < " + env.thresholds.bg_target_bottom + " will emmit " + emitAlarmType);
        //    }
        //}

        //if (!emitAlarmType && env.alarm_types.indexOf("predict") > -1) {
            // compute current loss
        //    var avgLoss = 0;
        //    var size = Math.min(predicted.length - 1, 6);
        //    for (var j = 0; j <= size; j++) {
        //        avgLoss += 1 / size * Math.pow(log10(predicted[j].y / 120), 2);
        //    }

        //    if (avgLoss > alarms['urgent_alarm'].threshold) {
        //        emitAlarmType = 'urgent_alarm';
        //        console.info(avgLoss + " < " + alarms['urgent_alarm'].threshold + " will emmit " + emitAlarmType);
        //    } else if (avgLoss > alarms['alarm'].threshold) {
        //        emitAlarmType = 'alarm';
        //        console.info(avgLoss + " < " + alarms['alarm'].threshold + " will emmit " + emitAlarmType);
        //    }
        //}

        //if (errorCode) {
        //    emitAlarmType = 'urgent_alarm';
        //}

        //if (emitAlarmType) {
        //    emitAlarm(emitAlarmType);
        //} else {
        //    autoAckAlarms();
        //}

        var currentBG = actual[actualLength].y;
        var hourNow = new Date().getHours();
        var currentDirection = actual[actualLength].direction;
        var minLastReading = (now - lastValidReadingTime)/60/1000;
        //console.log("now: " + now);
        //console.log("lastValidReadingTime: " + lastValidReadingTime);
        //console.log("minLastReading: " + minLastReading);

        if(errorCode == 'false' && minLastReading > 21){
            emitAlarm('alarm');
        }else if (currentBG < alarms['LowLowAlarm'].threshold){
            emitAlarm('LowLowAlarm');
        }else if(currentBG < alarms['LowAlarm'].threshold){
            emitAlarm('LowAlarm');
        }else if(currentBG < alarms['LowFallingAlarm'].threshold && currentDirection == '⇊'){
            emitAlarm('LowFallingAlarm');
        }else if(currentBG > alarms['HighRiseAlarm'].threshold && currentDirection == '⇈'){
            emitAlarm('HighRiseAlarm');
        //hour run on server and are 6 hours ahead for 8am to 8pm is 14 to 2
        }else if(currentBG > alarms['HighAlarmDay'].threshold && hourNow > 14){
            emitAlarm('HighAlarmDay');
        }else if(currentBG > alarms['HighAlarmDay'].threshold && hourNow < 2){
            emitAlarm('HighAlarmDay');
        }else if(currentBG > alarms['HighAlarmNight'].threshold){
            emitAlarm('HighAlarmNight');
        }else if (errorCode) {
            emitAlarm('urgent_alarm');
        }else if (minLastReading > 21){
            emitAlarm('alarm');
        }
    }
}

  function is_different (actual, predicted, mbg, treatment, cal) {
    if (patientData && patientData.length < 3) {
      return true;
    }
    var old =  {
        actual: patientData[0].slice(-1).pop( )
      , predicted: patientData[1].slice(-1).pop( )
      , mbg: patientData[2].slice(-1).pop( )
      , treatment: patientData[3].slice(-1).pop( )
      , cal: patientData[4].slice(-1).pop( )
    };
    var last = {
        actual: actual.slice(-1).pop( )
      , predicted: predicted.slice(-1).pop( )
      , mbg: mbg.slice(-1).pop( )
      , treatment: treatment.slice(-1).pop( )
      , cal: cal.slice(-1).pop( )
    };

    // textual diff of objects
    if (JSON.stringify(old) == JSON.stringify(last)) {
      console.info("data isn't different, will not send to clients");
      return false;
    }
    return true;
  }

  start( );
  configure( );
  listeners( );
  setInterval(kickstart(update), ONE_MINUTE);

  return io;
}

///////////////////////////////////////////////////
// helper functions
///////////////////////////////////////////////////

function log10(val) { return Math.log(val) / Math.LN10; }

module.exports = websocket;
<commit_msg>Update websocket.js<commit_after>
function websocket (env, server, entries, treatments) {
"use strict";
// CONSTANTS
var ONE_HOUR = 3600000,
    ONE_MINUTE = 60000,
    FIVE_MINUTES = 300000,
    FORTY_MINUTES = 2400000,
    TWO_DAYS = 172800000;

var dir2Char = {
  'NONE': '⇼',
  'DoubleUp': '⇈',
  'SingleUp': '↑',
  'FortyFiveUp': '↗',
  'Flat': '→',
  'FortyFiveDown': '↘',
  'SingleDown': '↓',
  'DoubleDown': '⇊',
  'NOT COMPUTABLE': '-',
  'RATE OUT OF RANGE': '↮'
};

  var io;
  var watchers = 0;
  var now = new Date().getTime();

  var cgmData = [],
    treatmentData = [],
    mbgData = [],
    calData = [],
    patientData = [];

  function start ( ) {
    io = require('socket.io').listen(server);
  }
  // get data from database and setup to update every minute
  function kickstart (fn) {
    //TODO: test server to see how data is stored (timestamps, entry values, etc)
    //TODO: check server settings to configure alerts, entry units, etc
    console.log(now, new Date(now), fn.name);
    fn( );
    return fn;
  }

  function emitData ( ) {
    console.log('running emitData', now, patientData && patientData.length);
    if (patientData.length > 0) {
      io.sockets.emit("sgv", patientData);
    }
  }

  function configure ( ) {
    // reduce logging
    io.set('log level', 0);

    //TODO: make websockets support an option
    io.configure(function () {
        io.set('transports', ['xhr-polling']);
    });
  }

  function listeners ( ) {
    io.sockets.on('connection', function (socket) {
        io.sockets.emit("sgv", patientData);
        io.sockets.emit("clients", ++watchers);
        socket.on('ack', function(alarmType, silenceTime) {
            ackAlarm(alarmType, silenceTime);
            if (alarmType == "urgent_alarm") {
              //also clean normal alarm so we don't get a double alarm as BG comes back into range
              ackAlarm("alarm", silenceTime);
            }
            io.sockets.emit("clear_alarm", true);
            console.log("alarm cleared");
        });
        socket.on('disconnect', function () {
            io.sockets.emit("clients", --watchers);
        });
    });
  }

///////////////////////////////////////////////////
// data handling functions
///////////////////////////////////////////////////

function directionToChar(direction) {
  return dir2Char[direction] || '-';
}

var Alarm = function(_typeName, _threshold) {
    this.typeName = _typeName;
    this.silenceTime = FORTY_MINUTES;
    this.lastAckTime = 0;
    this.threshold = _threshold;
};

// list of alarms with their thresholds
var alarms = {
    "alarm" : new Alarm("Regular", 0.05),
    "urgent_alarm": new Alarm("Urgent", 0.10),
    "LowLowAlarm": new Alarm("Low_Low_Alarm", 60),
    "LowAlarm": new Alarm("Low_Alarm", 90),
    "LowFallingAlarm": new Alarm("Low_Falling_Alarm", 180),
    "HighAlarmDay": new Alarm("High_Alarm_Day", 240),
    "HighAlarmNight": new Alarm("High_Alarm_Night", 260),
    "HighRiseAlarm": new Alarm("High_Rise_Alarm", 300)
};

function ackAlarm(alarmType, silenceTime) {
    alarms[alarmType].lastAckTime = new Date().getTime();
    alarms[alarmType].silenceTime = silenceTime ? silenceTime : FORTY_MINUTES;
    delete alarms[alarmType].lastEmitTime;
}

//should only be used when auto acking the alarms after going back in range or when an error corrects
//setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes
//since this wasn't ack'd by a user action
function autoAckAlarms() {
    var sendClear = false;
    for (var alarmType in alarms) {
        if (alarms.hasOwnProperty(alarmType)) {
            var alarm = alarms[alarmType];
            if (alarm.lastEmitTime) {
                console.info("auto acking " + alarmType);
                ackAlarm(alarmType, 1);
                sendClear = true;
            }
        }
    }
    if (sendClear) {
        io.sockets.emit("clear_alarm", true);
        console.info("emitted clear_alarm to all clients");
    }
}

function emitAlarm(alarmType) {
    var alarm = alarms[alarmType];
    if (now > alarm.lastAckTime + alarm.silenceTime) {
        io.sockets.emit(alarmType);
        alarm.lastEmitTime = now;
        console.info("emitted " + alarmType + " to all clients");
    } else {
        console.log(alarm.typeName + " alarm is silenced for " + Math.floor((alarm.silenceTime - (now - alarm.lastAckTime)) / 60000) + " minutes more");
    }
}

function update() {

    console.log('running update');
    now = Date.now();

    cgmData = [];
    treatmentData = [];
    mbgData = [];
    var earliest_data = now - TWO_DAYS;
    var q = { find: {"date": {"$gte": earliest_data}} };
    entries.list(q, function (err, results) {
      results.forEach(function(element, index, array) {
          if (element) {
              if (element.mbg) {
                  var obj = {};
                  obj.y = element.mbg;
                  obj.x = element.date;
                  obj.d = element.dateString;
                  obj.device = element.device;
                  mbgData.push(obj);
              } else if (element.sgv) {
                  var obj = {};
                  obj.y = element.sgv;
                  obj.x = element.date;
                  obj.d = element.dateString;
                  obj.device = element.device;
                  obj.direction = directionToChar(element.direction);
                  obj.filtered = element.filtered;
                  obj.unfiltered = element.unfiltered;
                  obj.noise = element.noise;
                  obj.rssi = element.rssi;
                  cgmData.push(obj);
              } else if (element.slope) {
                  var obj = {};
                  obj.x = element.date;
                  obj.d = element.dateString;
                  obj.scale = element.scale;
                  obj.intercept = element.intercept;
                  obj.slope = element.slope;
                  calData.push(obj);
              }
          }
      });
      var tq = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} };
      treatments.list(tq, function (err, results) {
          treatmentData = results.map(function(treatment) {
              var timestamp = new Date(treatment.timestamp || treatment.created_at);
              treatment.x = timestamp.getTime();
              return treatment;
          });
          // all done, do loadData
          loadData( );
      });
    });

    return update;
}

function loadData() {

    console.log('running loadData');

    var actual = [],
        actualCurrent,
        treatment = [],
        mbg = [],
        cal = [],
        errorCode;

    if (cgmData) {
        actual = cgmData.slice();
        actual.sort(function(a, b) {
            return a.x - b.x;
        });

        actualCurrent = actual.length > 0 ? actual[actual.length - 1].y : null;
    }

    if (treatmentData) {
        treatment = treatmentData.slice();
        treatment.sort(function(a, b) {
            return a.x - b.x;
        });
    }

    if (mbgData) {
        mbg = mbgData.slice();
        mbg.sort(function(a, b) {
            return a.x - b.x;
        });
    }

    if (calData) {
        cal = calData.slice(calData.length-200, calData.length);
        cal.sort(function(a, b) {
            return a.x - b.x;
        });
    }

    if (actualCurrent && actualCurrent < 39) errorCode = actualCurrent;

    var actualLength = actual.length - 1;

    if (actualLength > 1) {
        // predict using AR model
        var predicted = [];
        var lastValidReadingTime = actual[actualLength].x;
        var elapsedMins = (actual[actualLength].x - actual[actualLength - 1].x) / ONE_MINUTE;
        var BG_REF = 140;
        var BG_MIN = 36;
        var BG_MAX = 400;
        var y = Math.log(actual[actualLength].y / BG_REF);
        if (elapsedMins < 5.1) {
            y = [Math.log(actual[actualLength - 1].y / BG_REF), y];
        } else {
            y = [y, y];
        }
        var n = Math.ceil(12 * (1 / 2 + (now - lastValidReadingTime) / ONE_HOUR));
        var AR = [-0.723, 1.716];
        var dt = actual[actualLength].x;
        for (var i = 0; i <= n; i++) {
            y = [y[1], AR[0] * y[0] + AR[1] * y[1]];
            dt = dt + FIVE_MINUTES;
            predicted[i] = {
                x: dt,
                y: Math.max(BG_MIN, Math.min(BG_MAX, Math.round(BG_REF * Math.exp(y[1]))))
            };
        }

        //TODO: need to consider when data being sent has less than the 2 day minimum

        // consolidate and send the data to the client
        var shouldEmit = is_different(actual, predicted, mbg, treatment, cal);
        patientData = [actual, predicted, mbg, treatment, cal];
        console.log('patientData', patientData.length);
        if (shouldEmit) {
          emitData( );
        }

        //var emitAlarmType = null;

        //if (env.alarm_types.indexOf("simple") > -1) {
        //    var lastBG = actual[actualLength].y;

        //    if (lastBG > env.thresholds.bg_high) {
        //        emitAlarmType = 'urgent_alarm';
        //        console.info(lastBG + " > " + env.thresholds.bg_high + " will emmit " + emitAlarmType);
        //    } else if (lastBG > env.thresholds.bg_target_top) {
        //        emitAlarmType = 'alarm';
        //        console.info(lastBG + " > " + env.thresholds.bg_target_top + " will emmit " + emitAlarmType);
        //    } else if (lastBG < env.thresholds.bg_low) {
        //        emitAlarmType = 'urgent_alarm';
        //        console.info(lastBG + " < " + env.thresholds.bg_low + " will emmit " + emitAlarmType);
        //    } else if (lastBG < env.thresholds.bg_target_bottom) {
        //        emitAlarmType = 'alarm';
        //        console.info(lastBG + " < " + env.thresholds.bg_target_bottom + " will emmit " + emitAlarmType);
        //    }
        //}

        //if (!emitAlarmType && env.alarm_types.indexOf("predict") > -1) {
            // compute current loss
        //    var avgLoss = 0;
        //    var size = Math.min(predicted.length - 1, 6);
        //    for (var j = 0; j <= size; j++) {
        //        avgLoss += 1 / size * Math.pow(log10(predicted[j].y / 120), 2);
        //    }

        //    if (avgLoss > alarms['urgent_alarm'].threshold) {
        //        emitAlarmType = 'urgent_alarm';
        //        console.info(avgLoss + " < " + alarms['urgent_alarm'].threshold + " will emmit " + emitAlarmType);
        //    } else if (avgLoss > alarms['alarm'].threshold) {
        //        emitAlarmType = 'alarm';
        //        console.info(avgLoss + " < " + alarms['alarm'].threshold + " will emmit " + emitAlarmType);
        //    }
        //}

        //if (errorCode) {
        //    emitAlarmType = 'urgent_alarm';
        //}

        //if (emitAlarmType) {
        //    emitAlarm(emitAlarmType);
        //} else {
        //    autoAckAlarms();
        //}

        var currentBG = actual[actualLength].y;
        var hourNow = new Date().getHours();
        var currentDirection = actual[actualLength].direction;
        var minLastReading = (now - lastValidReadingTime)/60/1000;
        //console.log("now: " + now);
        //console.log("lastValidReadingTime: " + lastValidReadingTime);
        //console.log("minLastReading: " + minLastReading);

        if(errorCode == 'false' && minLastReading > 21){
            emitAlarm('alarm');
        }else if (currentBG < alarms['LowLowAlarm'].threshold){
            emitAlarm('LowLowAlarm');
        }else if(currentBG < alarms['LowAlarm'].threshold){
            emitAlarm('LowAlarm');
        }else if(currentBG < alarms['LowFallingAlarm'].threshold && currentDirection == '⇊'){
            emitAlarm('LowFallingAlarm');
        }else if(currentBG > alarms['HighRiseAlarm'].threshold && currentDirection == '⇈'){
            emitAlarm('HighRiseAlarm');
        //hour run on server and are 6 hours ahead for 8am to 8pm is 14 to 2
        }else if(currentBG > alarms['HighAlarmDay'].threshold && hourNow > 14){
            emitAlarm('HighAlarmDay');
        }else if(currentBG > alarms['HighAlarmDay'].threshold && hourNow < 2){
            emitAlarm('HighAlarmDay');
        }else if(currentBG > alarms['HighAlarmNight'].threshold){
            emitAlarm('HighAlarmNight');
        }else if (errorCode) {
            emitAlarm('urgent_alarm');
        }else if (minLastReading > 21){
            emitAlarm('alarm');
        }
    }
}

  function is_different (actual, predicted, mbg, treatment, cal) {
    if (patientData && patientData.length < 3) {
      return true;
    }
    var old =  {
        actual: patientData[0].slice(-1).pop( )
      , predicted: patientData[1].slice(-1).pop( )
      , mbg: patientData[2].slice(-1).pop( )
      , treatment: patientData[3].slice(-1).pop( )
      , cal: patientData[4].slice(-1).pop( )
    };
    var last = {
        actual: actual.slice(-1).pop( )
      , predicted: predicted.slice(-1).pop( )
      , mbg: mbg.slice(-1).pop( )
      , treatment: treatment.slice(-1).pop( )
      , cal: cal.slice(-1).pop( )
    };

    // textual diff of objects
    if (JSON.stringify(old) == JSON.stringify(last)) {
      console.info("data isn't different, will not send to clients");
      return false;
    }
    return true;
  }

  start( );
  configure( );
  listeners( );
  setInterval(kickstart(update), ONE_MINUTE);

  return io;
}

///////////////////////////////////////////////////
// helper functions
///////////////////////////////////////////////////

function log10(val) { return Math.log(val) / Math.LN10; }

module.exports = websocket;
<|endoftext|>


<commit_before>
whitelist.knownRemotes = {
    '207.97.227.253',
    '50.57.128.197',
    '108.171.174.178'
};

function whitelist( options )
{
    options = options || {};

    var ips = options.ips || [];

    if( !ips.length || options.known ) {
        ips.concat( whitelist.knownRemotes );
    }

    return function( req, res, next )
    {
        var err;

        if( ips.indexOf( req.socket.remoteAddress ) < 0 ) {
            err = new Error( '[bait] whitelist: Remote is not whitelisted (' + req.socket.remoteAddress + ').' );
            err.status = 403;
        }

        next( err );
    };
};

module.exports = whitelist;
<commit_msg>Fix a horrible typo.<commit_after>
whitelist.knownRemotes = [
    '207.97.227.253',
    '50.57.128.197',
    '108.171.174.178'
];

function whitelist( options )
{
    options = options || {};

    var ips = options.ips || [];

    if( !ips.length || options.known ) {
        ips.concat( whitelist.knownRemotes );
    }

    return function( req, res, next )
    {
        var err;

        if( ips.indexOf( req.socket.remoteAddress ) < 0 ) {
            err = new Error( '[bait] whitelist: Remote is not whitelisted (' + req.socket.remoteAddress + ').' );
            err.status = 403;
        }

        next( err );
    };
};

module.exports = whitelist;
<|endoftext|>


<commit_before>import '/resources/components/postal-lib.js';

import { TgEntityMasterBehavior } from '/resources/master/tg-entity-master-behavior.js';
import { queryElements } from '/resources/components/tg-element-selector-behavior.js';
import { IronA11yKeysBehavior } from '/resources/polymer/@polymer/iron-a11y-keys-behavior/iron-a11y-keys-behavior.js';
import { tearDownEvent, deepestActiveElement, getActiveParentAnd, FOCUSABLE_ELEMENTS_SELECTOR } from '/resources/reflection/tg-polymer-utils.js';

const TgEgiMasterBehaviorImpl = {

    properties :{
        editors: Array,
        saveButton: Object,
        cancelButton: Object,
        focusLastOnRetrieve: {
            type: Boolean,
            value: false
        },
        _fixedMasterContainer: {
            type: Object,
            observer: "_fixedMasterContainerChanged"
        },
        _scrollableMasterContainer: {
            type: Object,
            observer: "_scrollableMasterContainerChanged"
        },
        _editNextRow: Function,
        _editPreviousRow: Function,
        _acceptValues: Function,
        //_cancelValues: Function,
        _closeMaster: Function,
        _lastFocusedEditor: Object,
        _shouldEditNextRow: {
            type: Boolean,
            value :false
        },
        _shouldEditPreviousRow: {
            type: Boolean,
            value: false
        }
    },

    created: function() {
        this.noUI = false;
        this.saveOnActivation = false;
    },

    ready: function () {
        this.editors = [...this._masterDom().querySelectorAll('[tg-editor]')];
        this.saveButton = this._masterDom().querySelector('.master-save-action');
        this.cancelButton = this._masterDom().querySelector('.master-cancel-action');
        this.saveButton.removeAttribute("selectable-elements-container");
        this.cancelButton.removeAttribute("selectable-elements-container");

        this.closeMaster = this.closeMaster.bind(this);

        this.editors.forEach(editor => editor.decorator().noLabelFloat = true);
        this.addEventListener('data-loaded-and-focused', this._selectLastFocusedEditor.bind(this));
        this.addEventListener('focusin', this._editorFocused.bind(this));

        this.postSaved = function (potentiallySavedOrNewEntity) {
            if (potentiallySavedOrNewEntity.isValid()) {
                this._acceptValues();
                if (this._shouldEditNextRow) {
                    this._shouldEditNextRow = false;
                    this._editNextRow();
                } else if (this._shouldEditPreviousRow) {
                    this._shouldEditPreviousRow = false;
                    this._editPreviousRow();
                } else {
                    this._closeMaster();
                }
                postal.publish({
                    channel: "centre_" + this.centreUuid,
                    topic: "detail.saved",
                    data: {
                        shouldRefreshParentCentreAfterSave: true,
                        entity: potentiallySavedOrNewEntity,
                        // send selectedEntitiesInContext further to be able to update only them on EGI
                        selectedEntitiesInContext: [potentiallySavedOrNewEntity]
                    }
                });
            }
        }
    },

    closeMaster: function () {
        this._closeMaster();
        this._postClose();
    },

    getEditors: function () {
        const focusableElemnts = this._lastFocusedEditor ? [this._lastFocusedEditor] :
                                [...this._fixedMasterContainer.querySelectorAll("slot"), ...this._scrollableMasterContainer.querySelectorAll("slot")]
                                .filter(slot => slot.assignedNodes().length > 0)
                                .map(slot => slot.assignedNodes()[0]).filter(element => element.hasAttribute("tg-editor"));
        this._lastFocusedEditor = null;
        if (this.focusLastOnRetrieve) {
            return focusableElemnts.reverse();
        }
        return focusableElemnts;
    },

    _postClose: function () {

    },

    // doNotValidate: function () {
    //     if (this.postValidated) {
    //         this.postValidated();
    //     }
    // },

    // _postValidatedDefaultError: function (error) {
    //     if (this.postValidated) {
    //         this.postValidated();
    //     }
    // },

    _selectLastFocusedEditor: function (e) {
        const focusedElement = deepestActiveElement();
        if (focusedElement && typeof focusedElement.select === "function") {
            focusedElement.select();
        }
    },

    _editorFocused: function (e) {
        console.log("Editor focused", e.target);
    },

    //Event listeners
    _fixedMasterContainerChanged: function (newContainer, oldContainer) {
        this._masterContainerChanged(newContainer, oldContainer);
    },

    _scrollableMasterContainerChanged: function (newContainer, oldContainer) {
        this._masterContainerChanged(newContainer, oldContainer);
    },

    _masterContainerChanged: function (newContainer, oldContainer) {
        this._onCaptureKeyDown = this._onCaptureKeyDown.bind(this);
        if (oldContainer) {
            oldContainer.removeEventListener('keydown', this._onCaptureKeyDown, true);
        }
        if (newContainer) {
            newContainer.addEventListener('keydown', this._onCaptureKeyDown, true);
        }
    },

    _onCaptureKeyDown: function(event) {
        if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'tab')) {
            if (event.shiftKey) {
                this._onShiftTabDown(event);
            } else {
                this._onTabDown(event);
            }
        } else if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc')) {
            this._closeMaster();
        } else if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'enter')) {
            this._lastFocusedEditor = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            this._saveAndEditNextRow();
        } else if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'alt+up')) {
            this._lastFocusedEditor = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            this._saveAndEditPreviousRow();
        } else if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'alt+down')) {
            this._lastFocusedEditor = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            this._saveAndEditNextRow();
        }
    },

    _saveAndEditNextRow: function () {
        if (!this.saveButton._disabled) {
            this._shouldEditNextRow = true;
            const editorToCommit = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            editorToCommit.commit();
            this.saveButton._asyncRun();
        } else {
            this._editNextRow();
        }
    },

    _saveAndEditPreviousRow: function () {
        if (!this.saveButton._disabled) {
            this._shouldEditPreviousRow = true;
            const editorToCommit = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            editorToCommit.commit();
            this.saveButton._asyncRun();
        } else {
            this._editPreviousRow();
        }
    },

    _onTabDown: function (event) {
        const activeElement = deepestActiveElement();
        //Check whether active element is in hierarchy of the last fixed editor if it is then focus first editor in scrollable area.
        const fixedEditors = this._getFixedEditors();
        const scrollableEditors = this._getScrollableEditors();
        if (fixedEditors.length > 0 && activeElement === fixedEditors[fixedEditors.length - 1]) {
            if (scrollableEditors.length > 0) {
                scrollableEditors[0].focus();
                if (typeof scrollableEditors[0].select === 'function') {
                    scrollableEditors[0].select();
                }
                tearDownEvent(event);
            }
        //If the last editor in scrollable area is focused then make next row editable
        } else if (scrollableEditors.length > 0 && activeElement === scrollableEditors[scrollableEditors.length - 1]) {
            tearDownEvent(event);
            this._saveAndEditNextRow();
        }
    },

    _onShiftTabDown: function (event) {
        const activeElement = deepestActiveElement();
        //Check whether active element is in hierarchy of the first scrollable editor if it is then focus last editor in fixed area.
        const fixedEditors = this._getFixedEditors();
        const scrollableEditors = this._getScrollableEditors();
        if (scrollableEditors.length > 0 && activeElement === scrollableEditors[0]) {
            if (fixedEditors.length > 0) {
                fixedEditors[fixedEditors.length - 1].focus();
                if (typeof fixedEditors[fixedEditors.length - 1].select === 'function') {
                    fixedEditors[fixedEditors.length - 1].select();
                }
                tearDownEvent(event);
            }
        //If the first editor in fixed area is focused then make previous row editable
        } else if (fixedEditors.length > 0 && activeElement === fixedEditors[0]) {
            tearDownEvent(event);
            this._saveAndEditPreviousRow();
        }
    },

    _getFixedEditors: function () {
        return queryElements(this._fixedMasterContainer, FOCUSABLE_ELEMENTS_SELECTOR).filter(element => !element.disabled && element.offsetParent !== null);

    },

    _getScrollableEditors: function () {
        return queryElements(this._scrollableMasterContainer, FOCUSABLE_ELEMENTS_SELECTOR).filter(element => !element.disabled && element.offsetParent !== null);
    },

    _masterDom: function () {
        return this.$.masterDom;
    },

    /**
     * The core-ajax component for entity retrieval.
     */
    _ajaxRetriever: function () {
        return this._masterDom()._ajaxRetriever();
    },

    /**
     * The core-ajax component for entity saving.
     */
    _ajaxSaver: function () {
        return this._masterDom()._ajaxSaver();
    },

    /**
     * The validator component.
     */
    _validator: function () {
        return this._masterDom()._validator();
    },

    /**
     * The component for entity serialisation.
     */
    _serialiser: function () {
        return this._masterDom()._serialiser();
    },

    /**
     * The reflector component.
     */
    _reflector: function () {
        return this._masterDom()._reflector();
    },

    /**
     * The toast component.
     */
    _toastGreeting: function () {
        return this._masterDom()._toastGreeting();
    }
};

export const TgEgiMasterBehavior = [TgEntityMasterBehavior, TgEgiMasterBehaviorImpl];<commit_msg>#938 Fixed the save functionality that saves the localy invalid entity.<commit_after>import '/resources/components/postal-lib.js';

import { TgEntityMasterBehavior } from '/resources/master/tg-entity-master-behavior.js';
import { queryElements } from '/resources/components/tg-element-selector-behavior.js';
import { IronA11yKeysBehavior } from '/resources/polymer/@polymer/iron-a11y-keys-behavior/iron-a11y-keys-behavior.js';
import { tearDownEvent, deepestActiveElement, getActiveParentAnd, FOCUSABLE_ELEMENTS_SELECTOR } from '/resources/reflection/tg-polymer-utils.js';

const TgEgiMasterBehaviorImpl = {

    properties :{
        editors: Array,
        saveButton: Object,
        cancelButton: Object,
        focusLastOnRetrieve: {
            type: Boolean,
            value: false
        },
        _fixedMasterContainer: {
            type: Object,
            observer: "_fixedMasterContainerChanged"
        },
        _scrollableMasterContainer: {
            type: Object,
            observer: "_scrollableMasterContainerChanged"
        },
        _editNextRow: Function,
        _editPreviousRow: Function,
        _acceptValues: Function,
        //_cancelValues: Function,
        _closeMaster: Function,
        _lastFocusedEditor: Object,
        _shouldEditNextRow: {
            type: Boolean,
            value :false
        },
        _shouldEditPreviousRow: {
            type: Boolean,
            value: false
        }
    },

    created: function() {
        this.noUI = false;
        this.saveOnActivation = false;
    },

    ready: function () {
        this.editors = [...this._masterDom().querySelectorAll('[tg-editor]')];
        this.saveButton = this._masterDom().querySelector('.master-save-action');
        this.cancelButton = this._masterDom().querySelector('.master-cancel-action');
        this.saveButton.removeAttribute("selectable-elements-container");
        this.cancelButton.removeAttribute("selectable-elements-container");

        this.closeMaster = this.closeMaster.bind(this);

        this.editors.forEach(editor => editor.decorator().noLabelFloat = true);
        this.addEventListener('data-loaded-and-focused', this._selectLastFocusedEditor.bind(this));
        this.addEventListener('focusin', this._editorFocused.bind(this));

        this.postSaved = function (potentiallySavedOrNewEntity) {
            if (potentiallySavedOrNewEntity.isValid() && potentiallySavedOrNewEntity.exceptionOccured() === null) {
                this._acceptValues();
                if (this._shouldEditNextRow) {
                    this._shouldEditNextRow = false;
                    this._editNextRow();
                } else if (this._shouldEditPreviousRow) {
                    this._shouldEditPreviousRow = false;
                    this._editPreviousRow();
                } else {
                    this._closeMaster();
                }
                postal.publish({
                    channel: "centre_" + this.centreUuid,
                    topic: "detail.saved",
                    data: {
                        shouldRefreshParentCentreAfterSave: true,
                        entity: potentiallySavedOrNewEntity,
                        // send selectedEntitiesInContext further to be able to update only them on EGI
                        selectedEntitiesInContext: [potentiallySavedOrNewEntity]
                    }
                });
            } else {
                this._lastFocusedEditor = null;
                this.focusLastOnRetrieve = false;
                this._shouldEditNextRow = false;
                this._shouldEditPreviousRow = false;
            }
        }
    },

    closeMaster: function () {
        this._closeMaster();
        this._postClose();
    },

    getEditors: function () {
        const focusableElemnts = this._lastFocusedEditor ? [this._lastFocusedEditor] :
                                [...this._fixedMasterContainer.querySelectorAll("slot"), ...this._scrollableMasterContainer.querySelectorAll("slot")]
                                .filter(slot => slot.assignedNodes().length > 0)
                                .map(slot => slot.assignedNodes()[0]).filter(element => element.hasAttribute("tg-editor"));
        this._lastFocusedEditor = null;
        if (this.focusLastOnRetrieve) {
            return focusableElemnts.reverse();
        }
        return focusableElemnts;
    },

    _postClose: function () {

    },

    // doNotValidate: function () {
    //     if (this.postValidated) {
    //         this.postValidated();
    //     }
    // },

    // _postValidatedDefaultError: function (error) {
    //     if (this.postValidated) {
    //         this.postValidated();
    //     }
    // },

    _selectLastFocusedEditor: function (e) {
        const focusedElement = deepestActiveElement();
        if (focusedElement && typeof focusedElement.select === "function") {
            focusedElement.select();
        }
    },

    _editorFocused: function (e) {
        console.log("Editor focused", e.target);
    },

    //Event listeners
    _fixedMasterContainerChanged: function (newContainer, oldContainer) {
        this._masterContainerChanged(newContainer, oldContainer);
    },

    _scrollableMasterContainerChanged: function (newContainer, oldContainer) {
        this._masterContainerChanged(newContainer, oldContainer);
    },

    _masterContainerChanged: function (newContainer, oldContainer) {
        this._onCaptureKeyDown = this._onCaptureKeyDown.bind(this);
        if (oldContainer) {
            oldContainer.removeEventListener('keydown', this._onCaptureKeyDown, true);
        }
        if (newContainer) {
            newContainer.addEventListener('keydown', this._onCaptureKeyDown, true);
        }
    },

    _onCaptureKeyDown: function(event) {
        if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'tab')) {
            if (event.shiftKey) {
                this._onShiftTabDown(event);
            } else {
                this._onTabDown(event);
            }
        } else if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc')) {
            this._closeMaster();
        } else if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'enter')) {
            this._lastFocusedEditor = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            this._saveAndEditNextRow();
        } else if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'alt+up')) {
            this._lastFocusedEditor = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            this._saveAndEditPreviousRow();
        } else if (IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'alt+down')) {
            this._lastFocusedEditor = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            this._saveAndEditNextRow();
        }
    },

    _saveAndEditNextRow: function () {
        if (!this.saveButton._disabled) {
            this._shouldEditNextRow = true;
            const editorToCommit = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            editorToCommit.commit();
            this.saveButton._asyncRun();
        } else {
            this._editNextRow();
        }
    },

    _saveAndEditPreviousRow: function () {
        if (!this.saveButton._disabled) {
            this._shouldEditPreviousRow = true;
            const editorToCommit = getActiveParentAnd(element => element.hasAttribute('tg-editor'));
            editorToCommit.commit();
            this.saveButton._asyncRun();
        } else {
            this._editPreviousRow();
        }
    },

    _onTabDown: function (event) {
        const activeElement = deepestActiveElement();
        //Check whether active element is in hierarchy of the last fixed editor if it is then focus first editor in scrollable area.
        const fixedEditors = this._getFixedEditors();
        const scrollableEditors = this._getScrollableEditors();
        if (fixedEditors.length > 0 && activeElement === fixedEditors[fixedEditors.length - 1]) {
            if (scrollableEditors.length > 0) {
                scrollableEditors[0].focus();
                if (typeof scrollableEditors[0].select === 'function') {
                    scrollableEditors[0].select();
                }
                tearDownEvent(event);
            }
        //If the last editor in scrollable area is focused then make next row editable
        } else if (scrollableEditors.length > 0 && activeElement === scrollableEditors[scrollableEditors.length - 1]) {
            tearDownEvent(event);
            this._saveAndEditNextRow();
        }
    },

    _onShiftTabDown: function (event) {
        const activeElement = deepestActiveElement();
        //Check whether active element is in hierarchy of the first scrollable editor if it is then focus last editor in fixed area.
        const fixedEditors = this._getFixedEditors();
        const scrollableEditors = this._getScrollableEditors();
        if (scrollableEditors.length > 0 && activeElement === scrollableEditors[0]) {
            if (fixedEditors.length > 0) {
                fixedEditors[fixedEditors.length - 1].focus();
                if (typeof fixedEditors[fixedEditors.length - 1].select === 'function') {
                    fixedEditors[fixedEditors.length - 1].select();
                }
                tearDownEvent(event);
            }
        //If the first editor in fixed area is focused then make previous row editable
        } else if (fixedEditors.length > 0 && activeElement === fixedEditors[0]) {
            tearDownEvent(event);
            this._saveAndEditPreviousRow();
        }
    },

    _getFixedEditors: function () {
        return queryElements(this._fixedMasterContainer, FOCUSABLE_ELEMENTS_SELECTOR).filter(element => !element.disabled && element.offsetParent !== null);

    },

    _getScrollableEditors: function () {
        return queryElements(this._scrollableMasterContainer, FOCUSABLE_ELEMENTS_SELECTOR).filter(element => !element.disabled && element.offsetParent !== null);
    },

    _masterDom: function () {
        return this.$.masterDom;
    },

    /**
     * The core-ajax component for entity retrieval.
     */
    _ajaxRetriever: function () {
        return this._masterDom()._ajaxRetriever();
    },

    /**
     * The core-ajax component for entity saving.
     */
    _ajaxSaver: function () {
        return this._masterDom()._ajaxSaver();
    },

    /**
     * The validator component.
     */
    _validator: function () {
        return this._masterDom()._validator();
    },

    /**
     * The component for entity serialisation.
     */
    _serialiser: function () {
        return this._masterDom()._serialiser();
    },

    /**
     * The reflector component.
     */
    _reflector: function () {
        return this._masterDom()._reflector();
    },

    /**
     * The toast component.
     */
    _toastGreeting: function () {
        return this._masterDom()._toastGreeting();
    }
};

export const TgEgiMasterBehavior = [TgEntityMasterBehavior, TgEgiMasterBehaviorImpl];<|endoftext|>