<commit_before>"use strict";

var debug          = require('debug-ext')('oauth')
  , mano           = require('mano')
  , deferred       = require('deferred')
  , assign         = require('es5-ext/object/assign')
  , userEmailMap   = require('mano/lib/server/user-email-map')
  , authentication = require('mano-auth/server/authentication')
  , serializeValue = require('dbjs/_setup/serialize/value')
  , generateUnique = require('time-uuid')
  , request        = require('request')
  , jwtDecode      = require('jwt-decode')
  , env            = mano.env;

var createUser = function (data) {
	return mano.queryMemoryDb([], 'addUser', JSON.stringify(data));
};

var registerDemoUser = function (userId, data) {
	return mano.queryMemoryDb([userId], 'registerDemoUser', { userId: userId, data: data });
};

var generateUrl = function (path, query) {
	var keys = Object.keys(query);

	keys.forEach(function (key, index) {
		if (index === 0) path += '?';

		path += key + '=' + encodeURIComponent(query[key]);

		if (index !== (keys.length - 1)) path += '&';
	});

	return path;
};

var jwtTimeCodeToDate = function (timeCode) {
	return new Date(timeCode * 1000);
};

module.exports = exports = {
	loginMiddleware: function (req, res, next) {
		// 1. If not proper path, end.
		if (req._parsedUrl.pathname !== '/oauth-login/') {
			next();
			return;
		}

		// 2. Is there a user logged in?
		if (req.$user) {
			var demoUserId      = res.cookies.get('demoUser')
			  , isDifferentUser = demoUserId !== req.$user;

			// 2.1. If he is not current demo user, end.
			if (!demoUserId || isDifferentUser) {
				next();
				return;
			}
		}

		// 3. Generate and store unique state.
		var state = generateUnique();
		res.cookies.set('OAuthState', state);

		debug('Login request. Unique state:', state);

		// 4. Build redirect query.
		var locationQuery = assign({}, req.query, {
			response_type: 'code',
			client_id: env.oauth.clientId,
			state: state,
			scope: env.oauth.scope,
			redirect_uri: env.oauth.redirectUrl
		});

		if (env.oauth.extraAuthorizationParameters) {
			assign(locationQuery, env.oauth.extraAuthorizationParameters);
		}

		// 5. Redirect user to authorization endpoint.
		res.writeHead(302, {
			Location: generateUrl(env.oauth.authorizationEndpoint, locationQuery)
		});
		res.end();
	},
	logoutMiddleware: function (req, res, next) {
		var isLogoutPath = req._parsedUrl.pathname === '/logout/'
		  , accessToken  = res.cookies.get('oAuthToken');

		// 1. If proper path and token is set.
		if (isLogoutPath && accessToken) {
			debug('Invalidating token');

			// 1.1. Clean the tokens from cookie.
			res.cookies.set('oAuthToken', null);
			res.cookies.set('oAuthRefreshToken', null);

			// 1.2. Invalidate the token in CAS.
			request({
				uri: env.oauth.invalidationEndpoint,
				method: 'GET',
				headers: {
					Authorization: 'Bearer ' + accessToken
				}
			}, function (error, response, body) {
				if (error) {
					debug('Error received from invalidation endpoint:', error);
				}
			});
		}

		// 2. Passthru to other middlewares.
		next();
	},
	refreshMiddleware: function (req, res, next) {
		deferred(null)(function () {
			var accessToken  = res.cookies.get('oAuthToken')
			  , refreshToken = res.cookies.get('oAuthRefreshToken')
			  , currentTime  = new Date()

			  , decodedAccessToken, accIssueTimeCode, accExpiryTimeCode, accExpiryDate, accValidLength
			  , refExpiryDate, isRefExpired, isAccExpired, isAccSoonToExpire, deferredRequest;

			// 1. If tokens are set.
			if (!accessToken || !refreshToken) return;

			// 1.1. Decode access token.
			decodedAccessToken = jwtDecode(accessToken);

			// 1.2. Get issue and expiry dates from token.
			accIssueTimeCode  = decodedAccessToken.iat;
			accExpiryTimeCode = decodedAccessToken.exp;
			accExpiryDate     = jwtTimeCodeToDate(accExpiryTimeCode);

			// 1.3. Calculate for how long access token is going to be valid.
			accValidLength = accExpiryTimeCode - accIssueTimeCode;

			// 1.4. Calculate refresh token expiry date.
			refExpiryDate = jwtTimeCodeToDate(accIssueTimeCode);
			refExpiryDate.setDate(refExpiryDate.getDate() + 1);

			// 2. If refresh token is already expired.
			isRefExpired = currentTime.getTime() > refExpiryDate.getTime();

			if (isRefExpired) {
				// 2.1 Logout user.
				debug('Refresh token expired');

				authentication.logout(req, res);
				return;
			}

			// 3. If access token is already expired or soon will be.
			isAccExpired      = currentTime.getTime() > accExpiryDate.getTime();
			isAccSoonToExpire = currentTime.getTime() > jwtTimeCodeToDate(Math.floor(
				accExpiryTimeCode - (accValidLength / 10)
			)).getTime();

			if (!isAccExpired && !isAccSoonToExpire) return;

			// 3.1. Request refresh.
			debug('Refreshing token');

			deferredRequest = deferred();

			request({
				uri: env.oauth.tokenEndpoint,
				method: 'POST',
				headers: {
					'Content-Type': 'application/x-www-form-urlencoded',
					Authorization: 'Basic ' + new Buffer(env.oauth.clientId + ':'
						+ env.oauth.clientSecret).toString('base64')
				},
				form: {
					grant_type: 'refresh_token',
					refresh_token: refreshToken,
					redirect_uri: env.oauth.redirectUrl
				}
			}, function (error, response, body) {
				var failed = false;

				if (error) {
					debug('Error received from token endpoint:', error);

					failed = true;
				} else if (response.statusCode >= 200 && response.statusCode < 300) {
					var parsedBody            = JSON.parse(body)
					  , newAccessToken        = parsedBody.access_token
					  , newRefreshToken       = parsedBody.refresh_token
					  , decodedNewAccessToken = jwtDecode(newAccessToken);

					debug('Refreshed JWT received for:', decodedNewAccessToken.email);

					res.cookies.set('oAuthToken', newAccessToken);
					res.cookies.set('oAuthRefreshToken', newRefreshToken);
				} else {
					debug('Failed to refresh token:', response.statusCode);

					failed = true;
				}

				if (failed && isAccExpired) {
					authentication.logout(req, res);
				}

				deferredRequest.resolve();
			});

			return deferredRequest.promise;
		}).done(function () {
			// 4. Passthru to other middlewares.
			next();
		});
	},
	callbackMiddleware: function (req, res, next) {
		var query = req.query;

		if (req._parsedUrl.pathname !== '/eid-callback') {
			next();
			return;
		}

		debug('Callback received. Query:', query);

		if (query.error) {
			// TODO: How should we notify the user about the error? Flash message?
			res.writeHead(302, { Location: '/' });
			res.end();

			return;
		}

		var state = req.cookies.get('OAuthState');

		// Clear state as it's supposed to be one time only.
		res.cookies.set('OAuthState', null);

		if (state !== query.state) {
			debug('State value did not match:', state, query.state);

			// TODO: Notify user.
			res.writeHead(302, { Location: '/' });
			res.end();

			return;
		}

		debug('States match');

		request({
			uri: env.oauth.tokenEndpoint,
			method: 'POST',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
				Authorization: 'Basic ' + new Buffer(env.oauth.clientId + ':'
					+ env.oauth.clientSecret).toString('base64')
			},
			form: {
				grant_type: 'authorization_code',
				code: query.code,
				redirect_uri: env.oauth.redirectUrl
			}
		}, function (error, response, body) {
			if (error) {
				debug('Error received from token endpoint:', error);

				// TODO: Notify user.
				res.writeHead(302, { Location: '/' });
				res.end();
			} else if (response.statusCode >= 200 && response.statusCode < 300) {
				var parsedBody   = JSON.parse(body)
				  , accessToken  = parsedBody.access_token
				  , refreshToken = parsedBody.refresh_token
				  , decoded      = jwtDecode(accessToken)
				  , isPublicApp  = req.$appName === 'public'
				  , roles        = ['user']
				  , demoUserId   = isPublicApp ? null : res.cookies.get('demoUser');

				debug('JWT received for:', decoded.email);

				userEmailMap(function (map) {
					return map.get(serializeValue(decoded.email));
				})(function (userId) {
					if (userId) return userId;

					if (!demoUserId) {
						return createUser({
							firstName: decoded.fname,
							lastName: decoded.lname,
							email: decoded.email,
							roles: roles
						});
					}

					return registerDemoUser(demoUserId, {
						firstName: decoded.fname,
						lastName: decoded.lname,
						email: decoded.email,
						roles: roles
					});
				}).done(function (userId) {
					if (!env.isAccountConfirmationDisabled && !decoded.email_verified) {
						if (demoUserId) {
							authentication.logout(req, res);
						}

						res.writeHead(302, { Location: '/request-confirm-account/' });
						res.end();
						return;
					}

					res.cookies.set('oAuthToken', accessToken);
					res.cookies.set('oAuthRefreshToken', refreshToken);

					authentication.login(userId, req, res);

					res.writeHead(303, {
						Location: '/',
						Authorization: 'Bearer ' + accessToken
					});
					res.end();
				}, function (error) {
					debug('Error while logging in:', error);

					// TODO: Notify user.
					res.writeHead(302, { Location: '/' });
					res.end();
				});
			} else {
				debug('Failed to authorize:', response.statusCode);

				// TODO: Notify user.
				res.writeHead(302, { Location: '/' });
				res.end();
			}
		});
	},
	profileMiddleware: function (req, res, next) {
		var isProfilePath = req._parsedUrl.pathname === '/oauth-profile/'
		  , accessToken   = res.cookies.get('oAuthToken');

		// 1. If not proper path or token is not set, end.
		if (!isProfilePath || !accessToken) {
			next();
			return;
		}

		res.writeHead(303, {
			Location: generateUrl(env.oauth.profileEndoint, { token: accessToken })
		});
		res.end();
	}
};
<commit_msg>Fixed the invalidation problem<commit_after>"use strict";

var debug          = require('debug-ext')('oauth')
  , mano           = require('mano')
  , deferred       = require('deferred')
  , assign         = require('es5-ext/object/assign')
  , userEmailMap   = require('mano/lib/server/user-email-map')
  , authentication = require('mano-auth/server/authentication')
  , serializeValue = require('dbjs/_setup/serialize/value')
  , generateUnique = require('time-uuid')
  , request        = require('request')
  , jwtDecode      = require('jwt-decode')
  , env            = mano.env;

var createUser = function (data) {
	return mano.queryMemoryDb([], 'addUser', JSON.stringify(data));
};

var registerDemoUser = function (userId, data) {
	return mano.queryMemoryDb([userId], 'registerDemoUser', { userId: userId, data: data });
};

var generateUrl = function (path, query) {
	var keys = Object.keys(query);

	keys.forEach(function (key, index) {
		if (index === 0) path += '?';

		path += key + '=' + encodeURIComponent(query[key]);

		if (index !== (keys.length - 1)) path += '&';
	});

	return path;
};

var jwtTimeCodeToDate = function (timeCode) {
	return new Date(timeCode * 1000);
};

module.exports = exports = {
	loginMiddleware: function (req, res, next) {
		// 1. If not proper path, end.
		if (req._parsedUrl.pathname !== '/oauth-login/') {
			next();
			return;
		}

		// 2. Is there a user logged in?
		if (req.$user) {
			var demoUserId      = res.cookies.get('demoUser')
			  , isDifferentUser = demoUserId !== req.$user;

			// 2.1. If he is not current demo user, end.
			if (!demoUserId || isDifferentUser) {
				next();
				return;
			}
		}

		// 3. Generate and store unique state.
		var state = generateUnique();
		res.cookies.set('OAuthState', state);

		debug('Login request. Unique state:', state);

		// 4. Build redirect query.
		var locationQuery = assign({}, req.query, {
			response_type: 'code',
			client_id: env.oauth.clientId,
			state: state,
			scope: env.oauth.scope,
			redirect_uri: env.oauth.redirectUrl
		});

		if (env.oauth.extraAuthorizationParameters) {
			assign(locationQuery, env.oauth.extraAuthorizationParameters);
		}

		// 5. Redirect user to authorization endpoint.
		res.writeHead(302, {
			Location: generateUrl(env.oauth.authorizationEndpoint, locationQuery)
		});
		res.end();
	},
	logoutMiddleware: function (req, res, next) {
		var isLogoutPath = req._parsedUrl.pathname === '/logout/'
		  , accessToken  = res.cookies.get('oAuthToken');

		// 1. If proper path and token is set.
		if (isLogoutPath && accessToken) {
			debug('Invalidating token');

			// 1.1. Clean the tokens from cookie.
			res.cookies.set('oAuthToken', null);
			res.cookies.set('oAuthRefreshToken', null);

			// 1.2. Invalidate the token in CAS.
			request({
				uri: env.oauth.invalidationEndpoint + '?redirect_uri=' + env.oauth.redirectUrl,
				method: 'GET',
				headers: {
					Authorization: 'Bearer ' + accessToken
				}
			}, function (error, response, body) {
				if (error) {
					debug('Error received from invalidation endpoint:', error);
				}
			});
		}

		// 2. Passthru to other middlewares.
		next();
	},
	refreshMiddleware: function (req, res, next) {
		deferred(null)(function () {
			var accessToken  = res.cookies.get('oAuthToken')
			  , refreshToken = res.cookies.get('oAuthRefreshToken')
			  , currentTime  = new Date()

			  , decodedAccessToken, accIssueTimeCode, accExpiryTimeCode, accExpiryDate, accValidLength
			  , refExpiryDate, isRefExpired, isAccExpired, isAccSoonToExpire, deferredRequest;

			// 1. If tokens are set.
			if (!accessToken || !refreshToken) return;

			// 1.1. Decode access token.
			decodedAccessToken = jwtDecode(accessToken);

			// 1.2. Get issue and expiry dates from token.
			accIssueTimeCode  = decodedAccessToken.iat;
			accExpiryTimeCode = decodedAccessToken.exp;
			accExpiryDate     = jwtTimeCodeToDate(accExpiryTimeCode);

			// 1.3. Calculate for how long access token is going to be valid.
			accValidLength = accExpiryTimeCode - accIssueTimeCode;

			// 1.4. Calculate refresh token expiry date.
			refExpiryDate = jwtTimeCodeToDate(accIssueTimeCode);
			refExpiryDate.setDate(refExpiryDate.getDate() + 1);

			// 2. If refresh token is already expired.
			isRefExpired = currentTime.getTime() > refExpiryDate.getTime();

			if (isRefExpired) {
				// 2.1 Logout user.
				debug('Refresh token expired');

				authentication.logout(req, res);
				return;
			}

			// 3. If access token is already expired or soon will be.
			isAccExpired      = currentTime.getTime() > accExpiryDate.getTime();
			isAccSoonToExpire = currentTime.getTime() > jwtTimeCodeToDate(Math.floor(
				accExpiryTimeCode - (accValidLength / 10)
			)).getTime();

			if (!isAccExpired && !isAccSoonToExpire) return;

			// 3.1. Request refresh.
			debug('Refreshing token');

			deferredRequest = deferred();

			request({
				uri: env.oauth.tokenEndpoint,
				method: 'POST',
				headers: {
					'Content-Type': 'application/x-www-form-urlencoded',
					Authorization: 'Basic ' + new Buffer(env.oauth.clientId + ':'
						+ env.oauth.clientSecret).toString('base64')
				},
				form: {
					grant_type: 'refresh_token',
					refresh_token: refreshToken,
					redirect_uri: env.oauth.redirectUrl
				}
			}, function (error, response, body) {
				var failed = false;

				if (error) {
					debug('Error received from token endpoint:', error);

					failed = true;
				} else if (response.statusCode >= 200 && response.statusCode < 300) {
					var parsedBody            = JSON.parse(body)
					  , newAccessToken        = parsedBody.access_token
					  , newRefreshToken       = parsedBody.refresh_token
					  , decodedNewAccessToken = jwtDecode(newAccessToken);

					debug('Refreshed JWT received for:', decodedNewAccessToken.email);

					res.cookies.set('oAuthToken', newAccessToken);
					res.cookies.set('oAuthRefreshToken', newRefreshToken);
				} else {
					debug('Failed to refresh token:', response.statusCode);

					failed = true;
				}

				if (failed && isAccExpired) {
					authentication.logout(req, res);
				}

				deferredRequest.resolve();
			});

			return deferredRequest.promise;
		}).done(function () {
			// 4. Passthru to other middlewares.
			next();
		});
	},
	callbackMiddleware: function (req, res, next) {
		var query = req.query;

		if (req._parsedUrl.pathname !== '/eid-callback') {
			next();
			return;
		}

		debug('Callback received. Query:', query);

		if (query.error) {
			// TODO: How should we notify the user about the error? Flash message?
			res.writeHead(302, { Location: '/' });
			res.end();

			return;
		}

		var state = req.cookies.get('OAuthState');

		// Clear state as it's supposed to be one time only.
		res.cookies.set('OAuthState', null);

		if (state !== query.state) {
			debug('State value did not match:', state, query.state);

			// TODO: Notify user.
			res.writeHead(302, { Location: '/' });
			res.end();

			return;
		}

		debug('States match');

		request({
			uri: env.oauth.tokenEndpoint,
			method: 'POST',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
				Authorization: 'Basic ' + new Buffer(env.oauth.clientId + ':'
					+ env.oauth.clientSecret).toString('base64')
			},
			form: {
				grant_type: 'authorization_code',
				code: query.code,
				redirect_uri: env.oauth.redirectUrl
			}
		}, function (error, response, body) {
			if (error) {
				debug('Error received from token endpoint:', error);

				// TODO: Notify user.
				res.writeHead(302, { Location: '/' });
				res.end();
			} else if (response.statusCode >= 200 && response.statusCode < 300) {
				var parsedBody   = JSON.parse(body)
				  , accessToken  = parsedBody.access_token
				  , refreshToken = parsedBody.refresh_token
				  , decoded      = jwtDecode(accessToken)
				  , isPublicApp  = req.$appName === 'public'
				  , roles        = ['user']
				  , demoUserId   = isPublicApp ? null : res.cookies.get('demoUser');

				debug('JWT received for:', decoded.email);

				userEmailMap(function (map) {
					return map.get(serializeValue(decoded.email));
				})(function (userId) {
					if (userId) return userId;

					if (!demoUserId) {
						return createUser({
							firstName: decoded.fname,
							lastName: decoded.lname,
							email: decoded.email,
							roles: roles
						});
					}

					return registerDemoUser(demoUserId, {
						firstName: decoded.fname,
						lastName: decoded.lname,
						email: decoded.email,
						roles: roles
					});
				}).done(function (userId) {
					if (!env.isAccountConfirmationDisabled && !decoded.email_verified) {
						if (demoUserId) {
							authentication.logout(req, res);
						}

						res.writeHead(302, { Location: '/request-confirm-account/' });
						res.end();
						return;
					}

					res.cookies.set('oAuthToken', accessToken);
					res.cookies.set('oAuthRefreshToken', refreshToken);

					authentication.login(userId, req, res);

					res.writeHead(303, {
						Location: '/',
						Authorization: 'Bearer ' + accessToken
					});
					res.end();
				}, function (error) {
					debug('Error while logging in:', error);

					// TODO: Notify user.
					res.writeHead(302, { Location: '/' });
					res.end();
				});
			} else {
				debug('Failed to authorize:', response.statusCode);

				// TODO: Notify user.
				res.writeHead(302, { Location: '/' });
				res.end();
			}
		});
	},
	profileMiddleware: function (req, res, next) {
		var isProfilePath = req._parsedUrl.pathname === '/oauth-profile/'
		  , accessToken   = res.cookies.get('oAuthToken');

		// 1. If not proper path or token is not set, end.
		if (!isProfilePath || !accessToken) {
			next();
			return;
		}

		res.writeHead(303, {
			Location: generateUrl(env.oauth.profileEndoint, { token: accessToken })
		});
		res.end();
	}
};
<|endoftext|>


<commit_before>import ActiveModelAdapter from "active-model-adapter";
import ActiveModelSerializer from "active-model-adapter/active-model-serializer";

export default {
  name: 'active-model-adapter',
  initialize: function(app) {
    if (arguments.length === 1) {
      // support the old registration API
      app.register('adapter:-active-model', ActiveModelAdapter);
      app.register('serializer:-active-model', ActiveModelSerializer);
    } else {
      let registry = app;
      registry.register('adapter:-active-model', ActiveModelAdapter);
      registry.register('serializer:-active-model', ActiveModelSerializer);
    }
  }
};
<commit_msg>[bugfix] use old initializer code<commit_after>import ActiveModelAdapter from "active-model-adapter";
import ActiveModelSerializer from "active-model-adapter/active-model-serializer";

export default {
  name: 'active-model-adapter',
  initialize: function() {
    var application = arguments[1] || arguments[0];
    application.register('adapter:-active-model', ActiveModelAdapter);
    application.register('serializer:-active-model', ActiveModelSerializer);
  }
};
<|endoftext|>


<commit_before>/**
* Copyright 2018 Awear Solutions Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

module.exports = function(RED) {
  "use strict";
  const EventEmitter = require('events').EventEmitter;
  const appEnv = require('cfenv').getAppEnv();
  const mongodb = require('mongodb');
  const forEachIteration = new Error("node-red-contrib-mongodb3 forEach iteration");
  const forEachEnd = new Error("node-red-contrib-mongodb3 forEach end");

  let services = [];
  Object.keys(appEnv.services).forEach(function(label) {
    if ((/^mongo/i).test(label)) {
      services = services.concat(appEnv.services[label].map(function(service) {
        return {
          "name": service.name,
          "label": service.label
        };
      }));
    }
  });

  const operations = {};
  Object.keys(mongodb.Collection.prototype).forEach(function(operationName) {
    if ('function' == typeof Object.getOwnPropertyDescriptor(mongodb.Collection.prototype, operationName).value) {
      operations[operationName] = mongodb.Collection.prototype[operationName];
    }
  });
  // We don't want to pass the find-operation's cursor directly.
  delete operations.find;

  operations['find.toArray'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.find.apply(this, args).toArray(callback);
  };
  operations['find.forEach'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.find.apply(this, args).forEach(function(doc) {
      return callback(forEachIteration, doc);
    }, function(err) {
      return callback(err || forEachEnd);
    });
  };

  // We don't want to pass the aggregate's cursor directly.
  delete operations.aggregate;
  operations['aggregate.toArray'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.aggregate.apply(this, args).toArray(callback);
  };
  operations['aggregate.forEach'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.aggregate.apply(this, args).forEach(function(doc) {
      return callback(forEachIteration, doc);
    }, function(err) {
      return callback(err || forEachEnd);
    });
  };

  // We don't want to pass the listIndexes's cursor directly.
  delete operations.listIndexes;
  operations['listIndexes.toArray'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.listIndexes.apply(this, args).toArray(callback);
  };
  operations['listIndexes.forEach'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.listIndexes.apply(this, args).forEach(function(doc) {
      return callback(forEachIteration, doc);
    }, function(err) {
      return callback(err || forEachEnd);
    });
  };

  operations.db = function(callback) {
    return callback(null, this);
  };

  operations.collection = function(callback) {
    return callback(null, this);
  };

  RED.nodes.registerType("mongodb3", function MongoConfigNode(n) {
    RED.nodes.createNode(this, n);
    this.uri = '' + n.uri;
    if (this.credentials.user || this.credentials.password) {
      this.uri = this.uri.replace(/^mongodb:\/\//, 'mongodb://' + encodeURIComponent(this.credentials.user) + ':' + encodeURIComponent(this.credentials.password) + '@');
    }
    this.name = n.name;
    this.parallelism = n.parallelism * 1;
    if (!!n.options) {
      try {
        this.options = JSON.parse(n.options);
      } catch (err) {
        this.error("Failed to parse options: " + err);
      }
    }
    this.deploymentId = (1 + Math.random() * 0xffffffff).toString(16).replace('.', '');
  }, {
    "credentials": {
      "user": {
        "type": "text"
      },
      "password": {
        "type": "password"
      }
    }
  });

  RED.httpAdmin.get('/mongodb3/vcap', function(req, res) {
    res.json(services);
  });

  RED.httpAdmin.get('/mongodb3/operations', function(req, res) {
    res.json(Object.keys(operations).sort());
  });

  const mongoPool = {};

  function getClient(config) {
    let poolCell = mongoPool['#' + config.deploymentId];
    if (!poolCell) {
      mongoPool['#' + config.deploymentId] = poolCell = {
        "instances": 0,
        // es6-promise. A client will be called only once.
        "promise": mongodb.MongoClient.connect(config.uri, config.options || {}).then(function(client) {
          const dbName = decodeURIComponent((config.uri.match(/^.*\/([^?]*)\??.*$/) || [])[1] || '');
          const db = client.db(dbName);
          return {
			      "client": client,
            "db": db,
            "queue": [],
            "parallelOps": 0 // current number of operations
          };
        })
      };
    }
    poolCell.instances++;
    return poolCell.promise;
  }

  function closeClient(config) {
    const poolCell = mongoPool['#' + config.deploymentId];
    if (!poolCell) {
      return;
    }
    poolCell.instances--;
    if (poolCell.instances === 0) {
      delete mongoPool['#' + config.deploymentId];
      poolCell.promise.then(function(client) {
        client.client.close().catch(function(err) {
          node.error("Error while closing client: " + err);
        });
      }, function() { // ignore error
        // db-client was not created in the first place.
      });
    }
  }

  RED.nodes.registerType("mongodb3 in", function MongoInputNode(n) {
    RED.nodes.createNode(this, n);
    this.configNode = n.configNode;
    this.collection = n.collection;
    this.operation = n.operation;
    if (n.service == "_ext_") {
      // Refer to the config node's id, uri, options, parallelism and warn function.
      this.config = RED.nodes.getNode(this.configNode);
    } else if (n.service) {
      const configService = appEnv.getService(n.service);
      if (configService) {
        // Only a uri is defined.
        this.config = {
          "deploymentId": 'service:' + n.service, // different from node-red deployment ids.
          "uri": configService.credentials.uri || configService.credentials.url
        };
      }
    }
    if (!this.config || !this.config.uri) {
      this.error("missing mongodb3 configuration");
      return;
    }
    const node = this;
    getClient(node.config).then(function(client) {
      let nodeCollection;
      if (node.collection) {
        nodeCollection = client.db.collection(node.collection);
      }
      let nodeOperation;
      if (node.operation) {
        nodeOperation = operations[node.operation];
      }
      node.on('input', function(msg) {
        if (node.config.parallelism && (node.config.parallelism > 0) && (client.parallelOps >= node.config.parallelism)) {
          // msg cannot be handled right now - push to queue.
          client.queue.push({
            "node_id": node.id,
            "msg": msg
          });
          return;
        }
        client.parallelOps += 1;
        setImmediate(function() {
          handleMessage(msg);
        });
      });

      node.on('node-red-contrib-mongodb3 handleMessage', function(msg) {
        // see: messageHandlingCompleted
        setImmediate(function(){
          handleMessage(msg);
        });
      });

      function handleMessage(msg) {
        let operation = nodeOperation;
        if (!operation && msg.operation) {
          operation = operations[msg.operation];
        }
        if (!operation) {
          node.error("No operation defined", msg);
          return messageHandlingCompleted();
        }
        let collection; // stays undefined in the case of "db" operation.
        if (operation != operations.db) {
          collection = nodeCollection;
          if (!collection && msg.collection) {
            collection = client.db.collection(msg.collection);
          }
          if (!collection) {
            node.error("No collection defined", msg);
            return messageHandlingCompleted();
          }
        }

        delete msg.collection;
        delete msg.operation;
        let args = msg.payload;
        if (!Array.isArray(args)) {
          args = [args];
        }
        if (args.length === 0) {
          // All operations can accept one argument (some can accept more).
          // Some operations don't expect a single callback argument.
          args.push(undefined);
        }
        if ((operation.length > 0) && (args.length > operation.length - 1)) {
          // The operation was defined with arguments, thus it may not
          // assume that the last argument is the callback.
          // We must not pass too many arguments to the operation.
          args = args.slice(0, operation.length - 1);
        }
        profiling.requests += 1;
        debounceProfilingStatus();
        try {
          operation.apply(collection || client.db, args.concat(function(err, response) {
            if (err && (forEachIteration != err) && (forEachEnd != err)) {
              profiling.error += 1;
              debounceProfilingStatus();
              node.error(err, msg);
              return messageHandlingCompleted();
            }
            if (forEachEnd != err) {
              if (!!response) {
                // Some operations return a Connection object with the result.
                // Passing this large connection object might be heavy - it will
                // be cloned over and over by Node-RED, and there is no reason
                // the typical user will need it.
                // The mongodb package does not export the Connection prototype-function.
                // Instead of loading the Connection prototype-function from the
                // internal libs (which might change their path), I use the fact
                // that it inherits EventEmitter.
                if (response.connection instanceof EventEmitter) {
                  delete response.connection;
                }
                if (response.result && response.result.connection instanceof EventEmitter) {
                  delete response.result.connection;
                }
              }
							// `response` is an instance of CommandResult, and does not seem to have the standard Object methods,
							// which means that some props are not correctly being forwarded to msg.payload (eg "ops" ouputted from `insertOne`)
							// cloning the object fixes that.
							response = Object.assign({}, response);
							// response.message includes info about the DB op, but is large and never used (like the connection)
							delete response.message;

              // send msg (when err == forEachEnd, this is just a forEach completion).
              if (forEachIteration == err) {
                // Clone, so we can send the same message again with a different payload
                // in each iteration.
                const messageToSend = RED.util.cloneMessage(msg);
                messageToSend.payload = response;
                node.send(messageToSend);
              } else {
                // No need to clone - the same message will not be sent again.
                msg.payload = response;
                node.send(msg);
              }
            }
            if (forEachIteration != err) {
              // clear status
              profiling.success += 1;
              debounceProfilingStatus();
              messageHandlingCompleted();
            }
          }));
        } catch(err) {
          profiling.error += 1;
          debounceProfilingStatus();
          node.error(err, msg);
          return messageHandlingCompleted();
        }
      }
      function messageHandlingCompleted() {
        setImmediate(handlePendingMessageOnDemand);
      }
      function handlePendingMessageOnDemand() {
        while (client.queue.length > 0) {
          const pendingMessage = client.queue.shift();
          const targetNode = RED.nodes.getNode(pendingMessage.node_id);
          if (!targetNode) {
            // The node was removed before handling the pending message.
            // This is just a warning because a similar scenario can happen if
            // a node was removed just before handling a message that was sent
            // to it.
            const warningMessage = "Node " + pendingMessage.node_id + " was removed while having a pending message";
            if (node.config.warn) {
              // The warning will appear from the config node, because the target
              // node cannot be found.
              node.config.warn(warningMessage, pendingMessage.msg);
            } else {
              // If the node was configured with a service instead of a config node,
              // the warning will appear from the current node.
              // This shouldn't happen in real life because in such scenario
              // the parallelism limit is not configured.
              node.warn(warningMessage, pendingMessage.msg);
            }
            continue;
          }
          // Handle the pending message.
          if (!targetNode.emit('node-red-contrib-mongodb3 handleMessage', pendingMessage.msg)) {
            // Safety check - if emit() returned false it means there are no listeners to the event.
            // Was the target node closed?
            // This shouldn't happen normally, but if it does, we must try to handle the next message in the queue.
            const errorMessage = "Node " + pendingMessage.node_id + " could not handle the pending message";
            if (node.config.error) {
              node.config.error(errorMessage, pendingMessage.msg);
            } else {
              node.error(errorMessage, pendingMessage.msg);
            }
            continue;
          }
          // Another message is being handled. The number of parallel ops does not change.
          return;
        }
        // The queue is empty. The number of parallel ops has reduced.
        if (client.parallelOps <= 0) {
          return node.error("Something went wrong with node-red-contrib-mongodb3 parallel-ops count");
        }
        client.parallelOps -= 1;
      }
    }, function(err) {
      // Failed to create db client
      node.error(err);
    });
    const profiling = {
      "requests": 0,
      "success": 0,
      "error": 0
    };
    function profilingStatus() {
      node.status({
        "fill": "yellow",
        "shape": "dot",
        "text": "" + profiling.requests + ", success: " + profiling.success + ", error: " + profiling.error
      });
    }

    let debouncer = null;
    function debounceProfilingStatus() {
      if (debouncer) {
        return;
      }
      // show curent status, create debouncer.
      profilingStatus();
      debouncer = setTimeout(function() {
        profilingStatus(); // should we call only if there was a change?
        debouncer = null;
      }, 1000);
    }
    node.on('close', function() {
      if (node.config) {
        closeClient(node.config);
      }
      node.removeAllListeners('node-red-contrib-mongodb3 handleMessage');
      if (debouncer) {
        clearTimeout(debouncer);
      }
    });
  });
};
<commit_msg>add listCollections.toArray and listCollections.forEach operations<commit_after>/**
* Copyright 2018 Awear Solutions Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

module.exports = function(RED) {
  "use strict";
  const EventEmitter = require('events').EventEmitter;
  const appEnv = require('cfenv').getAppEnv();
  const mongodb = require('mongodb');
  const forEachIteration = new Error("node-red-contrib-mongodb3 forEach iteration");
  const forEachEnd = new Error("node-red-contrib-mongodb3 forEach end");

  let services = [];
  Object.keys(appEnv.services).forEach(function(label) {
    if ((/^mongo/i).test(label)) {
      services = services.concat(appEnv.services[label].map(function(service) {
        return {
          "name": service.name,
          "label": service.label
        };
      }));
    }
  });

  const operations = {};
  Object.keys(mongodb.Collection.prototype).forEach(function(operationName) {
    if ('function' == typeof Object.getOwnPropertyDescriptor(mongodb.Collection.prototype, operationName).value) {
      operations[operationName] = mongodb.Collection.prototype[operationName];
    }
  });
  // We don't want to pass the find-operation's cursor directly.
  delete operations.find;

  operations['find.toArray'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.find.apply(this, args).toArray(callback);
  };
  operations['find.forEach'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.find.apply(this, args).forEach(function(doc) {
      return callback(forEachIteration, doc);
    }, function(err) {
      return callback(err || forEachEnd);
    });
  };

  // We don't want to pass the aggregate's cursor directly.
  delete operations.aggregate;
  operations['aggregate.toArray'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.aggregate.apply(this, args).toArray(callback);
  };
  operations['aggregate.forEach'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.aggregate.apply(this, args).forEach(function(doc) {
      return callback(forEachIteration, doc);
    }, function(err) {
      return callback(err || forEachEnd);
    });
  };

  // We don't want to pass the listIndexes's cursor directly.
  delete operations.listIndexes;
  operations['listIndexes.toArray'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.listIndexes.apply(this, args).toArray(callback);
  };
  operations['listIndexes.forEach'] = function() {
    const args = Array.prototype.slice.call(arguments, 0);
    const callback = args.pop();
    mongodb.Collection.prototype.listIndexes.apply(this, args).forEach(function(doc) {
      return callback(forEachIteration, doc);
    }, function(err) {
      return callback(err || forEachEnd);
    });
  };

   // We don't want to pass the listCollections's cursor directly.
   delete operations.listCollections;
   operations['db.listCollections.toArray'] = function() {
      const args = Array.prototype.slice.call(arguments, 0);
      const callback = args.pop();
      mongodb.Db.prototype.listCollections.apply(this, args).toArray(callback);
   };
   operations['db.listCollections.forEach'] = function() {
      const args = Array.prototype.slice.call(arguments, 0);
      const callback = args.pop();
      mongodb.Db.prototype.listCollections.apply(this, args).forEach(
         function(doc) {
            return callback(forEachIteration, doc);
         },
         function(err) {
            return callback(err || forEachEnd);
         }
      );
   };

  operations.db = function(callback) {
    return callback(null, this);
  };

  operations.collection = function(callback) {
    return callback(null, this);
  };

  RED.nodes.registerType("mongodb3", function MongoConfigNode(n) {
    RED.nodes.createNode(this, n);
    this.uri = '' + n.uri;
    if (this.credentials.user || this.credentials.password) {
      this.uri = this.uri.replace(/^mongodb:\/\//, 'mongodb://' + encodeURIComponent(this.credentials.user) + ':' + encodeURIComponent(this.credentials.password) + '@');
    }
    this.name = n.name;
    this.parallelism = n.parallelism * 1;
    if (!!n.options) {
      try {
        this.options = JSON.parse(n.options);
      } catch (err) {
        this.error("Failed to parse options: " + err);
      }
    }
    this.deploymentId = (1 + Math.random() * 0xffffffff).toString(16).replace('.', '');
  }, {
    "credentials": {
      "user": {
        "type": "text"
      },
      "password": {
        "type": "password"
      }
    }
  });

  RED.httpAdmin.get('/mongodb3/vcap', function(req, res) {
    res.json(services);
  });

  RED.httpAdmin.get('/mongodb3/operations', function(req, res) {
    res.json(Object.keys(operations).sort());
  });

  const mongoPool = {};

  function getClient(config) {
    let poolCell = mongoPool['#' + config.deploymentId];
    if (!poolCell) {
      mongoPool['#' + config.deploymentId] = poolCell = {
        "instances": 0,
        // es6-promise. A client will be called only once.
        "promise": mongodb.MongoClient.connect(config.uri, config.options || {}).then(function(client) {
          const dbName = decodeURIComponent((config.uri.match(/^.*\/([^?]*)\??.*$/) || [])[1] || '');
          const db = client.db(dbName);
          return {
			      "client": client,
            "db": db,
            "queue": [],
            "parallelOps": 0 // current number of operations
          };
        })
      };
    }
    poolCell.instances++;
    return poolCell.promise;
  }

  function closeClient(config) {
    const poolCell = mongoPool['#' + config.deploymentId];
    if (!poolCell) {
      return;
    }
    poolCell.instances--;
    if (poolCell.instances === 0) {
      delete mongoPool['#' + config.deploymentId];
      poolCell.promise.then(function(client) {
        client.client.close().catch(function(err) {
          node.error("Error while closing client: " + err);
        });
      }, function() { // ignore error
        // db-client was not created in the first place.
      });
    }
  }

  RED.nodes.registerType("mongodb3 in", function MongoInputNode(n) {
    RED.nodes.createNode(this, n);
    this.configNode = n.configNode;
    this.collection = n.collection;
    this.operation = n.operation;
    if (n.service == "_ext_") {
      // Refer to the config node's id, uri, options, parallelism and warn function.
      this.config = RED.nodes.getNode(this.configNode);
    } else if (n.service) {
      const configService = appEnv.getService(n.service);
      if (configService) {
        // Only a uri is defined.
        this.config = {
          "deploymentId": 'service:' + n.service, // different from node-red deployment ids.
          "uri": configService.credentials.uri || configService.credentials.url
        };
      }
    }
    if (!this.config || !this.config.uri) {
      this.error("missing mongodb3 configuration");
      return;
    }
    const node = this;
    getClient(node.config).then(function(client) {
      let nodeCollection;
      if (node.collection) {
        nodeCollection = client.db.collection(node.collection);
      }
      let nodeOperation;
      if (node.operation) {
        nodeOperation = operations[node.operation];
      }
      node.on('input', function(msg) {
        if (node.config.parallelism && (node.config.parallelism > 0) && (client.parallelOps >= node.config.parallelism)) {
          // msg cannot be handled right now - push to queue.
          client.queue.push({
            "node_id": node.id,
            "msg": msg
          });
          return;
        }
        client.parallelOps += 1;
        setImmediate(function() {
          handleMessage(msg);
        });
      });

      node.on('node-red-contrib-mongodb3 handleMessage', function(msg) {
        // see: messageHandlingCompleted
        setImmediate(function(){
          handleMessage(msg);
        });
      });

      function handleMessage(msg) {
        let operation = nodeOperation;
        if (!operation && msg.operation) {
          operation = operations[msg.operation];
        }
        if (!operation) {
          node.error("No operation defined", msg);
          return messageHandlingCompleted();
        }
        let collection; // stays undefined in the case of "db" operation.
        if (
                  operation != operations.db &&
                  operation != operations['db.listCollections.toArray'] &&
                  operation != operations['db.listCollections.forEach']
               ) {
          collection = nodeCollection;
          if (!collection && msg.collection) {
            collection = client.db.collection(msg.collection);
          }
          if (!collection) {
            node.error("No collection defined", msg);
            return messageHandlingCompleted();
          }
        }

        delete msg.collection;
        delete msg.operation;
        let args = msg.payload;
        if (!Array.isArray(args)) {
          args = [args];
        }
        if (args.length === 0) {
          // All operations can accept one argument (some can accept more).
          // Some operations don't expect a single callback argument.
          args.push(undefined);
        }
        if ((operation.length > 0) && (args.length > operation.length - 1)) {
          // The operation was defined with arguments, thus it may not
          // assume that the last argument is the callback.
          // We must not pass too many arguments to the operation.
          args = args.slice(0, operation.length - 1);
        }
        profiling.requests += 1;
        debounceProfilingStatus();
        try {
          operation.apply(collection || client.db, args.concat(function(err, response) {
            if (err && (forEachIteration != err) && (forEachEnd != err)) {
              profiling.error += 1;
              debounceProfilingStatus();
              node.error(err, msg);
              return messageHandlingCompleted();
            }
            if (forEachEnd != err) {
              if (!!response) {
                // Some operations return a Connection object with the result.
                // Passing this large connection object might be heavy - it will
                // be cloned over and over by Node-RED, and there is no reason
                // the typical user will need it.
                // The mongodb package does not export the Connection prototype-function.
                // Instead of loading the Connection prototype-function from the
                // internal libs (which might change their path), I use the fact
                // that it inherits EventEmitter.
                if (response.connection instanceof EventEmitter) {
                  delete response.connection;
                }
                if (response.result && response.result.connection instanceof EventEmitter) {
                  delete response.result.connection;
                }
              }
							// `response` is an instance of CommandResult, and does not seem to have the standard Object methods,
							// which means that some props are not correctly being forwarded to msg.payload (eg "ops" ouputted from `insertOne`)
							// cloning the object fixes that.
							response = Object.assign({}, response);
							// response.message includes info about the DB op, but is large and never used (like the connection)
							delete response.message;

              // send msg (when err == forEachEnd, this is just a forEach completion).
              if (forEachIteration == err) {
                // Clone, so we can send the same message again with a different payload
                // in each iteration.
                const messageToSend = RED.util.cloneMessage(msg);
                messageToSend.payload = response;
                node.send(messageToSend);
              } else {
                // No need to clone - the same message will not be sent again.
                msg.payload = response;
                node.send(msg);
              }
            }
            if (forEachIteration != err) {
              // clear status
              profiling.success += 1;
              debounceProfilingStatus();
              messageHandlingCompleted();
            }
          }));
        } catch(err) {
          profiling.error += 1;
          debounceProfilingStatus();
          node.error(err, msg);
          return messageHandlingCompleted();
        }
      }
      function messageHandlingCompleted() {
        setImmediate(handlePendingMessageOnDemand);
      }
      function handlePendingMessageOnDemand() {
        while (client.queue.length > 0) {
          const pendingMessage = client.queue.shift();
          const targetNode = RED.nodes.getNode(pendingMessage.node_id);
          if (!targetNode) {
            // The node was removed before handling the pending message.
            // This is just a warning because a similar scenario can happen if
            // a node was removed just before handling a message that was sent
            // to it.
            const warningMessage = "Node " + pendingMessage.node_id + " was removed while having a pending message";
            if (node.config.warn) {
              // The warning will appear from the config node, because the target
              // node cannot be found.
              node.config.warn(warningMessage, pendingMessage.msg);
            } else {
              // If the node was configured with a service instead of a config node,
              // the warning will appear from the current node.
              // This shouldn't happen in real life because in such scenario
              // the parallelism limit is not configured.
              node.warn(warningMessage, pendingMessage.msg);
            }
            continue;
          }
          // Handle the pending message.
          if (!targetNode.emit('node-red-contrib-mongodb3 handleMessage', pendingMessage.msg)) {
            // Safety check - if emit() returned false it means there are no listeners to the event.
            // Was the target node closed?
            // This shouldn't happen normally, but if it does, we must try to handle the next message in the queue.
            const errorMessage = "Node " + pendingMessage.node_id + " could not handle the pending message";
            if (node.config.error) {
              node.config.error(errorMessage, pendingMessage.msg);
            } else {
              node.error(errorMessage, pendingMessage.msg);
            }
            continue;
          }
          // Another message is being handled. The number of parallel ops does not change.
          return;
        }
        // The queue is empty. The number of parallel ops has reduced.
        if (client.parallelOps <= 0) {
          return node.error("Something went wrong with node-red-contrib-mongodb3 parallel-ops count");
        }
        client.parallelOps -= 1;
      }
    }, function(err) {
      // Failed to create db client
      node.error(err);
    });
    const profiling = {
      "requests": 0,
      "success": 0,
      "error": 0
    };
    function profilingStatus() {
      node.status({
        "fill": "yellow",
        "shape": "dot",
        "text": "" + profiling.requests + ", success: " + profiling.success + ", error: " + profiling.error
      });
    }

    let debouncer = null;
    function debounceProfilingStatus() {
      if (debouncer) {
        return;
      }
      // show curent status, create debouncer.
      profilingStatus();
      debouncer = setTimeout(function() {
        profilingStatus(); // should we call only if there was a change?
        debouncer = null;
      }, 1000);
    }
    node.on('close', function() {
      if (node.config) {
        closeClient(node.config);
      }
      node.removeAllListeners('node-red-contrib-mongodb3 handleMessage');
      if (debouncer) {
        clearTimeout(debouncer);
      }
    });
  });
};
<|endoftext|>


<commit_before>"use strict";

angular.module('arethusa.search').service('search', function(state, configurator) {
  var self = this;
  this.conf = configurator.configurationFor('search');
  this.name = this.conf.name;
  this.template = this.conf.template;
  this.queryByRegex = this.conf.regex;

  function findByRegex(str) {
    // We might need to escape some chars here, we need to try
    // this out more
    var regex = new RegExp(str, 'i');
    return arethusaUtil.inject([], self.strings, function(memo, string, ids) {
      if (string.match(regex)) {
        arethusaUtil.pushAll(memo, ids);
      }
    });
  }

  this.queryTokens = function() {
    if (self.tokenQuery === '') {
      return;
    }
    var tokens = self.tokenQuery.split(' ');
    var ids = arethusaUtil.inject([], tokens, function(memo, token) {
      var hits = self.queryByRegex ?  findByRegex(token) : self.strings[token];
      arethusaUtil.pushAll(memo, hits);
    });
    state.multiSelect(ids);
  };

  this.collectTokenStrings = function() {
    return arethusaUtil.inject({}, state.tokens, function(memo, id, token) {
      var str = token.string;
      if (! memo[str]) {
        memo[str] = [];
      }
      memo[str].push(id);
    });
  };

  this.init = function() {
    self.strings = self.collectTokenStrings();
    self.tokenQuery = ''; // model used by the input form
  };
});
<commit_msg>Deselect all in search when no query is submitted<commit_after>"use strict";

angular.module('arethusa.search').service('search', function(state, configurator) {
  var self = this;
  this.conf = configurator.configurationFor('search');
  this.name = this.conf.name;
  this.template = this.conf.template;
  this.queryByRegex = this.conf.regex;

  function findByRegex(str) {
    // We might need to escape some chars here, we need to try
    // this out more
    var regex = new RegExp(str, 'i');
    return arethusaUtil.inject([], self.strings, function(memo, string, ids) {
      if (string.match(regex)) {
        arethusaUtil.pushAll(memo, ids);
      }
    });
  }

  this.queryTokens = function() {
    if (self.tokenQuery === '') {
      state.deselectAll();
      return;
    }
    var tokens = self.tokenQuery.split(' ');
    var ids = arethusaUtil.inject([], tokens, function(memo, token) {
      var hits = self.queryByRegex ?  findByRegex(token) : self.strings[token];
      arethusaUtil.pushAll(memo, hits);
    });
    state.multiSelect(ids);
  };

  this.collectTokenStrings = function() {
    return arethusaUtil.inject({}, state.tokens, function(memo, id, token) {
      var str = token.string;
      if (! memo[str]) {
        memo[str] = [];
      }
      memo[str].push(id);
    });
  };

  this.init = function() {
    self.strings = self.collectTokenStrings();
    self.tokenQuery = ''; // model used by the input form
  };
});
<|endoftext|>


<commit_before>module.exports = function(app) {

  app.controller('MapsController', ['$scope', function($scope) {
    $scope.showMap = true;
    angular.extend($scope, {
                seattle: {
                  lat: 47.6,
                  lng: -122.33,
                  zoom: 12
                },
                defaults: {
                  scrollWheelZoom: false,
                  tap: false
                }
              });

  }]);

  app.controller('RouteController', ['$scope', '$http', function($scope, $http) {
    $scope.busRouteData = null;

    $scope.displayRoute = function(route) {
      $http.get('/api/busroutes/' + route)
        .then(function(res) {
          $scope.busRouteData = res.data;
          console.log($scope.busRouteData);
        }, function(err) {
          console.log(err)
        });
      }
  }])

}
<commit_msg>plotting routes works<commit_after>module.exports = function(app) {

  app.controller('MapsController', ['$scope', function($scope) {
    $scope.showMap = true;
    angular.extend($scope, {
                seattle: {
                  lat: 47.6,
                  lng: -122.33,
                  zoom: 12
                },
                defaults: {
                  scrollWheelZoom: false,
                  tap: false
                }
              });

  }]);

  app.controller('RouteController', ['$scope', '$http', 'leafletData', function($scope, $http, leafletData) {
    $scope.busRouteData = null;

    $scope.displayRoute = function(route) {
      $http.get('/api/busroutes/' + route)
        .then(function(res) {
          //could make this cleaner
          $scope.busRouteData = res.data;
          console.log($scope.busRouteData);

          leafletData.getMap().then(function(map) {
            L.geoJson($scope.busRouteData).addTo(map);
          });
        }, function(err) {
          console.log(err);
          console.log(err.data);
        });

      };

  }])

}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import I18n from 'i18n!shared.flash_notices'
import $ from 'jquery'
import htmlEscape from 'str/htmlEscape'
import 'jquery.cookie'

function updateAriaLive({polite} = {polite: false}) {
  if (this.screenreaderHolderReady()) {
    const value = polite ? 'polite' : 'assertive'
    $(this.screenreader_holder).attr('aria-live', value)
    // instui FocusRegionManager throws aria-hidden on everything outside a Dialog when opened
    // removing it here sees that it's done whenever screenreader alerts are displayed
    $(this.screenreader_holder).removeAttr('aria-hidden')
  }
}

class RailsFlashNotificationsHelper {
  constructor() {
    this.holder = null
    this.screenreader_holder = null
  }

  initHolder() {
    const $current_holders = $('#flash_message_holder')

    if ($current_holders.length === 0) {
      this.holder = null
    } else {
      this.holder = $current_holders[0]

      $(this.holder).on('click', '.close_link', event => {
        event.preventDefault()
      })

      $(this.holder).on('click', 'li', event => {
        if ($(event.currentTarget).hasClass('no_close')) {
          return
        }

        if ($(event.currentTarget).hasClass('unsupported_browser')) {
          $.cookie('unsupported_browser_dismissed', true, {path: '/'})
        }

        $(event.currentTarget)
          .stop(true, true)
          .remove()
      })
    }
  }

  holderReady() {
    return this.holder != null
  }

  createNode(type, content, timeout, cssOptions = {}, classes = '') {
    if (this.holderReady()) {
      const node = this.generateNodeHTML(type, content)

      $(node)
        .addClass(classes)
        .appendTo($(this.holder))
        .css({zIndex: 2, ...cssOptions})
        .show('fast')
        .delay(ENV.flashAlertTimeout || timeout || 7000)
        .fadeOut('slow', function() {
          $(this).remove()
        })
    }
  }

  generateNodeHTML(type, content) {
    const icon = this.getIconType(type)

    // See generateScreenreaderNodeHtml for SR features
    return `

          ${this.escapeContent(content)}
          <button type="button" class="Button Button--icon-action close_link">

          </button>

      `
  }

  getIconType(type) {
    if (type === 'success') {
      return 'check'
    } else if (type === 'warning' || type === 'error') {
      return 'warning'
    } else {
      return 'info'
    }
  }

  initScreenreaderHolder() {
    const $current_screenreader_holders = $('#flash_screenreader_holder')

    if ($current_screenreader_holders.length === 0) {
      this.screenreader_holder = null
    } else {
      this.screenreader_holder = $current_screenreader_holders[0]
      this.setScreenreaderAttributes()
    }
  }

  screenreaderHolderReady() {
    return this.screenreader_holder != null
  }

  createScreenreaderNode(content, closable = true) {
    if (this.screenreaderHolderReady()) {
      updateAriaLive.call(this, {polite: false})
      const node = $(this.generateScreenreaderNodeHTML(content, closable))
      node.appendTo($(this.screenreader_holder))

      window.setTimeout(() => {
        // Accessibility attributes must be removed for the deletion of the node
        // and then reapplied because JAWS/IE will not respect the
        // "aria-relevant" attribute and read when the node is deleted if
        // the attributes are in place
        this.resetScreenreaderAttributes()
        node.remove()
        this.setScreenreaderAttributes()
      }, 10000)
    }
  }

  setScreenreaderAttributes() {
    if (this.screenreaderHolderReady()) {
      // These attributes are added for accessibility.  However, adding them
      // to the DOM at load causes some screenreaders to read "alert" when
      // the page is loaded.  That is why these attributes are added here.
      $(this.screenreader_holder).attr('role', 'alert')
      $(this.screenreader_holder).attr('aria-live', 'assertive')
      $(this.screenreader_holder).attr('aria-relevant', 'additions')
      $(this.screenreader_holder).attr('class', 'screenreader-only')
      $(this.screenreader_holder).attr('aria-atomic', 'false')
    }
  }

  resetScreenreaderAttributes() {
    if (this.screenreaderHolderReady()) {
      $(this.screenreader_holder).removeAttr('role')
      $(this.screenreader_holder).removeAttr('aria-live')
      $(this.screenreader_holder).removeAttr('aria-relevant')
      $(this.screenreader_holder).removeAttr('class')
      $(this.screenreader_holder).removeAttr('aria-atomic')
    }
  }

  createScreenreaderNodeExclusive(content, polite = false) {
    if (this.screenreaderHolderReady()) {
      updateAriaLive.call(this, {polite})
      this.screenreader_holder.innerHTML = ''
      const node = $(this.generateScreenreaderNodeHTML(content, false))
      node.appendTo($(this.screenreader_holder))
    }
  }

  generateScreenreaderNodeHTML(content, closable) {
    let closeContent
    if (closable) {
      closeContent = I18n.t('Close')
    } else {
      closeContent = ''
    }

    return `

          ${this.escapeContent(content)}
          ${htmlEscape(closeContent)}

      `
  }

  /*
xsslint safeString.method escapeContent
*/
  escapeContent(content) {
    if (content.hasOwnProperty('html')) {
      return content.html
    } else {
      return htmlEscape(content)
    }
  }
}

export default RailsFlashNotificationsHelper
<commit_msg>Get screenreaders to read the close button on flash messages<commit_after>/*
 * Copyright (C) 2016 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import I18n from 'i18n!shared.flash_notices'
import $ from 'jquery'
import htmlEscape from 'str/htmlEscape'
import 'jquery.cookie'

function updateAriaLive({polite} = {polite: false}) {
  if (this.screenreaderHolderReady()) {
    const value = polite ? 'polite' : 'assertive'
    $(this.screenreader_holder).attr('aria-live', value)
    // instui FocusRegionManager throws aria-hidden on everything outside a Dialog when opened
    // removing it here sees that it's done whenever screenreader alerts are displayed
    $(this.screenreader_holder).removeAttr('aria-hidden')
  }
}

class RailsFlashNotificationsHelper {
  constructor() {
    this.holder = null
    this.screenreader_holder = null
  }

  initHolder() {
    const $current_holders = $('#flash_message_holder')

    if ($current_holders.length === 0) {
      this.holder = null
    } else {
      this.holder = $current_holders[0]

      $(this.holder).on('click', '.close_link', event => {
        event.preventDefault()
      })

      $(this.holder).on('click', 'li', event => {
        if ($(event.currentTarget).hasClass('no_close')) {
          return
        }

        if ($(event.currentTarget).hasClass('unsupported_browser')) {
          $.cookie('unsupported_browser_dismissed', true, {path: '/'})
        }

        $(event.currentTarget)
          .stop(true, true)
          .remove()
      })
    }
  }

  holderReady() {
    return this.holder != null
  }

  createNode(type, content, timeout, cssOptions = {}, classes = '') {
    if (this.holderReady()) {
      const node = this.generateNodeHTML(type, content)

      $(node)
        .addClass(classes)
        .appendTo($(this.holder))
        .css({zIndex: 2, ...cssOptions})
        .show('fast')
        .delay(ENV.flashAlertTimeout || timeout || 7000)
        .fadeOut('slow', function() {
          $(this).remove()
        })
    }
  }

  generateNodeHTML(type, content) {
    const icon = this.getIconType(type)

    // See generateScreenreaderNodeHtml for SR features
    return `

          ${this.escapeContent(content)}
          <button type="button" class="Button Button--icon-action close_link" aria-label="${htmlEscape(
            I18n.t('Close')
          )}">

          </button>

      `
  }

  getIconType(type) {
    if (type === 'success') {
      return 'check'
    } else if (type === 'warning' || type === 'error') {
      return 'warning'
    } else {
      return 'info'
    }
  }

  initScreenreaderHolder() {
    const $current_screenreader_holders = $('#flash_screenreader_holder')

    if ($current_screenreader_holders.length === 0) {
      this.screenreader_holder = null
    } else {
      this.screenreader_holder = $current_screenreader_holders[0]
      this.setScreenreaderAttributes()
    }
  }

  screenreaderHolderReady() {
    return this.screenreader_holder != null
  }

  createScreenreaderNode(content, closable = true) {
    if (this.screenreaderHolderReady()) {
      updateAriaLive.call(this, {polite: false})
      const node = $(this.generateScreenreaderNodeHTML(content, closable))
      node.appendTo($(this.screenreader_holder))

      window.setTimeout(() => {
        // Accessibility attributes must be removed for the deletion of the node
        // and then reapplied because JAWS/IE will not respect the
        // "aria-relevant" attribute and read when the node is deleted if
        // the attributes are in place
        this.resetScreenreaderAttributes()
        node.remove()
        this.setScreenreaderAttributes()
      }, 10000)
    }
  }

  setScreenreaderAttributes() {
    if (this.screenreaderHolderReady()) {
      // These attributes are added for accessibility.  However, adding them
      // to the DOM at load causes some screenreaders to read "alert" when
      // the page is loaded.  That is why these attributes are added here.
      $(this.screenreader_holder).attr('role', 'alert')
      $(this.screenreader_holder).attr('aria-live', 'assertive')
      $(this.screenreader_holder).attr('aria-relevant', 'additions')
      $(this.screenreader_holder).attr('class', 'screenreader-only')
      $(this.screenreader_holder).attr('aria-atomic', 'false')
    }
  }

  resetScreenreaderAttributes() {
    if (this.screenreaderHolderReady()) {
      $(this.screenreader_holder).removeAttr('role')
      $(this.screenreader_holder).removeAttr('aria-live')
      $(this.screenreader_holder).removeAttr('aria-relevant')
      $(this.screenreader_holder).removeAttr('class')
      $(this.screenreader_holder).removeAttr('aria-atomic')
    }
  }

  createScreenreaderNodeExclusive(content, polite = false) {
    if (this.screenreaderHolderReady()) {
      updateAriaLive.call(this, {polite})
      this.screenreader_holder.innerHTML = ''
      const node = $(this.generateScreenreaderNodeHTML(content, false))
      node.appendTo($(this.screenreader_holder))
    }
  }

  generateScreenreaderNodeHTML(content, closable) {
    let closeContent
    if (closable) {
      closeContent = I18n.t('Close')
    } else {
      closeContent = ''
    }

    return `

          ${this.escapeContent(content)}
          ${htmlEscape(closeContent)}

      `
  }

  /*
xsslint safeString.method escapeContent
*/
  escapeContent(content) {
    if (content.hasOwnProperty('html')) {
      return content.html
    } else {
      return htmlEscape(content)
    }
  }
}

export default RailsFlashNotificationsHelper
<|endoftext|>


<commit_before>
var CherryTemplate = function(scope) {

  var _template = this

  _template.getSection = function(key) {
    return _template.parent.find('[data-'+key+']');
  }

  _template.scope = scope

  _template.parent = $('[data-cherry="'+scope.controller+'"]');
  _template.parent.addClass('cherry-watch');

  _template.formatForHTML = function(string) {
    return String(string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
  }

  _template.updateFormatters = function() {
    $(_template.getSection('formatter')).each(function(key, section) {
      var label = $(section).attr('data-formatter').split(':');

      var _name     = label[0];
      var parameter = label[1];

      var formatter = scope.formatters[_name];

      if(typeof formatter != 'undefined') {
        var val = scope.get(parameter);
        var format = $(section).attr('data-format');

        returned_value = formatter(val,format);

        $(section).html(returned_value);
      }
    });
  }

  _template.notify = function(key) {
    var value = _template.formatForHTML(_template.scope[key]).toString();

    var holder = $('[data-var="'+key+'"], [data-model="'+key+'"]');

    _template.replaceTokens()
    _template.updateFormatters();
    holder.html(value).val(value);
  }

  // Token functions
  // TODO: Comment
  _template.tokenFormat    = /\{\{(.*)\}\}/g
  _template.formatRawToken = function(string) {
    return string.replace('{{','').replace('}}','')
  }
  _template.findTokens  = function() {
    return _template.parent.html().match(_template.tokenFormat)
  }
  _template.replaceTokens = function() {
    var raw_tokens = _template.findTokens();

    if(raw_tokens == null) return;

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

      var token  = raw_tokens[i],
          token_formatted = _template.formatRawToken(token)
          result = _template.scope[token_formatted]

      if(typeof result !== 'undefined') {
        var rendered = _template.parent.html().replace(token, '');

        _template.parent.html(rendered)
      }

    }
  }

  $(document).on('click', '[data-click]', function() {
    var _event = $(this).attr('data-click')
    _template.scope.call(_event);
  })

  $(document).on('keyup', '[data-model]', function() {
    var scope_key  = $(this).attr('data-model')

    _template.scope[scope_key] = $(this).val()
    _template.scope.$digest()
  })
}<commit_msg>Fixed a problem with multiple tags<commit_after>
var CherryTemplate = function(scope) {

  var _template = this

  _template.getSection = function(key) {
    return _template.parent.find('[data-'+key+']');
  }

  _template.scope = scope

  _template.parent = $('[data-cherry="'+scope.controller+'"]');
  _template.parent.addClass('cherry-watch');

  _template.formatForHTML = function(string) {
    return String(string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
  }

  _template.updateFormatters = function() {
    $(_template.getSection('formatter')).each(function(key, section) {
      var label = $(section).attr('data-formatter').split(':');

      var _name     = label[0];
      var parameter = label[1];

      var formatter = scope.formatters[_name];

      if(typeof formatter != 'undefined') {
        var val = scope.get(parameter);
        var format = $(section).attr('data-format');

        returned_value = formatter(val,format);

        $(section).html(returned_value);
      }
    });
  }

  _template.notify = function(key) {
    var value = _template.formatForHTML(_template.scope[key]).toString();

    var holder = $('[data-var="'+key+'"], [data-model="'+key+'"]');

    _template.replaceTokens()
    _template.updateFormatters();
    holder.html(value).val(value);
  }

  // Token functions
  // TODO: Comment
  _template.tokenFormat    = /\{\{([a-z]*)\}\}/g
  _template.formatRawToken = function(string) {
    return string.replace('{{','').replace('}}','')
  }
  _template.findTokens  = function() {
    return _template.parent.html().match(_template.tokenFormat)
  }
  _template.replaceTokens = function() {
    var raw_tokens = _template.findTokens();

    if(raw_tokens == null) return;

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

      var token  = raw_tokens[i],
          token_formatted = _template.formatRawToken(token)
          result = _template.scope[token_formatted]

      if(typeof result !== 'undefined') {
        var rendered = _template.parent.html().replace(token, '');

        _template.parent.html(rendered)
      }

    }
  }

  $(document).on('click', '[data-click]', function() {
    var _event = $(this).attr('data-click')
    _template.scope.call(_event);
  })

  $(document).on('keyup', '[data-model]', function() {
    var scope_key  = $(this).attr('data-model')

    _template.scope[scope_key] = $(this).val()
    _template.scope.$digest()
  })
}<|endoftext|>


<commit_before>/**
 * Utilitário de tabelas de frequência de ônibus
 *
 * O código abaixo ajud a extrair os horários das tabelas de frequência
 * do site da Prefeitura Municipal de Vitória (PMV).
 *
 * URL de exemplo: http://sistemas.vitoria.es.gov.br/redeiti/listarHorario.cfm?cdLinha=0211
 *
 * Usando o inspetor de código (Chrome ou Firefox), selecione o elemento <tbody>
 * da tabela desejada e cole o código abaixo. O valor retornado será uma lista
 * com os horários.
 */

jQuery($0).find('.txtC').map(function() { return $(this).text(); }).get();
<commit_msg>correção de texto<commit_after>/**
 * Utilitário de tabelas de frequência de ônibus
 *
 * O código abaixo ajuda a extrair os horários das tabelas de frequência
 * do site da Prefeitura Municipal de Vitória (PMV).
 *
 * URL de exemplo: http://sistemas.vitoria.es.gov.br/redeiti/listarHorario.cfm?cdLinha=0211
 *
 * Usando o inspetor de código (Chrome ou Firefox), selecione o elemento <tbody>
 * da tabela desejada e cole o código abaixo. O valor retornado será uma lista
 * com os horários.
 */

jQuery($0).find('.txtC').map(function() { return $(this).text(); }).get();
<|endoftext|>


<commit_before>var
  intelHex = require('intel-hex'),
  Stream   = require('stream').Stream,
  util     = require('util');

var out = module.exports = {};

var d = function(c) {
  return (c + '').charCodeAt(0);
};

out.Flasher = function(serialport, options) {
  var that = this;
  this.options = options || {};
  this.sp = serialport;
  this.signature = this.options.signature || 'LUFACDC';

  if (this.options.debug) {
    this.sp.on('data', function(d) {
      process.stdout.write(' -> ');

      for (var i=0; i<d.length; i++) {
        var c = d.toString().substring(i, i+1);
        if (c.charCodeAt(0) < 32 || c.charCodeAt(0) > 126) {
          c = '.';
        }

        process.stdout.write(c + ' [' + d.readUInt8(i).toString(16) + '] ');
      }
      process.stdout.write('\n');
    });
  }

  this.c = function(value, fn) {
    that.cmds.push({
      value : value,
      callback : function(data) {
        fn && fn(data);
      }
    });
    return this;
  }

  this.flashChunkSize = 0;
  this.bytes = [];
  this.cmds = [];
};

out.Flasher.prototype = {
  run : function(fn) {
    var that = this;
    process.nextTick(function() {
      if (that.running) { return; }
      var cmd = that.cmds.shift();

      if (cmd) {
        running = true;
        that.options.debug && process.stdout.write('Send: ' + cmd.value);
        that.sp.once('data', function(d) {
          that.running = false;
          cmd.callback(d);

          process.nextTick(function() {
            if (that.cmds.length > 0) {
              that.run(fn);
            } else {
              fn && fn();
            }
          });
        });

        that.sp.write(cmd.value);
      }
    });
  },

  prepare : function(fn) {
    var that = this;
    this.c('S', function(d) {
          if (d.toString() !== that.signature) {
            fn(new Error('Invalid device signature; expecting: ' + that.signature + ' received: ' + d.toString()));
          }
        })
        .c('V')
        .c('v')
        .c('p')
        .c('a')
        .c('b', function(d) {
          that.flashChunkSize = d.readUInt8(2);
        })
        .c('t')
        .c('TD')
        .c('P')
        .c('F')
        .c('F')
        .c('F')
        .c('N')
        .c('N')
        .c('N')
        .c('Q')
        .c('Q')
        .c('Q')
        .c([d('A'), 0x03, 0xfc])
        .c([d('g'), 0x00, 0x01, d('E')])
        .c([d('A'), 0x03, 0xff])
        .c([d('g'), 0x00, 0x01, d('E')])
        .c([d('A'), 0x03, 0xff])
        .c([d('g'), 0x00, 0x01, d('E')])
        .c([d('A'), 0x03, 0xff])
        .c([d('g'), 0x00, 0x01, d('E')])

    this.run(function() {
      fn(null, that);
    });
  },

  erase : function(fn) {
    this.c('e', function() {
      fn && fn();
    }) // erase

    this.run();
  },

  program : function(fullString, fn) {

    var
      that = this,
      converter,
      bytes = [];

    this.totalBytes = 0;

    this.c([d('A'), 0x00, 0x00], function() {
      converter = intelHex.parse(fullString);

      that.totalBytes = converter.data.length;
      // buffer the bytes so we can push them in the expected size on 'end'
      Array.prototype.push.apply(bytes, converter.data);

      that.options.debug && console.log('programming', bytes.length, 'bytes');
      that.chunksSent = [];

      for (var i=0; i<bytes.length; i+=that.flashChunkSize) {
        var chunk = Array.prototype.slice.call(converter.data.slice(i, i+that.flashChunkSize));
        that.chunksSent.push(chunk);
        that.c([d('B'), 0x00, chunk.length, d('F')].concat(chunk));
      }
    });

    this.run(function() { fn && fn() });
  },

  verify : function(fn) {
    var that = this;
    // compare flash on device with the chunks we sent
    this.c([d('A'), 0x00, 0x00], function() {

      var
        index = 0,
        compare = function(deviceData) {

          var localChunk = that.chunksSent[index];
          index++;

          if (index >= that.chunksSent.length) {
            fn && fn();
            return;
          }

          // Quick check to make sure the lengths match
          if (localChunk.length !== deviceData.length) {
            fn(new Error(
              "Flashed content length differs! local: " + localChunk.length +
              ' vs device: ' + deviceData.length
            ));
          }

          localChunk.forEach(function(val, idx) {
            if (val !== deviceData.readUInt8(idx)) {
              fn(new Error('Firmware on the device does not match local data'));
            }
          });

          process.nextTick(function() {
            var readSize = that.flashChunkSize;
            that.options.debug && console.log(that.totalBytes - index*that.flashChunkSize);
            if (that.totalBytes - index*that.flashChunkSize < that.flashChunkSize) {
              readSize = that.totalBytes - index*that.flashChunkSize;
            }
            that.c([d('g'), 0x00, readSize, d('F')], compare);
            that.run();
          });
        };

      that.options.debug && console.log('\n\nVerifying flash..')

      that.c([d('g'), 0x00, that.flashChunkSize, d('F')], compare);
      that.run();
    });
    that.run();
  },

  fuseCheck :  fuseCheck = function(fn) {
    this.options.debug && console.log('checking fuses');
    // fuse check
    this.c('F')
        .c('F')
        .c('F')
        .c('N')
        .c('N')
        .c('N')
        .c('Q')
        .c('Q')
        .c('Q')
        .c('L')
        .c('E');

    this.run(function() {
      fn();
    });
  }
};

out.init = function(serialport, options, fn) {
  if (typeof options === 'function' && !fn) {
    fn = options;
    options = {};
  }

  var flasher = new out.Flasher(serialport, options);
  flasher.prepare(fn);
};
<commit_msg>implement non breaking error handling on verification<commit_after>var
  intelHex = require('intel-hex'),
  Stream   = require('stream').Stream,
  util     = require('util');

var out = module.exports = {};

var d = function(c) {
  return (c + '').charCodeAt(0);
};

out.Flasher = function(serialport, options) {
  var that = this;
  this.options = options || {};
  this.sp = serialport;
  this.signature = this.options.signature || 'LUFACDC';

  if (this.options.debug) {
    this.sp.on('data', function(d) {
      process.stdout.write(' -> ');

      for (var i=0; i<d.length; i++) {
        var c = d.toString().substring(i, i+1);
        if (c.charCodeAt(0) < 32 || c.charCodeAt(0) > 126) {
          c = '.';
        }

        process.stdout.write(c + ' [' + d.readUInt8(i).toString(16) + '] ');
      }
      process.stdout.write('\n');
    });
  }

  this.c = function(value, fn) {
    that.cmds.push({
      value : value,
      callback : function(data) {
        fn && fn(data);
      }
    });
    return this;
  }

  this.flashChunkSize = 0;
  this.bytes = [];
  this.cmds = [];
};

out.Flasher.prototype = {
  run : function(fn) {
    var that = this;
    process.nextTick(function() {
      if (that.running) { return; }
      var cmd = that.cmds.shift();

      if (cmd) {
        running = true;
        that.options.debug && process.stdout.write('Send: ' + cmd.value);
        that.sp.once('data', function(d) {
          that.running = false;
          cmd.callback(d);

          process.nextTick(function() {
            if (that.cmds.length > 0) {
              that.run(fn);
            } else {
              fn && fn();
            }
          });
        });

        that.sp.write(cmd.value);
      }
    });
  },

  prepare : function(fn) {
    var that = this;
    this.c('S', function(d) {
          if (d.toString() !== that.signature) {
            fn(new Error('Invalid device signature; expecting: ' + that.signature + ' received: ' + d.toString()));
          }
        })
        .c('V')
        .c('v')
        .c('p')
        .c('a')
        .c('b', function(d) {
          that.flashChunkSize = d.readUInt8(2);
        })
        .c('t')
        .c('TD')
        .c('P')
        .c('F')
        .c('F')
        .c('F')
        .c('N')
        .c('N')
        .c('N')
        .c('Q')
        .c('Q')
        .c('Q')
        .c([d('A'), 0x03, 0xfc])
        .c([d('g'), 0x00, 0x01, d('E')])
        .c([d('A'), 0x03, 0xff])
        .c([d('g'), 0x00, 0x01, d('E')])
        .c([d('A'), 0x03, 0xff])
        .c([d('g'), 0x00, 0x01, d('E')])
        .c([d('A'), 0x03, 0xff])
        .c([d('g'), 0x00, 0x01, d('E')])

    this.run(function() {
      fn(null, that);
    });
  },

  erase : function(fn) {
    this.c('e', function() {
      fn && fn();
    }) // erase

    this.run();
  },

  program : function(fullString, fn) {

    var
      that = this,
      converter,
      bytes = [];

    this.totalBytes = 0;

    this.c([d('A'), 0x00, 0x00], function() {
      converter = intelHex.parse(fullString);

      that.totalBytes = converter.data.length;
      // buffer the bytes so we can push them in the expected size on 'end'
      Array.prototype.push.apply(bytes, converter.data);

      that.options.debug && console.log('programming', bytes.length, 'bytes');
      that.chunksSent = [];

      for (var i=0; i<bytes.length; i+=that.flashChunkSize) {
        var chunk = Array.prototype.slice.call(converter.data.slice(i, i+that.flashChunkSize));
        that.chunksSent.push(chunk);
        that.c([d('B'), 0x00, chunk.length, d('F')].concat(chunk));
      }
    });

    this.run(function() { fn && fn() });
  },

  verify : function(fn) {
    var that = this;
    // compare flash on device with the chunks we sent
    this.c([d('A'), 0x00, 0x00], function() {

      var
        index = 0,
        compare = function(deviceData) {
          var error = null;
          var localChunk = that.chunksSent[index];
          index++;

          if (index >= that.chunksSent.length) {
            fn && fn();
            return;
          }

          // Quick check to make sure the lengths match
          if (localChunk.length !== deviceData.length) {
            return fn(new Error(
              "Flashed content length differs! local: " + localChunk.length +
              ' vs device: ' + deviceData.length
            ));
          }

          localChunk.forEach(function(val, idx) {
            if (val !== deviceData.readUInt8(idx)) {
              error = new Error('Firmware on the device does not match local data');
            }
          });

          if (error) {
            return fn(error);
          }

          process.nextTick(function() {
            var readSize = that.flashChunkSize;
            that.options.debug && console.log(that.totalBytes - index*that.flashChunkSize);
            if (that.totalBytes - index*that.flashChunkSize < that.flashChunkSize) {
              readSize = that.totalBytes - index*that.flashChunkSize;
            }
            that.c([d('g'), 0x00, readSize, d('F')], compare);
            that.run();
          });
        };

      that.options.debug && console.log('\n\nVerifying flash..')

      that.c([d('g'), 0x00, that.flashChunkSize, d('F')], compare);
      that.run();
    });
    that.run();
  },

  fuseCheck :  fuseCheck = function(fn) {
    this.options.debug && console.log('checking fuses');
    // fuse check
    this.c('F')
        .c('F')
        .c('F')
        .c('N')
        .c('N')
        .c('N')
        .c('Q')
        .c('Q')
        .c('Q')
        .c('L')
        .c('E');

    this.run(function() {
      fn();
    });
  }
};

out.init = function(serialport, options, fn) {
  if (typeof options === 'function' && !fn) {
    fn = options;
    options = {};
  }

  var flasher = new out.Flasher(serialport, options);
  flasher.prepare(fn);
};
<|endoftext|>