<commit_before>// external modules
import test from 'ava';
import sinon from 'sinon';
import { actionTest } from 'redux-ava';
import 'babel-polyfill';

// internal modules
import * as API from '../../src/util/apiConnection';
import {
    saveAttempt,
    saveSuccess,
    saveFailure,
    saveAgreement,
    AGREEMENT_SAVE_ATTEMPT,
    AGREEMENT_SAVE_SUCCESS,
    AGREEMENT_SAVE_FAILURE
} from '../../src/components/agreement/AgreementActions';

const agreementTestData = { some: 'data' };
const agreementTestDataError = { response: { some: 'error' } };

const callApiStub = sinon.stub(API, 'callApi')
    .withArgs('/agreement', 'post', agreementTestData)
    .returns(Promise.resolve(agreementTestData))
    .withArgs('/agreement', 'post', agreementTestDataError).returns(Promise.reject(agreementTestDataError));

test('saveSuccess returns correct type', actionTest(
    saveSuccess,
    { data: "some data" },
    agreementTestData,
    { type: AGREEMENT_SAVE_SUCCESS, text: 'Sopimus talletettu onnistuneesti', data: { data: "some data" } },
));

test('saveFailure returns correct type', actionTest(
    saveFailure,
    { error: "some error" },
    agreementTestData,
    { type: AGREEMENT_SAVE_FAILURE, text: 'Sopimuksen talletus epäonnistui', error: { error: "some error" } },
));

test('saveAttempt returns correct type', actionTest(
    saveAttempt,
    agreementTestData,
    { type: AGREEMENT_SAVE_ATTEMPT, text: 'Sopimuksen talletus käynnistetty' },
));

test('saveAgreement calls saveSuccess on succesful callApi promise resolution', async t => {
    let resArray = [];
    const compareArray = [saveAttempt(), saveSuccess(agreementTestData)];

    const forTest = await saveAgreement(agreementTestData)(a => resArray.push(a));

    t.deepEqual(resArray, compareArray);
});

/**
 * Tämä testi on .cb, eli callback modessa.
 * Testissä on määritelty monta vertailua tehdään (3kpl).
 * Testi loppuu vain kuin testi saavuttaa t.end() komennon.
 */
test.cb('saveAgreement calls saveSuccess on errorful callApi promise rejection', t => {
    t.plan(3);
    let compareArray = [saveAttempt(), saveFailure(agreementTestDataError.response)];
    let expectedLength = compareArray.length;
    let i = 0;

    saveAgreement(agreementTestDataError)(resObject => {
        i++;
        let temp = compareArray.shift();
        t.deepEqual(temp, resObject)
        if(i >= expectedLength){
            t.is(compareArray.length,0)
            t.end();
        }
    })
});
<commit_msg>dafuq<commit_after>// external modules
import test from 'ava';
import sinon from 'sinon';
import { actionTest } from 'redux-ava';
import 'babel-polyfill';

// internal modules
import * as API from '../../src/util/apiConnection';
import {
    saveAttempt,
    saveSuccess,
    saveFailure,
    saveAgreement,
    AGREEMENT_SAVE_ATTEMPT,
    AGREEMENT_SAVE_SUCCESS,
    AGREEMENT_SAVE_FAILURE
} from '../../src/components/agreement/AgreementActions';

const agreementTestData = { some: 'data' };
const agreementTestDataError = { response: { some: 'error' } };

const callApiStub = sinon.stub(API, 'callApi')
    .withArgs('/agreement', 'post', agreementTestData)
    .returns(Promise.resolve(agreementTestData))
    .withArgs('/agreement', 'post', agreementTestDataError).returns(Promise.reject(agreementTestDataError));

test('saveSuccess returns correct type', actionTest(
    saveSuccess,
    { data: "some data" },
    agreementTestData,
    { type: AGREEMENT_SAVE_SUCCESS, text: 'Sopimus talletettu onnistuneesti', data: { data: "some data" } },
));

test('saveFailure returns correct type', actionTest(
    saveFailure,
    { error: "some error" },
    agreementTestData,
    { type: AGREEMENT_SAVE_FAILURE, text: 'Sopimuksen talletus epäonnistui', error: { error: "some error" } },
));

test('saveAttempt returns correct type', actionTest(
    saveAttempt,
    agreementTestData,
    { type: AGREEMENT_SAVE_ATTEMPT, text: 'Sopimuksen talletus käynnistetty' },
));

test('saveAgreement calls saveSuccess on succesful callApi promise resolution', async t => {
    let resArray = [];
    const compareArray = [saveAttempt(), saveSuccess(agreementTestData)];

    const forTest = await saveAgreement(agreementTestData)(a => resArray.push(a));

    t.deepEqual(resArray, compareArray);
});

/**
 * Tämä testi on .cb, eli callback modessa.
 * Testissä on määritelty monta vertailua tehdään (3kpl).
 * Testi loppuu vain kuin testi saavuttaa t.end() komennon.
 */
test.cb('saveAgreement calls saveSuccess on errorful callApi promise rejection', t => {
    t.plan(3);
    let compareArray = [saveAttempt(), saveFailure(agreementTestDataError.response)];
    let expectedLength = compareArray.length;
    let i = 0;

    saveAgreement(agreementTestDataError)(resObject => {
        i++;
        let temp = compareArray.shift();
        t.deepEqual(temp, resObject)
        if(i >= expectedLength){
            t.is(compareArray.length,0)
            t.end();
        }
    })
});
<|endoftext|>


<commit_before>var jsdom = require('jsdom');
var Path = require('path');
var URL = require('url');
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var Step = require('step');
var format = require('util').format;
var fexists = fs.exists || Path.exists;
var Parser;
try {
	Parser = require('html5');
} catch (e) {}

jsdom.defaultDocumentFeatures = {
	FetchExternalResources: false,				// loaded depending on script[notemplate] attribute
	ProcessExternalResources: false,			// same
	MutationEvents: false,								// not needed
	QuerySelector: false									// not needed, we use jquery's bundled sizzle instead of jsdom's one.
};

var notemplate = module.exports = new EventEmitter();

// keep that in memory
var jquery = fs.readFileSync(Path.join(Path.dirname(require.resolve('jquery-browser')), 'lib/jquery.js')).toString();

function load(path, href, cb) {
	var view = { path: path };
	fs.stat(path, function(err, result) {
		if (err) return cb(err);
		fs.readFile(view.path, function(err, str) {
			if (err) return cb(err, view);
			view.window = getWindow(str, href);
			view.mtime = result.mtime;
			return cb(null, view);
		});
	});
}

function getWindow(str, href) {
	// create window with jquery
	var opts = {
		url: href || "/" // do not resolve to this file path !
	};
	if (Parser) opts.parser = Parser;
	var window = jsdom.jsdom(str, "2", opts).createWindow();
	window.navigator.server = true; // backward-compatibility - jsdom already sets window.navigator.noUI = true
	window.console = console;
	var tempfun = window.setTimeout;
	// jQuery calls setTimeout(jQuery.ready) once
	window.setTimeout = function(fun, tt) {};
	window.run(jquery);
	window.setTimeout = tempfun;
	var $ = window.jQuery;
	$._evalUrl = $.globalEval = function() {};

	return window;
}

notemplate.window = getWindow;

function loadScript(root, src, cb) {
	var url = URL.parse(src);
	if (url.hostname) return cb(format("express-notemplate error - cannot load remote script\n%s", src), null);
	var path = Path.join(root, url.pathname);
	fexists(path, function(exists) {
		if (exists) fs.readFile(path, cb);
		else cb(format("express-notemplate error - cannot find local script\n%s", path));
	});
}

function outer($nodes) {
	var ret = '';
	$nodes.each(function() {
		ret += this.outerHTML;
	});
	return ret;
}

function replaceCommentedTags(win) {
	var reg = /^\s*(\[if\s[^\]]+\]>)(.*)(<\!\[endif\])\s*$/
	var helper = win.document.createElement('div');
	var node = win.document.head.firstChild;
	while (node) {
		var cur = node;
		node = node.nextSibling;
		if (cur.nodeType != 8) continue;
		var match = reg.exec(cur.data);
		if (!match || match.length != 4) continue;
		helper.innerHTML = match[2].trim();
		var newNode = helper.firstChild;
		if (!newNode || newNode.nodeType != 1) continue;
		var parent = cur.parentNode;
		parent.insertBefore(newNode, cur);
		parent.removeChild(cur);
		newNode.setAttribute('notemplate:comment-start', match[1]);
		newNode.setAttribute('notemplate:comment-end', match[3]);
	}
}

function restoreCommentedTags(win) {
	var $ = win.$;
	var helper = win.document.createElement('div');
	$(win.document.head).find('[notemplate\\:comment-start]').each(function() {
		var comment = win.document.createComment("");
		$(this).replaceWith(comment);
		helper.appendChild(this);
		var start = this.getAttribute('notemplate:comment-start');
		this.attributes.removeNamedItem('notemplate:comment-start');
		var end = this.getAttribute('notemplate:comment-end');
		this.attributes.removeNamedItem('notemplate:comment-end');
		comment.data = start + helper.innerHTML + end;
	});
}

function merge(view, options, callback) {
	var window = view.window;
	var $ = window.$;
	var document = window.document;
	replaceCommentedTags(window);
	// call all pending document.ready listeners
	window.jQuery.ready(true);
	// view is a template, view.instance is a per-location instance of the template
	var instance = {
		window: window,
		options: options
	};
	instance.toString = toString.bind(instance);
	view.instance = instance;

	// global listeners
	notemplate.emit('data', view, options);
	// listeners from scripts loaded inside view.window
	$(document).triggerHandler('data', options);
	// global listeners

	notemplate.emit('render', view, options);

	restoreCommentedTags(window);

	if (!instance.output) instance.output = instance.toString();
	notemplate.emit('output', instance, options);
	var funClose = function() { close(view); view = null; };
	// notemplate-archive has a typical example of such an instance.output
	if (instance.output instanceof EventEmitter) {
		instance.output.on('end', funClose);
		instance.output.on('error', funClose);
	} else {
		funClose();
	}
	callback(null, instance.output);
}

function close(view) {
	if (view.instance) {
		view.instance.window.close();
		delete view.instance.window;
		delete view.instance;
	}
}

function toString() {
	var doc = this.window.document;
	var output;
	if (this.options.fragment) output = outer(this.window.$(this.options.fragment)); // output selected nodes
	else {
		output = doc.outerHTML;
		if (output.length < 2 || output.substr(0, 2) != "<!") {
			// add <!DOCTYPE... when missing (problem with parser)
			var docstr = doc.doctype.toString();
			if (output.length && output[0] != "\n") docstr += "\n";
			output = docstr + output;
		}
	}
	return output;
}

notemplate.__express = function(filename, options, callback) {
	load(filename, options.settings.href, function(err, view) {
		if (err) return callback(err);
		// the first time the DOM is ready is an event
		Step(function() {
			var group = this.group();
			view.window.$('script').each(function() {
				var script = this;
				var done = group();
				var att = script.attributes.notemplate;
				// default is notemplate="client"
				if (!att) return done();
				att = att.value;
				script.attributes.removeNamedItem('notemplate');
				// any other value is "client"
				if (att != "server" && att != "both") return done();
				var src = script.attributes.src;
				// html5 runs script content only when src is not set
				if (!src && script.textContent) view.window.run(script.textContent);
				if (att == "server") script.parentNode.removeChild(script);
				if (!src) return done();
				loadScript(options.settings.statics || process.cwd() + '/public', src.value, done);
			});
		}, function(err, scripts) {
			if (err) console.error(err); // errors are not fatal
			scripts.forEach(function(txt) {
				if (txt) view.window.run(txt.toString());
			});
			notemplate.emit('ready', view, options);
			// all scripts have been loaded
			// now we can deal with data merging
			merge(view, options, callback);
		});
	});
};

notemplate.middleware = function(req, res, next) {
	req.app.settings.href = req.protocol + '://' + req.headers.host + req.url;
	next();
};

<commit_msg>Refactor toward a View class (not yet there) and add xhr, timeout support.<commit_after>var jsdom = require('jsdom');
var Path = require('path');
var URL = require('url');
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var Step = require('step');
var format = require('util').format;
var fexists = fs.exists || Path.exists;
var Parser;
try {
	Parser = require('html5');
} catch (e) {}

jsdom.defaultDocumentFeatures = {
	FetchExternalResources: false,				// loaded depending on script[notemplate] attribute
	ProcessExternalResources: false,			// same
	MutationEvents: false,								// not needed
	QuerySelector: false									// not needed, we use jquery's bundled sizzle instead of jsdom's one.
};

var notemplate = module.exports = new EventEmitter();

// keep that in memory
var jquery = fs.readFileSync(Path.join(Path.dirname(require.resolve('jquery-browser')), 'lib/jquery.js')).toString();

function load(path, href, cb) {
	var view = {
		path: path,
		asyncs: [],
		merge: mergeView,
		render: renderView,
		done: doneView,
		close: closeView
	};
	fs.stat(path, function(err, result) {
		if (err) return cb(err);
		fs.readFile(view.path, function(err, str) {
			if (err) return cb(err, view);
			view.window = getWindow(str, href);
			handleXhrs(view);
			handleTimeouts(view);
			view.mtime = result.mtime;
			return cb(null, view);
		});
	});
}

function handleXhrs(view) {
	var window = view.window;
	var wxhr = window.XMLHttpRequest;
	window.XMLHttpRequest = function() {
		var xhr = wxhr();
		var xhrSend = xhr.send;
		xhr.send = function(data) {
			// while xhr is typically not reused, it can happen, so support it
			function listenXhr() {
				if (this.readyState != 4) return;
				xhr.removeEventListener(arguments.callee);
				arguments.callee.done = true;
				view.done();
			}
			xhr.addEventListener("readystatechange", listenXhr);
			view.asyncs.push(listenXhr);
			var ret, err
			try {
				ret = xhrSend.call(xhr, data);
			} catch(e) {
				err = e;
			}
			if (xhr.readyState == 4 || err) {
				// free it now
				listenXhr();
			} // else the call was asynchronous and no error was thrown
			if (err) throw err; // rethrow
			return ret;
		};
		return xhr;
	};
}

function handleTimeouts(view) {
	var window = view.window;
	var wto = window.setTimeout;
	window.setTimeout = function(fun, delay) {
		var args = Array.prototype.slice.call(arguments, 2);
		function listenTo() {
			fun.apply(null, args);
			arguments.callee.done = true;
			view.done();
		}
		view.asyncs.push(listenTo);
		return wto(listenTo, delay);
	};
}

function getWindow(str, href) {
	// create window with jquery
	var opts = {
		url: href || "/" // do not resolve to this file path !
	};
	if (Parser) opts.parser = Parser;
	var window = jsdom.jsdom(str, "2", opts).createWindow();
	window.navigator.server = true; // backward-compatibility - jsdom already sets window.navigator.noUI = true
	window.console = console;
	var tempfun = window.setTimeout;
	// jQuery calls setTimeout(jQuery.ready) once
	window.setTimeout = function(fun, tt) {};
	window.run(jquery);
	window.setTimeout = tempfun;
	var $ = window.jQuery;
	$._evalUrl = $.globalEval = function() {};
	return window;
}

notemplate.window = getWindow;

function loadScript(root, src, cb) {
	var url = URL.parse(src);
	if (url.hostname) return cb(format("express-notemplate error - cannot load remote script\n%s", src), null);
	var path = Path.join(root, url.pathname);
	fexists(path, function(exists) {
		if (exists) fs.readFile(path, cb);
		else cb(format("express-notemplate error - cannot find local script\n%s", path));
	});
}

function outer($nodes) {
	var ret = '';
	$nodes.each(function() {
		ret += this.outerHTML;
	});
	return ret;
}

function replaceCommentedTags(win) {
	var reg = /^\s*(\[if\s[^\]]+\]>)(.*)(<\!\[endif\])\s*$/
	var helper = win.document.createElement('div');
	var node = win.document.head.firstChild;
	while (node) {
		var cur = node;
		node = node.nextSibling;
		if (cur.nodeType != 8) continue;
		var match = reg.exec(cur.data);
		if (!match || match.length != 4) continue;
		helper.innerHTML = match[2].trim();
		var newNode = helper.firstChild;
		if (!newNode || newNode.nodeType != 1) continue;
		var parent = cur.parentNode;
		parent.insertBefore(newNode, cur);
		parent.removeChild(cur);
		newNode.setAttribute('notemplate:comment-start', match[1]);
		newNode.setAttribute('notemplate:comment-end', match[3]);
	}
}

function restoreCommentedTags(win) {
	var $ = win.$;
	var helper = win.document.createElement('div');
	$(win.document.head).find('[notemplate\\:comment-start]').each(function() {
		var comment = win.document.createComment("");
		$(this).replaceWith(comment);
		helper.appendChild(this);
		var start = this.getAttribute('notemplate:comment-start');
		this.attributes.removeNamedItem('notemplate:comment-start');
		var end = this.getAttribute('notemplate:comment-end');
		this.attributes.removeNamedItem('notemplate:comment-end');
		comment.data = start + helper.innerHTML + end;
	});
}

function mergeView(options) {
	var window = this.window;
	var $ = window.$;
	var document = window.document;
	replaceCommentedTags(window);
	// call all pending document.ready listeners
	window.jQuery.ready(true);
	// view is a template, view.instance is a per-location instance of the template
	var instance = {
		window: window,
		options: options
	};
	instance.toString = toString.bind(instance);
	this.instance = instance;

	// global listeners
	notemplate.emit('data', this, options);
	// listeners from scripts loaded inside view.window
	$(document).triggerHandler('data', options);
	this.done();
}

function renderView() {
	var view = this;
	var instance = view.instance;
	var window = instance.window;
	// global listeners
	notemplate.emit('render', view, instance.options);

	restoreCommentedTags(window);

	if (!instance.output) instance.output = instance.toString();
	notemplate.emit('output', instance, instance.options);
	var cb = view.callback;
	var funClose = function() { view.close(); };
	// notemplate-archive has a typical example of such an instance.output
	if (instance.output instanceof EventEmitter) {
		instance.output.on('end', funClose);
		instance.output.on('error', funClose);
	} else {
		funClose();
	}
	cb(null, instance.output);
}

function doneView() {
	var asyncs = this.asyncs;
	for (var i=0, len=asyncs.length; i < len; i++) {
		if (!asyncs[i].done) return;
	}
	// render if there was no asyncs registered or they were all done
	this.render();
}

function closeView() {
	if (this.instance) {
		this.instance.window.close();
		delete this.instance.window;
		delete this.instance;
	}
	this.asyncs = null;
	this.callback = null;
}

function toString() {
	var doc = this.window.document;
	var output;
	if (this.options.fragment) output = outer(this.window.$(this.options.fragment)); // output selected nodes
	else {
		output = doc.outerHTML;
		if (output.length < 2 || output.substr(0, 2) != "<!") {
			// add <!DOCTYPE... when missing (problem with parser)
			var docstr = doc.doctype.toString();
			if (output.length && output[0] != "\n") docstr += "\n";
			output = docstr + output;
		}
	}
	return output;
}

notemplate.__express = function(filename, options, callback) {
	load(filename, options.settings.href, function(err, view) {
		if (err) return callback(err);
		// the first time the DOM is ready is an event
		Step(function() {
			var group = this.group();
			view.window.$('script').each(function() {
				var script = this;
				var done = group();
				var att = script.attributes.notemplate;
				// default is notemplate="client"
				if (!att) return done();
				att = att.value;
				script.attributes.removeNamedItem('notemplate');
				// any other value is "client"
				if (att != "server" && att != "both") return done();
				var src = script.attributes.src;
				// html5 runs script content only when src is not set
				if (!src && script.textContent) view.window.run(script.textContent);
				if (att == "server") script.parentNode.removeChild(script);
				if (!src) return done();
				loadScript(options.settings.statics || process.cwd() + '/public', src.value, done);
			});
		}, function(err, scripts) {
			if (err) console.error(err); // errors are not fatal
			scripts.forEach(function(txt) {
				if (txt) view.window.run(txt.toString());
			});
			notemplate.emit('ready', view, options);
			// all scripts have been loaded
			// now we can deal with data merging
			view.callback = callback;
			view.merge(options);
		});
	});
};

notemplate.middleware = function(req, res, next) {
	req.app.settings.href = req.protocol + '://' + req.headers.host + req.url;
	next();
};

<|endoftext|>


<commit_before>// @flow

import type { FetchJSONOptions, FetchJSONServerResponse } from './fetch-json';

import invariant from 'invariant';
import _cloneDeep from 'lodash/fp/cloneDeep';
import _throttle from 'lodash/throttle';

import { getConfig } from './config';

function uploadBlob(
  url: string,
  cookie: ?string,
  sessionID: ?string,
  input: {[key: string]: mixed},
  options?: ?FetchJSONOptions,
): Promise<FetchJSONServerResponse> {
  const formData = new FormData();
  if (getConfig().setCookieOnRequest) {
    // We make sure that if setCookieOnRequest is true, we never set cookie to
    // undefined. null has a special meaning here: we don't currently have a
    // cookie, and we want the server to specify the new cookie it will generate
    // in the response body rather than the response header. See
    // session-types.js for more details on why we specify cookies in the body.
    formData.append('cookie', cookie ? cookie : "");
  }
  if (getConfig().setSessionIDOnRequest) {
    // We make sure that if setSessionIDOnRequest is true, we never set
    // sessionID to undefined. null has a special meaning here: we cannot
    // consider the cookieID to be a unique session identifier, but we do not
    // have a sessionID to use either. This should only happen when the user is
    // not logged in on web.
    formData.append('sessionID', sessionID ? sessionID : "");
  }

  for (let key in input) {
    if (key === "multimedia") {
      continue;
    }
    const value = input[key];
    invariant(
      typeof value === "string",
      "blobUpload calls can only handle string values for non-multimedia keys",
    );
    formData.append(key, value);
  }
  const { multimedia } = input;
  if (multimedia && Array.isArray(multimedia)) {
    for (let media of multimedia) {
      // We perform an any-cast here because of React Native. Though Blob
      // support was introduced in react-native@0.54, it isn't compatible with
      // FormData. Instead, React Native requires a specific object format.
      formData.append('multimedia', (media: any));
    }
  }

  const xhr = new XMLHttpRequest();
  xhr.open('POST', url);
  xhr.withCredentials = true;
  xhr.setRequestHeader('Accept', 'application/json');

  if (options && options.timeout) {
    xhr.timeout = options.timeout;
  }

  if (options && options.onProgress) {
    const { onProgress } = options;
    xhr.upload.onprogress = _throttle(
      ({ loaded, total }) => onProgress(loaded / total),
      150,
    );
  }

  let failed = false;
  const responsePromise = new Promise((resolve, reject) => {
    xhr.onload = () => {
      if (failed) {
        return;
      }
      const text = xhr.responseText;
      try {
        resolve(_cloneDeep(JSON.parse(text)));
      } catch (e) {
        console.log(text);
        reject(e);
      }
    };
    xhr.onabort = () => {
      failed = true;
      reject(new Error("request aborted"));
    };
    xhr.onerror = (event) => {
      failed = true;
      reject(event);
    };
    if (options && options.timeout) {
      xhr.ontimeout = (event) => {
        failed = true;
        reject(event);
      };
    }
    if (options && options.abortHandler) {
      options.abortHandler(() => {
        failed = true;
        reject(new Error("request aborted"));
        xhr.abort();
      });
    }
  });

  if (!failed) {
    xhr.send(formData);
  }

  return responsePromise;
}

export {
  uploadBlob,
};
<commit_msg>[lib] Throttle onProgress to once every 50ms<commit_after>// @flow

import type { FetchJSONOptions, FetchJSONServerResponse } from './fetch-json';

import invariant from 'invariant';
import _cloneDeep from 'lodash/fp/cloneDeep';
import _throttle from 'lodash/throttle';

import { getConfig } from './config';

function uploadBlob(
  url: string,
  cookie: ?string,
  sessionID: ?string,
  input: {[key: string]: mixed},
  options?: ?FetchJSONOptions,
): Promise<FetchJSONServerResponse> {
  const formData = new FormData();
  if (getConfig().setCookieOnRequest) {
    // We make sure that if setCookieOnRequest is true, we never set cookie to
    // undefined. null has a special meaning here: we don't currently have a
    // cookie, and we want the server to specify the new cookie it will generate
    // in the response body rather than the response header. See
    // session-types.js for more details on why we specify cookies in the body.
    formData.append('cookie', cookie ? cookie : "");
  }
  if (getConfig().setSessionIDOnRequest) {
    // We make sure that if setSessionIDOnRequest is true, we never set
    // sessionID to undefined. null has a special meaning here: we cannot
    // consider the cookieID to be a unique session identifier, but we do not
    // have a sessionID to use either. This should only happen when the user is
    // not logged in on web.
    formData.append('sessionID', sessionID ? sessionID : "");
  }

  for (let key in input) {
    if (key === "multimedia") {
      continue;
    }
    const value = input[key];
    invariant(
      typeof value === "string",
      "blobUpload calls can only handle string values for non-multimedia keys",
    );
    formData.append(key, value);
  }
  const { multimedia } = input;
  if (multimedia && Array.isArray(multimedia)) {
    for (let media of multimedia) {
      // We perform an any-cast here because of React Native. Though Blob
      // support was introduced in react-native@0.54, it isn't compatible with
      // FormData. Instead, React Native requires a specific object format.
      formData.append('multimedia', (media: any));
    }
  }

  const xhr = new XMLHttpRequest();
  xhr.open('POST', url);
  xhr.withCredentials = true;
  xhr.setRequestHeader('Accept', 'application/json');

  if (options && options.timeout) {
    xhr.timeout = options.timeout;
  }

  if (options && options.onProgress) {
    const { onProgress } = options;
    xhr.upload.onprogress = _throttle(
      ({ loaded, total }) => onProgress(loaded / total),
      50,
    );
  }

  let failed = false;
  const responsePromise = new Promise((resolve, reject) => {
    xhr.onload = () => {
      if (failed) {
        return;
      }
      const text = xhr.responseText;
      try {
        resolve(_cloneDeep(JSON.parse(text)));
      } catch (e) {
        console.log(text);
        reject(e);
      }
    };
    xhr.onabort = () => {
      failed = true;
      reject(new Error("request aborted"));
    };
    xhr.onerror = (event) => {
      failed = true;
      reject(event);
    };
    if (options && options.timeout) {
      xhr.ontimeout = (event) => {
        failed = true;
        reject(event);
      };
    }
    if (options && options.abortHandler) {
      options.abortHandler(() => {
        failed = true;
        reject(new Error("request aborted"));
        xhr.abort();
      });
    }
  });

  if (!failed) {
    xhr.send(formData);
  }

  return responsePromise;
}

export {
  uploadBlob,
};
<|endoftext|>


<commit_before>/*jslint node: true, nomen: true, white: true*/
/*!
 * Sitegear3
 * Copyright(c) 2014 Ben New, Sitegear.org
 * MIT Licensed
 */

(function (_, JaySchema) {
	"use strict";

	module.exports = function (schema) {
		var js = new JaySchema(module.exports.loaders.local),
			mapErrorMessage = function (error) {
				return error.message || 'Constraint "' + error.constraintName + '" broken by "' + error.testedValue + '", expected "' + error.constraintValue + '"' + (error.desc ? ' (' + error.desc + ')' : '');
			};
		return function (data, callback) {
			js.validate(data, schema, function (errors) {
				if (errors && errors.length > 0) {
					var messages = _.map(errors, mapErrorMessage);
					callback(new Error('Received ' + errors.length + ' errors from JSON schema validator: [[ ' + messages.join(' ]]; [[ ') + ' ]]'));
				} else {
					callback();
				}
			});
		};
	};

	module.exports.loaders = {
		local: function (ref, callback) {
			if (module.exports.localSchema.hasOwnProperty(ref)) {
				callback(null, module.exports.localSchema[ref]);
			}
			callback(new Error('Attempted to load unknown local schema with ref "' + ref + '"'));
		}
	};

	module.exports.localSchema = {
		"http://json-schema.org/address": {
		    "description": "An Address following the convention of http://microformats.org/wiki/hcard",
		    "type": "object",
		    "properties": {
		        "post-office-box": { "type": "string" },
		        "extended-address": { "type": "string" },
		        "street-address": { "type": "string" },
		        "locality":{ "type": "string", "required": true },
		        "region": { "type": "string", "required": true },
		        "postal-code": { "type": "string" },
		        "country-name": { "type": "string", "required": true}
		    },
		    "dependencies": {
		        "post-office-box": "street-address",
		        "extended-address": "street-address"
		    }
		},
		"http://json-schema.org/calendar": {
		    "description": "A representation of an event",
		    "type": "object",
		    "properties": {
		        "dtstart": {
		            "format": "date-time",
		            "type": "string",
		            "description": "Event starting time",
		            "required": true
		        },
		        "dtend": {
		            "format": "date-time",
		            "type": "string",
		            "description": "Event ending time"
		        },
		        "summary": { "type": "string", "required": true },
		        "location": { "type": "string" },
		        "url": { "type": "string", "format": "uri" },
		        "duration": {
		            "format": "time",
		            "type": "string",
		            "description": "Event duration"
		        },
		        "rdate": {
		            "format": "date-time",
		            "type": "string",
		            "description": "Recurrence date"
		        },
		        "rrule": {
		            "type": "string",
		            "description": "Recurrence rule"
		        },
		        "category": { "type": "string" },
		        "description": { "type": "string" },
		        "geo": { "$ref": "http: //json-schema.org/geo" }
		    }
		},
		"http://json-schema.org/card": {
		    "$schema": "http://json-schema.org/draft-03/schema#",
		    "description": "A representation of a person, company, organization, or place",
		    "type": "object",
		    "properties": {
		        "fn": {
		            "description": "Formatted Name",
		            "type": "string"
		        },
		        "familyName": { "type": "string", "required": true },
		        "givenName": { "type": "string", "required": true },
		        "additionalName": { "type": "array", "items": { "type": "string" } },
		        "honorificPrefix": { "type": "array", "items": { "type": "string" } },
		        "honorificSuffix": { "type": "array", "items": { "type": "string" } },
		        "nickname": { "type": "string" },
		        "url": { "type": "string", "format": "uri" },
		        "email": {
		            "type": "object",
		            "properties": {
		                "type": { "type": "string" },
		                "value": { "type": "string", "format": "email" }
		            }
		        },
		        "tel": {
		            "type": "object",
		            "properties": {
		                "type": { "type": "string" },
		                "value": { "type": "string", "format": "phone" }
		            }
		        },
		        "adr": { "$ref": "http://json-schema.org/address" },
		        "geo": { "$ref": "http://json-schema.org/geo" },
		        "tz": { "type": "string" },
		        "photo": { "type": "string" },
		        "logo": { "type": "string" },
		        "sound": { "type": "string" },
		        "bday": { "type": "string", "format": "date" },
		        "title": { "type": "string" },
		        "role": { "type": "string" },
		        "org": {
		            "type": "object",
		            "properties": {
		                "organizationName": { "type": "string" },
		                "organizationUnit": { "type": "string" }
		            }
		        }
		    }
		},
		"http://json-schema.org/geo": {
			"description": "A geographical coordinate",
			"type": "object",
			"properties": {
				"latitude": { "type": "number" },
				"longitude": { "type": "number" }
			}
		}
	};
}(require('lodash'), require('jayschema')));
<commit_msg>Add todo.<commit_after>/*jslint node: true, nomen: true, white: true, todo: true*/
/*!
 * Sitegear3
 * Copyright(c) 2014 Ben New, Sitegear.org
 * MIT Licensed
 */

(function (_, JaySchema) {
	"use strict";

	module.exports = function (schema) {
		var js = new JaySchema(module.exports.loaders.local),
			mapErrorMessage = function (error) {
				// TODO Allow this to be overridden
				return error.message || 'Constraint "' + error.constraintName + '" broken by "' + error.testedValue + '", expected "' + error.constraintValue + '"' + (error.desc ? ' (' + error.desc + ')' : '');
			};
		return function (data, callback) {
			js.validate(data, schema, function (errors) {
				if (errors && errors.length > 0) {
					var messages = _.map(errors, mapErrorMessage);
					callback(new Error('Received ' + errors.length + ' errors from JSON schema validator: [[ ' + messages.join(' ]]; [[ ') + ' ]]'));
				} else {
					callback();
				}
			});
		};
	};

	module.exports.loaders = {
		local: function (ref, callback) {
			if (module.exports.localSchema.hasOwnProperty(ref)) {
				callback(null, module.exports.localSchema[ref]);
			}
			callback(new Error('Attempted to load unknown local schema with ref "' + ref + '"'));
		}
	};

	module.exports.localSchema = {
		"http://json-schema.org/address": {
		    "description": "An Address following the convention of http://microformats.org/wiki/hcard",
		    "type": "object",
		    "properties": {
		        "post-office-box": { "type": "string" },
		        "extended-address": { "type": "string" },
		        "street-address": { "type": "string" },
		        "locality":{ "type": "string", "required": true },
		        "region": { "type": "string", "required": true },
		        "postal-code": { "type": "string" },
		        "country-name": { "type": "string", "required": true}
		    },
		    "dependencies": {
		        "post-office-box": "street-address",
		        "extended-address": "street-address"
		    }
		},
		"http://json-schema.org/calendar": {
		    "description": "A representation of an event",
		    "type": "object",
		    "properties": {
		        "dtstart": {
		            "format": "date-time",
		            "type": "string",
		            "description": "Event starting time",
		            "required": true
		        },
		        "dtend": {
		            "format": "date-time",
		            "type": "string",
		            "description": "Event ending time"
		        },
		        "summary": { "type": "string", "required": true },
		        "location": { "type": "string" },
		        "url": { "type": "string", "format": "uri" },
		        "duration": {
		            "format": "time",
		            "type": "string",
		            "description": "Event duration"
		        },
		        "rdate": {
		            "format": "date-time",
		            "type": "string",
		            "description": "Recurrence date"
		        },
		        "rrule": {
		            "type": "string",
		            "description": "Recurrence rule"
		        },
		        "category": { "type": "string" },
		        "description": { "type": "string" },
		        "geo": { "$ref": "http: //json-schema.org/geo" }
		    }
		},
		"http://json-schema.org/card": {
		    "$schema": "http://json-schema.org/draft-03/schema#",
		    "description": "A representation of a person, company, organization, or place",
		    "type": "object",
		    "properties": {
		        "fn": {
		            "description": "Formatted Name",
		            "type": "string"
		        },
		        "familyName": { "type": "string", "required": true },
		        "givenName": { "type": "string", "required": true },
		        "additionalName": { "type": "array", "items": { "type": "string" } },
		        "honorificPrefix": { "type": "array", "items": { "type": "string" } },
		        "honorificSuffix": { "type": "array", "items": { "type": "string" } },
		        "nickname": { "type": "string" },
		        "url": { "type": "string", "format": "uri" },
		        "email": {
		            "type": "object",
		            "properties": {
		                "type": { "type": "string" },
		                "value": { "type": "string", "format": "email" }
		            }
		        },
		        "tel": {
		            "type": "object",
		            "properties": {
		                "type": { "type": "string" },
		                "value": { "type": "string", "format": "phone" }
		            }
		        },
		        "adr": { "$ref": "http://json-schema.org/address" },
		        "geo": { "$ref": "http://json-schema.org/geo" },
		        "tz": { "type": "string" },
		        "photo": { "type": "string" },
		        "logo": { "type": "string" },
		        "sound": { "type": "string" },
		        "bday": { "type": "string", "format": "date" },
		        "title": { "type": "string" },
		        "role": { "type": "string" },
		        "org": {
		            "type": "object",
		            "properties": {
		                "organizationName": { "type": "string" },
		                "organizationUnit": { "type": "string" }
		            }
		        }
		    }
		},
		"http://json-schema.org/geo": {
			"description": "A geographical coordinate",
			"type": "object",
			"properties": {
				"latitude": { "type": "number" },
				"longitude": { "type": "number" }
			}
		}
	};
}(require('lodash'), require('jayschema')));
<|endoftext|>


<commit_before>'use babel';
/**
  Eval Console
  @description Provides a console where user can enter input in order to
    evaluate Go expressions within the current scope of the debugger in
    a REPL-esque style

  TODO: We will need to listen for the enter key from the input element,
    after hitting enter:
    - send to DebuggerActions.evalExpression (should return promise w/ result)
    - get result & write under the input (need to figure out how we're going to
        pretty print the resulting Go code)
    - disable the "spent" input element
    - create a new input element at the bottom of the console

  TODO: for the eval console input we might want to use a textarea to allow
    the user to type in multiple lines
**/

/* eslint no-unused-vars:0 */
import { createClass as createJSX, jsx } from 'vanilla-jsx';

/** @jsx jsx */
const DelveExprVal = createJSX({
  renderView(val) {
    return
{val}
;
  }
});

/** @jsx jsx */
const DelveEvalInput = createJSX({
  renderView() {
    return (

        <input type="text" class="delve-terminal-input delve-eval-textbox cur-input" />

    );
  },

  deactivate() {
    this.element.children[0].disabled = true;
    this.element.children[0].classList.remove('cur-input');
  }
});

/** @jsx jsx */
const EvalConsoleElement = createJSX({
  renderView() {
    return (

Eval Console

    );
  },

  addExpressionValue(val) {
    let outputComp = new DelveExprVal();
    let $val = outputComp.render(val);

    this.element.appendChild($val);
  },

  addNewEvalInput() {
    if (this.curInput) this.curInput.deactivate();
    let newInput = new DelveEvalInput();
    this.element.appendChild(newInput.render());
    this.curInput = newInput;

    // first child is always the <input /> itself
    return newInput.element.children[0];
  }
});

export default class EvalConsole {
  constructor() {
    this.consoleElement = new EvalConsoleElement();
    this.evalConsole = this.consoleElement.render();
    this.$curInput = this.consoleElement.addNewEvalInput();

    this.$curInput
    .addEventListener('keyup', this.sendEvalExpr.bind(this), true);
  }

  sendEvalExpr(evt) {
    if (evt.code !== 'Enter' || evt.shiftKey || !this.$curInput) return;

    const expression = this.$curInput.value.trim();

    atom.notifications.addInfo(
      'Would normally eval the expression', { detail: expression }
    );

    // NOTE: temporary
    const val = '&interface{}';
    this.consoleElement.addExpressionValue(val);
    this.$curInput = this.consoleElement.addNewEvalInput();

    this.$curInput
    .addEventListener('keyup', this.sendEvalExpr.bind(this), true);

    evt.preventDefault();
  }

  getElement() {
    return this.evalConsole;
  }

  destroy() {
    this.consoleElement.dispose();
  }
}
<commit_msg>Focus next input after evaluating<commit_after>'use babel';
/**
  Eval Console
  @description Provides a console where user can enter input in order to
    evaluate Go expressions within the current scope of the debugger in
    a REPL-esque style

  TODO: We will need to listen for the enter key from the input element,
    after hitting enter:
    - send to DebuggerActions.evalExpression (should return promise w/ result)
    - get result & write under the input (need to figure out how we're going to
        pretty print the resulting Go code)
    - disable the "spent" input element
    - create a new input element at the bottom of the console

  TODO: for the eval console input we might want to use a textarea to allow
    the user to type in multiple lines
**/

/* eslint no-unused-vars:0 */
import { createClass as createJSX, jsx } from 'vanilla-jsx';

/** @jsx jsx */
const DelveExprVal = createJSX({
  renderView(val) {
    return
{val}
;
  }
});

/** @jsx jsx */
const DelveEvalInput = createJSX({
  renderView() {
    return (

        <input type="text" class="delve-terminal-input delve-eval-textbox cur-input" />

    );
  },

  deactivate() {
    this.element.children[0].disabled = true;
    this.element.children[0].classList.remove('cur-input');
  }
});

/** @jsx jsx */
const EvalConsoleElement = createJSX({
  renderView() {
    return (

Eval Console

    );
  },

  addExpressionValue(val) {
    let outputComp = new DelveExprVal();
    let $val = outputComp.render(val);

    this.element.appendChild($val);
  },

  addNewEvalInput() {
    if (this.curInput) this.curInput.deactivate();
    let newInput = new DelveEvalInput();
    this.element.appendChild(newInput.render());
    this.curInput = newInput;

    // first child is always the <input /> itself
    return newInput.element.children[0];
  }
});

export default class EvalConsole {
  constructor() {
    this.consoleElement = new EvalConsoleElement();
    this.evalConsole = this.consoleElement.render();
    this.$curInput = this.consoleElement.addNewEvalInput();

    this.$curInput
    .addEventListener('keyup', this.sendEvalExpr.bind(this), true);
  }

  sendEvalExpr(evt) {
    if (evt.code !== 'Enter' || evt.shiftKey || !this.$curInput) return;

    const expression = this.$curInput.value.trim();

    atom.notifications.addInfo(
      'Would normally eval the expression', { detail: expression }
    );

    // NOTE: temporary
    const val = '&interface{}';
    this.consoleElement.addExpressionValue(val);
    this.$curInput = this.consoleElement.addNewEvalInput();

    this.$curInput
    .addEventListener('keyup', this.sendEvalExpr.bind(this), true);

    // TODO: after we add the val and then add the new input, if it's at the
    // bottom of the eval console the focused element will not be scrolled
    // into view (but will be after enter is pressed)
    this.$curInput.focus();
    evt.preventDefault();
  }

  getElement() {
    return this.evalConsole;
  }

  destroy() {
    this.consoleElement.dispose();
  }
}
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var util = require('./util');

var endpoint = require('../lib/endpoint');
var Endpoint = endpoint.Endpoint;

var settings = {
  SETTINGS_MAX_CONCURRENT_STREAMS: 100,
  SETTINGS_INITIAL_WINDOW_SIZE: 100000
};

describe('endpoint.js', function() {
  describe('scenario', function() {
    describe('connection setup', function() {
      it('should work as expected', function(done) {
        var c = new Endpoint('CLIENT', settings, util.log.child({ role: 'client' }));
        var s = new Endpoint('SERVER', settings, util.log.child({ role: 'server' }));

        util.log.debug('Test initialization over, starting piping.');
        c.pipe(s).pipe(c);

        setTimeout(function() {
          // If there are no exception until this, then we're done
          done();
        }, 10);
      });
    });
  });
});
<commit_msg>Endpoint tests: testing endpoint formatter.<commit_after>var expect = require('chai').expect;
var util = require('./util');

var endpoint = require('../lib/endpoint');
var Endpoint = endpoint.Endpoint;

var settings = {
  SETTINGS_MAX_CONCURRENT_STREAMS: 100,
  SETTINGS_INITIAL_WINDOW_SIZE: 100000
};

describe('endpoint.js', function() {
  describe('scenario', function() {
    describe('connection setup', function() {
      it('should work as expected', function(done) {
        var c = new Endpoint('CLIENT', settings, util.log.child({ role: 'client' }));
        var s = new Endpoint('SERVER', settings, util.log.child({ role: 'server' }));

        util.log.debug('Test initialization over, starting piping.');
        c.pipe(s).pipe(c);

        setTimeout(function() {
          // If there are no exception until this, then we're done
          done();
        }, 10);
      });
    });
  });
  describe('bunyan serializer', function() {
    describe('`e`', function() {
      var format = endpoint.serializers.e;
      it('should assign a unique ID to each endpoint', function() {
        var c = new Endpoint('CLIENT', settings, util.log.child({ role: 'client' }));
        var s = new Endpoint('SERVER', settings, util.log.child({ role: 'server' }));
        expect(format(c)).to.not.equal(format(s));
        expect(format(c)).to.equal(format(c));
        expect(format(s)).to.equal(format(s));
      });
    });
  });
});
<|endoftext|>


<commit_before>/* collection of game related helper functions */
var $db = require('./mongo');
var debug = require('debug')('wol:leaderboard');
var gameres = require('./gameres');
var $q = require('q');

exports.process = function(game, dmp) {
    var match = gameres.parse(dmp);

    /* discontinue if no game id or match is less than 5 seconds */
    if (!match.idno || match.dura < 5) return;
    debug('game: %s, idno: %d', game, match.idno);

    // create raw dump entry
    // TODO: check against spid (sender id) to ensure only 1 packet from each player
    // TODO: idno is random but could collide. check if record exists and compare dates
    var $dumps = $db.get(game +'_dumps');
    $dumps.update(
        {idno: match.idno},
        {$push: {buffers: match.buffer}},
        {upsert: true},
        _stats
    );

    function _stats(err, data) {
        $dumps.find({idno: match.idno}, function(err, doc) {
            if (doc.length > 1) {
                // error scneario, we have two entries for the same game
                // todo: figure out what the hell to do?
                // todo: elimiate duplicates?
                console.log('found duplicates! game: %s, idno: %d', game, match.idno);
                return;
            }

            /* if we have ra stats normalize then carry on  */
            if (game == 'ra') {
                if (match.nump !== 2) return; /* only process stats for 1v1 */

                /* hack for now to process games only when 2 packets received */
                if (doc[0].buffers && doc[0].buffers.length == 2) {

                    /* interpret packet and update it to use wolv2 completion stats */
                    gameres.process(game, gameres.normalize(game, match));
                }
                return;
            }

            // only continue if this is the first entry for a game
            if (doc[0].buffers && doc[0].buffers.length > 1) return;

            /* save game stats */
            gameres.process(game, match);
        });
    }

    return;
};

exports.information = function(game, gameId) {
    var defer = $q.defer();
    $db.get(game +'_games').find({idno: gameId}, function(err, data) {
        if (data.length < 1) return defer.reject();
        defer.resolve(data[0]);
    });
    return defer.promise;
};
<commit_msg>only proceess valid completion status<commit_after>/* collection of game related helper functions */
var $db = require('./mongo');
var debug = require('debug')('wol:leaderboard');
var gameres = require('./gameres');
var $q = require('q');

exports.process = function(game, dmp) {
    var match = gameres.parse(dmp);

    /* discontinue if no game id or match is less than 5 seconds */
    if (!match.idno || match.dura < 5) return;
    debug('game: %s, idno: %d', game, match.idno);

    // create raw dump entry
    // TODO: check against spid (sender id) to ensure only 1 packet from each player
    // TODO: idno is random but could collide. check if record exists and compare dates
    var $dumps = $db.get(game +'_dumps');
    $dumps.update(
        {idno: match.idno},
        {$push: {buffers: match.buffer}},
        {upsert: true},
        _stats
    );

    function _stats(err, data) {
        $dumps.find({idno: match.idno}, function(err, doc) {
            if (doc.length > 1) {
                // error scneario, we have two entries for the same game
                // todo: figure out what the hell to do?
                // todo: elimiate duplicates?
                console.log('found duplicates! game: %s, idno: %d', game, match.idno);
                return;
            }

            /* if we have ra stats normalize then carry on  */
            if (game == 'ra') {
                if (match.nump !== 2) return; /* only process stats for 1v1 */

                /* hack for now to process games only when 2 packets received */
                if (doc[0].buffers && doc[0].buffers.length == 2) {
                    if (match.client.cmpl > -1) return; /* only process legit cmpl */
                    // TODO: attempt to process other buffer if current one is bad

                    /* interpret packet and update it to use wolv2 completion stats */
                    gameres.process(game, gameres.normalize(game, match));
                }
                return;
            }

            // only continue if this is the first entry for a game
            if (doc[0].buffers && doc[0].buffers.length > 1) return;

            /* save game stats */
            gameres.process(game, match);
        });
    }

    return;
};

exports.information = function(game, gameId) {
    var defer = $q.defer();
    $db.get(game +'_games').find({idno: gameId}, function(err, data) {
        if (data.length < 1) return defer.reject();
        defer.resolve(data[0]);
    });
    return defer.promise;
};
<|endoftext|>


<commit_before>'use babel';

/* @flow*/
import { $, ScrollView } from 'atom-space-pen-views';
import { convertAnsiStringToHtml, escapeHtml } from '../helpers';

const defaultMessage = 'Nothing new to show';

class OutputView extends ScrollView {
  static initClass() {
    this.prototype.html = defaultMessage;
  }
  static content() {
    return this.div({
      class: 'tester-output-view',
    }, () => {
      this.div({
        class: 'tester-resize-handle',
        style: 'position: absolute; top: 0; left: 0; right: 0; height: 4px; cursor: row-resize; z-index: 3',
      });

      this.div({
        class: 'tester-panel-head block highlight',
      }, () => {
        this.div({
          class: 'tester-panel-title inline-block icon icon-terminal',
          outlet: 'panelTitle',
          style: 'cursor: default;',
        }, 'Tester Output');
        this.div({
          class: 'tester-panel-buttons btn-toolbar inline-block pull-right',
        }, () => {
          this.div({
            class: 'tester-panel-scroll-bottom inline-block icon-arrow-down',
            title: 'Scroll to bottom',
            style: 'cursor: pointer;',
            outlet: 'btnScrollToBottom',
            click: 'scrollToBottom',
          });
          this.div({
            class: 'tester-panel-scroll-bottom inline-block icon-arrow-up',
            title: 'Scroll to top',
            style: 'cursor: pointer;',
            outlet: 'btnScrollToTop',
            click: 'scrollToTop',
          });
          this.div({
            class: 'tester-panel-clear inline-block icon-circle-slash',
            title: 'Clear Output',
            style: 'cursor: pointer;',
            outlet: 'btnClear',
            click: 'clear',
          });
          this.div({
            class: 'tester-panel-close inline-block icon-x',
            title: 'Close',
            style: 'cursor: pointer;',
            outlet: 'btnClose',
            click: 'close',
          });
        });
      });

      this.pre({
        class: 'output',
      }, defaultMessage);
    });
  }

  initialize() {
    this.find('.tester-resize-handle').mousedown(() => this.resizeStarted());
    return super.initialize(...arguments);
  }

  resizeStarted() {
    $(this).css({
      WebkitUserSelect: 'none',
    });

    $(document).on('mousemove', { that: this }, this.resizePanel);
    $(document).on('mouseup', { that: this }, this.resizeStopped);
  }

  resizeStopped(e/* :Object*/) {
    $(e.data.that).css({
      WebkitUserSelect: '',
    });

    $(document).off('mousemove', this.resizePanel);
    $(document).off('mouseup', this.resizeStopped);
  }

  resizePanel(e/* :Object*/) {
    const panelBody = $('.output', e.data.that);
    const newHeight = $(document.body).height() - e.pageY - 56;
    panelBody.css({
      height: newHeight,
      maxHeight: newHeight,
    });
  }

  clear() {
    this.html = '';
    this.find('.output').html(this.html);
  }

  reset() {
    this.html = defaultMessage;
  }

  setContent(content /* :string*/, shouldAnsiToHtml /* : boolean*/) {
    content = escapeHtml(content);
    if (shouldAnsiToHtml) {
      content = convertAnsiStringToHtml(content);
    }
    this.html += content;
    return this;
  }

  finish(showOutputAfterTestRun/* :boolean*/, messageTimeout/* :number*/) {
    this.find('.output').html(this.html);
    if (!this.isVisible() && showOutputAfterTestRun) {
      this.show();
      if (messageTimeout && messageTimeout !== 0) {
        this.timeout = setTimeout(() =>
              this.hide(), messageTimeout * 1000);
      }
    }
    this.scrollToBottom();
  }

  scrollToTop() {
    this.find('.output').animate({ scrollTop: this.height() }, 1000);
  }

  scrollToBottom() {
    this.find('.output').animate({ scrollTop: 0 }, 1000);
  }

  close() {
    this.hide();
  }

  toggle() {
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
    return $.fn.toggle.call(this);
  }
}
OutputView.initClass();

export default OutputView;
<commit_msg>fix scroll<commit_after>'use babel';

/* @flow*/
import { $, ScrollView } from 'atom-space-pen-views';
import { convertAnsiStringToHtml, escapeHtml } from '../helpers';

const defaultMessage = 'Nothing new to show';

class OutputView extends ScrollView {
  static initClass() {
    this.prototype.html = defaultMessage;
  }
  static content() {
    return this.div({
      class: 'tester-output-view',
    }, () => {
      this.div({
        class: 'tester-resize-handle',
        style: 'position: absolute; top: 0; left: 0; right: 0; height: 4px; cursor: row-resize; z-index: 3',
      });

      this.div({
        class: 'tester-panel-head block highlight',
      }, () => {
        this.div({
          class: 'tester-panel-title inline-block icon icon-terminal',
          outlet: 'panelTitle',
          style: 'cursor: default;',
        }, 'Tester Output');
        this.div({
          class: 'tester-panel-buttons btn-toolbar inline-block pull-right',
        }, () => {
          this.div({
            class: 'tester-panel-scroll-bottom inline-block icon-arrow-down',
            title: 'Scroll to bottom',
            style: 'cursor: pointer;',
            outlet: 'btnScrollToBottom',
            click: 'scrollToBottom',
          });
          this.div({
            class: 'tester-panel-scroll-bottom inline-block icon-arrow-up',
            title: 'Scroll to top',
            style: 'cursor: pointer;',
            outlet: 'btnScrollToTop',
            click: 'scrollToTop',
          });
          this.div({
            class: 'tester-panel-clear inline-block icon-circle-slash',
            title: 'Clear Output',
            style: 'cursor: pointer;',
            outlet: 'btnClear',
            click: 'clear',
          });
          this.div({
            class: 'tester-panel-close inline-block icon-x',
            title: 'Close',
            style: 'cursor: pointer;',
            outlet: 'btnClose',
            click: 'close',
          });
        });
      });

      this.pre({
        class: 'output',
      }, defaultMessage);
    });
  }

  initialize() {
    this.find('.tester-resize-handle').mousedown(() => this.resizeStarted());
    return super.initialize(...arguments);
  }

  resizeStarted() {
    $(this).css({
      WebkitUserSelect: 'none',
    });

    $(document).on('mousemove', { that: this }, this.resizePanel);
    $(document).on('mouseup', { that: this }, this.resizeStopped);
  }

  resizeStopped(e/* :Object*/) {
    $(e.data.that).css({
      WebkitUserSelect: '',
    });

    $(document).off('mousemove', this.resizePanel);
    $(document).off('mouseup', this.resizeStopped);
  }

  resizePanel(e/* :Object*/) {
    const panelBody = $('.output', e.data.that);
    const newHeight = $(document.body).height() - e.pageY - 56;
    panelBody.css({
      height: newHeight,
      maxHeight: newHeight,
    });
  }

  clear() {
    this.html = '';
    this.find('.output').html(this.html);
  }

  reset() {
    this.html = defaultMessage;
  }

  setContent(content /* :string*/, shouldAnsiToHtml /* : boolean*/) {
    content = escapeHtml(content);
    if (shouldAnsiToHtml) {
      content = convertAnsiStringToHtml(content);
    }
    this.html += content;
    return this;
  }

  finish(showOutputAfterTestRun/* :boolean*/, messageTimeout/* :number*/) {
    this.find('.output').html(this.html);
    if (!this.isVisible() && showOutputAfterTestRun) {
      this.show();
      if (messageTimeout && messageTimeout !== 0) {
        this.timeout = setTimeout(() =>
              this.hide(), messageTimeout * 1000);
      }
    }
    this.scrollToBottom();
  }

  scrollToBottom() {
    const output = this.find('.output');
    output.animate({ scrollTop: output.height() }, 1000);
  }

  scrollToTop() {
    this.find('.output').animate({ scrollTop: 0 }, 1000);
  }

  close() {
    this.hide();
  }

  toggle() {
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
    return $.fn.toggle.call(this);
  }
}
OutputView.initClass();

export default OutputView;
<|endoftext|>


<commit_before>const express = require('express');

const api = express();
const bodyParser = require('body-parser');

const User = require('./resources/user');
const Period = require('./resources/period');
const PeriodType = require('./resources/period_type');
const Timesheet = require('./resources/timesheet');

api.use(bodyParser.json()); // for parsing application/json
api.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

/**
 * add extra debug information to sentry
 */
api.use((req, res, next) => {
    const sentryClient = api.get('sentry_client');
    if (!sentryClient) {
        next();
        return;
    }

    // add request information
    sentryClient.mergeContext({
        extra: {
            url: req.originalUrl,
            method: req.method,
            params: req.params,
            request: req,
        },
    });

    next();
});

/**
 * add user information to the sentry client so we have this data to debug in case of errors
 */
api.param('user', (req, res, next, id) => {
    // try to get the user details from the User model and attach it to the request object
    User.get(api.get('pg'), id, (user) => {
        if (user) {
            const sentryClient = api.get('sentry_client');
            if (sentryClient) {
                sentryClient.mergeContext({ user });
            }
            next();
        } else {
            next(new Error('failed to load user'));
        }
    });
});

api.get('/users', (req, res) => {
    console.info('API GET Request for Users');
    User.list(api.get('pg'), (users) => {
        res.json(users);
    });
});

api.get('/period-types', (req, res) => {
    console.info('API GET Request for Period Types');
    PeriodType.list(api.get('pg'), (types) => {
        res.json(types);
    });
});

api.get('/users/:user/timesheet/:from/:to', (req, res) => {
    console.info('API GET Request for Timesheet', req.params.from, 'to', req.params.to, 'for user', req.params.user);
    Timesheet.get(api.get('pg'), req.params.user, req.params.from, req.params.to, (timesheet) => {
        res.json(timesheet);
    });
});

api.post('/users/:user/periods', (req, res) => {
    console.info('API POST Request for Period for user', req.params.user);
    const data = req.body;
    data.userId = req.params.user;

    // Validation
    if (!validateData(data, res)) {
        return;
    }
    // POST specific Validation
    if (!data.date) {
        res.status(400).send('Missing Date!').end();
        return;
    }

    Period.post(api.get('pg'), req.params.user, data, (period) => {
        res.status(201);
        res.json(period);
    });
});

api.put('/users/:user/periods/:id', (req, res) => {
    console.info('API PUT Request for Period', req.params.id, 'for user', req.params.user);

    const data = req.body;
    data.per_id = parseInt(req.params.id, 10);

    // Validation
    if (!validateData(data, res)) {
        return;
    }

    Period.put(api.get('pg'), req.params.user, data, (period) => {
        res.json(period);
    });
});

api.delete('/users/:user/periods/:id', (req, res) => {
    console.info('API DELETE Request for Period');
    Period.delete(api.get('pg'), req.params.id, req.params.user, () => {
        res.status(204).end();
    });
});

api.all(/.*/, (req, res) => {
    res.status(405).send('Bad Request').end();
});

function validateData(data, res) {
    if (!data.per_pty_id) {
        res.status(400).send('Missing Period Type!').end();
        return false;
    }

    if (!data.per_start && !data.per_duration) {
        res.status(400).send('Missing Data \'Start\' or \'Duration\'!').end();
        return false;
    }

    return true;
}

exports.api = api;
<commit_msg>add periods id and user to console info on periods DELETE request<commit_after>const express = require('express');

const api = express();
const bodyParser = require('body-parser');

const User = require('./resources/user');
const Period = require('./resources/period');
const PeriodType = require('./resources/period_type');
const Timesheet = require('./resources/timesheet');

api.use(bodyParser.json()); // for parsing application/json
api.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

/**
 * add extra debug information to sentry
 */
api.use((req, res, next) => {
    const sentryClient = api.get('sentry_client');
    if (!sentryClient) {
        next();
        return;
    }

    // add request information
    sentryClient.mergeContext({
        extra: {
            url: req.originalUrl,
            method: req.method,
            params: req.params,
            request: req,
        },
    });

    next();
});

/**
 * add user information to the sentry client so we have this data to debug in case of errors
 */
api.param('user', (req, res, next, id) => {
    // try to get the user details from the User model and attach it to the request object
    User.get(api.get('pg'), id, (user) => {
        if (user) {
            const sentryClient = api.get('sentry_client');
            if (sentryClient) {
                sentryClient.mergeContext({ user });
            }
            next();
        } else {
            next(new Error('failed to load user'));
        }
    });
});

api.get('/users', (req, res) => {
    console.info('API GET Request for Users');
    User.list(api.get('pg'), (users) => {
        res.json(users);
    });
});

api.get('/period-types', (req, res) => {
    console.info('API GET Request for Period Types');
    PeriodType.list(api.get('pg'), (types) => {
        res.json(types);
    });
});

api.get('/users/:user/timesheet/:from/:to', (req, res) => {
    console.info('API GET Request for Timesheet', req.params.from, 'to', req.params.to, 'for user', req.params.user);
    Timesheet.get(api.get('pg'), req.params.user, req.params.from, req.params.to, (timesheet) => {
        res.json(timesheet);
    });
});

api.post('/users/:user/periods', (req, res) => {
    console.info('API POST Request for Period for user', req.params.user);
    const data = req.body;
    data.userId = req.params.user;

    // Validation
    if (!validateData(data, res)) {
        return;
    }
    // POST specific Validation
    if (!data.date) {
        res.status(400).send('Missing Date!').end();
        return;
    }

    Period.post(api.get('pg'), req.params.user, data, (period) => {
        res.status(201);
        res.json(period);
    });
});

api.put('/users/:user/periods/:id', (req, res) => {
    console.info('API PUT Request for Period', req.params.id, 'for user', req.params.user);

    const data = req.body;
    data.per_id = parseInt(req.params.id, 10);

    // Validation
    if (!validateData(data, res)) {
        return;
    }

    Period.put(api.get('pg'), req.params.user, data, (period) => {
        res.json(period);
    });
});

api.delete('/users/:user/periods/:id', (req, res) => {
    console.info('API DELETE Request for Period', req.params.id, 'for user', req.params.user);
    Period.delete(api.get('pg'), req.params.id, req.params.user, () => {
        res.status(204).end();
    });
});

api.all(/.*/, (req, res) => {
    res.status(405).send('Bad Request').end();
});

function validateData(data, res) {
    if (!data.per_pty_id) {
        res.status(400).send('Missing Period Type!').end();
        return false;
    }

    if (!data.per_start && !data.per_duration) {
        res.status(400).send('Missing Data \'Start\' or \'Duration\'!').end();
        return false;
    }

    return true;
}

exports.api = api;
<|endoftext|>


<commit_before>// Modifications copyright 2020 Caf.js Labs and contributors
/*!
 Copyright 2013 Hewlett-Packard Development Company, L.P.

 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.
 */

'use strict';
/**
 *  Provides access to BLE GATT services.
 *
 *  * Properties:
 *
 *  {findCharactTimeout: number, RWCharactTimeout: number}
 *
 * `findCharactTimeout` is a timeout in msec to detect a hang while finding
 * a BLE device characteristics.
 *
 * `RWCharactTimeout` is a timeout in msec to detect a hang while
 * reading or writing a BLE device characteristic.
 *
 * @module caf_iot_gatt/plug_iot_gatt
 * @augments external:caf_iot/gen_plug_iot
 *
 */
const assert = require('assert');
const caf_iot = require('caf_iot');
const caf_comp = caf_iot.caf_components;
const myUtils = caf_comp.myUtils;
const async = caf_comp.async;
const genPlugIoT = caf_iot.gen_plug_iot;
const noble = require('@abandonware/noble');
const utilGatt = require('./util_gatt');

/**
 * Factory method for a plug to  BLE GATT services.
 *
 */
exports.newInstance = async function($, spec) {
    try {
        const that = genPlugIoT.create($, spec);

        $._.$.log && $._.$.log.debug('New BLE GATT plug');

        assert.equal(typeof(spec.env.findCharactTimeout), 'number',
                     "'spec.env.findCharactTimeout' is not a number");
        const findCharactTimeout = spec.env.findCharactTimeout;

        assert.equal(typeof(spec.env.RWCharactTimeout), 'number',
                     "'spec.env.RWCharactTimeout' is not a number");
        const RWCharactTimeout = spec.env.RWCharactTimeout;

        const subscribers = {};

        const finders = {};

        that.__iot_findServices__ = function(service, handlerMethodName) {
            const findServices = function() {
                that.__iot_stopFindServices__();
                const serviceArray = Array.isArray(service) ?
                    service :
                    [service];
                const serviceKey = Array.isArray(service) ?
                    service.join('#') :
                    service;
                noble.startScanning(serviceArray);
                const f = function(peripheral) {
                    $._.$.log && $._.$.log.debug('found device:' +
                                                 peripheral.uuid +
                                                 ' for service:' + serviceKey);
                    const args = [service, peripheral];
                    $._.$.queue.process(handlerMethodName, args,
                                        {noSync: true});
                };
                finders[serviceKey] = f;
                noble.on('discover', f);
            };

            // allow lazy initialization of noble
            if (noble.state === 'poweredOn') {
                findServices();
            } else {
                noble.removeAllListeners('stateChange');
                noble.once('stateChange', function(state) {
                    if (state === 'poweredOn') {
                        findServices();
                    } else {
                        const err = new Error('BLE not powered on');
                        $._.$.log &&
                            $._.$.log.warn(myUtils.errToPrettyStr(err));
                    }
                });
            }
        };

        that.__iot_stopFindServices__ = function() {
            const resetFinders = function() {
                noble.stopScanning();
                Object.keys(finders).forEach((x) => {
                    noble.removeListener('discover', finders[x]);
                    delete finders[x];
                });
            };
            resetFinders();
        };

        that.__iot_disconnect__ = function(peripheral, delay, cb) {
            if (cb) {
                setTimeout(function() {
                    try {
                        peripheral.disconnect(cb);
                    } catch (err) {
                        cb(err);
                    }
                }, delay || 0);
                return null;
            } else {
                return new Promise((resolve, reject) => {
                    setTimeout(function() {
                        try {
                            peripheral.disconnect((err, data) => {
                                if (err) {
                                    reject(err);
                                } else {
                                    resolve(data || null);
                                }
                            });
                        } catch (err) {
                            reject(err);
                        }
                    }, delay || 0);
                });
            }
        };

        that.__iot_reset__ = function() {
            const result = [];
            // This is a hack, not clear how to enumerate connections otherwise
            const allP = noble._peripherals || {};
            Object.keys(allP).forEach((pId) => {
                const p = allP[pId];
                if (p.state === 'connected') {
                    $._.$.log && $._.$.log.debug('Disconnecting ' + pId);
                    result.push(that.__iot_disconnect__(pId));
                }
            });
            return Promise.all(result);
        };

        const findSomeCharact = function(service, charIds, cb0) {
            let targetCharIds = []; // the default is find them all, then filter

            if ((typeof window !== 'undefined') &&
                window.iOSNativeAPI && window.iOSNativeAPI.sendMessage) {
                /* WebBLE app on Apple iOS.
                 *
                 * It does not support Web Bluetooth `getCharacteristics()`,
                 * breaking noble `discoverCharacteristics()`, so we need to
                 * monkey patch to use enumeration...
                 */

                // Hack, really brittle...
                service._noble._bindings.discoverCharacteristics =
                    utilGatt.monkeyPatchDiscoverCharacteristics.bind(
                        service._noble._bindings
                    );

                targetCharIds = utilGatt.expandCharactIds(charIds);
            }

            service.discoverCharacteristics(
                targetCharIds, function(err, characts) {
                    if (err) {
                        cb0(err);
                    } else {
                        const res = utilGatt.matchCharactIds(charIds, characts);
                        if (res.length !== charIds.length) {
                            const err = new Error('Missing characteristics');
                            err.wanted = charIds;
                            err.found = res;
                            cb0(err);
                        } else {
                            cb0(null, res);
                        }
                    }
                }
            );
        };

        that.__iot_findCharacteristics__ = function(serviceId, peripheral,
                                                    charIds, cb0) {
            const f = function(cbTop) {
                that.__iot_stopFindServices__();
                var service = null;
                async.waterfall([
                    function(cb1) {
                        $._.$.log && $._.$.log.debug('#1# connect');
                        try {
                            peripheral.connect((err) => cb1(err, null));
                        } catch (err) {
                            cb1(err);
                        }
                    },
                    function(_ignore, cb1) {
                        $._.$.log && $._.$.log.debug('#2# discover services');
                        try {
                            peripheral.discoverServices([], (err, data) => {
                                cb1(err, data || []);
                            });
                        } catch (err) {
                            cb1(err);
                        }
                    },
                    function(services, cb1) {
                        $._.$.log && $._.$.log.debug('#3# discover characts');
                        services = Array.isArray(services) ?
                            services :
                            [services];
                        service = utilGatt.matchServiceId(services, serviceId);
                        if (service) {
                            try {
                                if (Array.isArray(charIds)) {
                                    findSomeCharact(service, charIds, cb1);
                                } else {
                                    service.discoverCharacteristics([], cb1);
                                }
                            } catch (err) {
                                cb1(err);
                            }
                        } else {
                            cb1(new Error('no matching service ' + serviceId));
                        }
                    }
                ], function (err, chrs) {
                    $._.$.log && $._.$.log.debug('#4# queue handler');
                    if (err) {
                        $._.$.log &&
                            $._.$.log.warn(myUtils.errToPrettyStr(err));
                        try {
                            peripheral.disconnect();
                        } catch (_ign) {/*ignore*/}
                        cbTop(err);
                    } else {
                        const result = {service: service, device: peripheral,
                                        characteristics: chrs};
                        cbTop(null, result);
                    }
                });
            };

            const fTimeout = myUtils.wrapWithTimeout(f, findCharactTimeout);

            if (cb0) {
                fTimeout(cb0);
                return null;
            } else {
                return new Promise((resolve, reject) => {
                    fTimeout((err, data) => {
                        if (err) {
                            // best effort, do not block...
                            that.__iot_reset__();
                            reject(err);
                        } else {
                            resolve(data);
                        }
                    });
                });
            }
        };

        that.__iot_read__ = function(characteristic, handlerMethodName) {
            characteristic.read(function(err, data) {
                if (err) {
                    $._.$.log && $._.$.log.warn(myUtils.errToPrettyStr(err));
                } else {
                    const args = [characteristic, data];
                    $._.$.queue.process(handlerMethodName, args,
                                        {noSync: true});
                }
            });
        };

        that.__iot_dirtyRead__ = function(characteristic) {
            const bRead = characteristic.read.bind(characteristic);
            const fTimeout = myUtils.wrapWithTimeout(bRead, RWCharactTimeout);
            return new Promise((resolve, reject) => {
                fTimeout((err, data) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(data);
                    }
                });
            });
        };

        that.__iot_write__ = function(characteristic, value, withoutResponse) {
            //needs a Buffer not Uint8Array with node.js
            if ((typeof window === 'undefined') && (!Buffer.isBuffer(value))) {
                value = Buffer.from(value.buffer);
            }

            const bWrite = (cb) => {
                characteristic.write(value, !!withoutResponse, cb);
            };
            const fTimeout = myUtils.wrapWithTimeout(bWrite, RWCharactTimeout);
            return new Promise((resolve, reject) => {
                fTimeout((err, data) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(data || null);
                    }
                });
            });
        };

        that.__iot_subscribe__ = function(characteristic, handlerMethodName) {
            return new Promise((resolve, reject) => {
                if (!subscribers[characteristic.uuid]) {
                    const bSubscribe = (cb) => {
                        characteristic.subscribe(cb);
                    };
                    const fTimeout = myUtils.wrapWithTimeout(bSubscribe,
                                                             RWCharactTimeout);
                    fTimeout((err) => {
                        if (err) {
                            reject(err);
                        } else {
                            const f = function(data) {
                                const args = [characteristic, data];
                                $._.$.queue.process(handlerMethodName, args,
                                                    {noSync: true});
                            };
                            characteristic.on('data', f);
                            subscribers[characteristic.uuid] = f;
                            resolve(null);
                        }
                    });
                } else {
                    $._.$.log && $._.$.log.debug(
                        'Ignoring duplicate subscription in ' +
                            characteristic.uuid
                    );
                    resolve(null);
                }
            });
        };

        that.__iot_unsubscribe__ = function(characteristic) {
            return new Promise((resolve, reject) => {
                if (subscribers[characteristic.uuid]) {
                    const bUnsubscribe = (cb) => {
                        characteristic.unsubscribe(cb);
                    };
                    const fTimeout = myUtils.wrapWithTimeout(bUnsubscribe,
                                                             RWCharactTimeout);
                    fTimeout((err) => {
                        if (err) {
                            reject(err);
                        } else {
                            characteristic.removeListener(
                                'data', subscribers[characteristic.uuid]
                            );
                            delete subscribers[characteristic.uuid];
                            resolve(null);
                        }
                    });
                } else {
                    $._.$.log && $._.$.log.debug('Ignoring unsubscribe ' +
                                                 ' in ' + characteristic.uuid);
                    resolve(null);
                }
            });
        };

        const super__ca_shutdown__ = myUtils.superior(that, '__ca_shutdown__');
        that.__ca_shutdown__ = function(data, cb0) {
            super__ca_shutdown__(data, function(err) {
                if (err) {
                    cb0(err);
                } else {
                    that.__iot_reset__()
                        .then(() => cb0(null))
                        .catch(err => cb0(err));
                }
            });

        };

        noble.removeAllListeners('error');
        noble.on('error', function(err) {
            $._.$.log && $._.$.log.debug('Got error in BLE :' +
                                         myUtils.errToPrettyStr(err));
            that.__ca_shutdown__(null, function(err) {
                if (err) {
                    $._.$.log &&
                        $._.$.log.debug('Got error in BLE shutdown:' +
                                        myUtils.errToPrettyStr(err));
                }
            });
        });

        return [null, that];
    } catch (err) {
        return [err];
    }
};
<commit_msg>Fix reset<commit_after>// Modifications copyright 2020 Caf.js Labs and contributors
/*!
 Copyright 2013 Hewlett-Packard Development Company, L.P.

 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.
 */

'use strict';
/**
 *  Provides access to BLE GATT services.
 *
 *  * Properties:
 *
 *  {findCharactTimeout: number, RWCharactTimeout: number}
 *
 * `findCharactTimeout` is a timeout in msec to detect a hang while finding
 * a BLE device characteristics.
 *
 * `RWCharactTimeout` is a timeout in msec to detect a hang while
 * reading or writing a BLE device characteristic.
 *
 * @module caf_iot_gatt/plug_iot_gatt
 * @augments external:caf_iot/gen_plug_iot
 *
 */
const assert = require('assert');
const caf_iot = require('caf_iot');
const caf_comp = caf_iot.caf_components;
const myUtils = caf_comp.myUtils;
const async = caf_comp.async;
const genPlugIoT = caf_iot.gen_plug_iot;
const noble = require('@abandonware/noble');
const utilGatt = require('./util_gatt');

/**
 * Factory method for a plug to  BLE GATT services.
 *
 */
exports.newInstance = async function($, spec) {
    try {
        const that = genPlugIoT.create($, spec);

        $._.$.log && $._.$.log.debug('New BLE GATT plug');

        assert.equal(typeof(spec.env.findCharactTimeout), 'number',
                     "'spec.env.findCharactTimeout' is not a number");
        const findCharactTimeout = spec.env.findCharactTimeout;

        assert.equal(typeof(spec.env.RWCharactTimeout), 'number',
                     "'spec.env.RWCharactTimeout' is not a number");
        const RWCharactTimeout = spec.env.RWCharactTimeout;

        const subscribers = {};

        const finders = {};

        that.__iot_findServices__ = function(service, handlerMethodName) {
            const findServices = function() {
                that.__iot_stopFindServices__();
                const serviceArray = Array.isArray(service) ?
                    service :
                    [service];
                const serviceKey = Array.isArray(service) ?
                    service.join('#') :
                    service;
                noble.startScanning(serviceArray);
                const f = function(peripheral) {
                    $._.$.log && $._.$.log.debug('found device:' +
                                                 peripheral.uuid +
                                                 ' for service:' + serviceKey);
                    const args = [service, peripheral];
                    $._.$.queue.process(handlerMethodName, args,
                                        {noSync: true});
                };
                finders[serviceKey] = f;
                noble.on('discover', f);
            };

            // allow lazy initialization of noble
            if (noble.state === 'poweredOn') {
                findServices();
            } else {
                noble.removeAllListeners('stateChange');
                noble.once('stateChange', function(state) {
                    if (state === 'poweredOn') {
                        findServices();
                    } else {
                        const err = new Error('BLE not powered on');
                        $._.$.log &&
                            $._.$.log.warn(myUtils.errToPrettyStr(err));
                    }
                });
            }
        };

        that.__iot_stopFindServices__ = function() {
            const resetFinders = function() {
                noble.stopScanning();
                Object.keys(finders).forEach((x) => {
                    noble.removeListener('discover', finders[x]);
                    delete finders[x];
                });
            };
            resetFinders();
        };

        that.__iot_disconnect__ = function(peripheral, delay, cb) {
            if (cb) {
                setTimeout(function() {
                    try {
                        peripheral.disconnect(cb);
                    } catch (err) {
                        cb(err);
                    }
                }, delay || 0);
                return null;
            } else {
                return new Promise((resolve, reject) => {
                    setTimeout(function() {
                        try {
                            peripheral.disconnect((err, data) => {
                                if (err) {
                                    reject(err);
                                } else {
                                    resolve(data || null);
                                }
                            });
                        } catch (err) {
                            reject(err);
                        }
                    }, delay || 0);
                });
            }
        };

        that.__iot_reset__ = function() {
            const result = [];
            // This is a hack, not clear how to enumerate connections otherwise
            const allP = noble._peripherals || {};
            Object.keys(allP).forEach((pId) => {
                const p = allP[pId];
                if (p.state === 'connected') {
                    $._.$.log && $._.$.log.debug('Disconnecting ' + pId);
                    result.push(that.__iot_disconnect__(p));
                }
            });
            return Promise.all(result);
        };

        const findSomeCharact = function(service, charIds, cb0) {
            let targetCharIds = []; // the default is find them all, then filter

            if ((typeof window !== 'undefined') &&
                window.iOSNativeAPI && window.iOSNativeAPI.sendMessage) {
                /* WebBLE app on Apple iOS.
                 *
                 * It does not support Web Bluetooth `getCharacteristics()`,
                 * breaking noble `discoverCharacteristics()`, so we need to
                 * monkey patch to use enumeration...
                 */

                // Hack, really brittle...
                service._noble._bindings.discoverCharacteristics =
                    utilGatt.monkeyPatchDiscoverCharacteristics.bind(
                        service._noble._bindings
                    );

                targetCharIds = utilGatt.expandCharactIds(charIds);
            }

            service.discoverCharacteristics(
                targetCharIds, function(err, characts) {
                    if (err) {
                        cb0(err);
                    } else {
                        const res = utilGatt.matchCharactIds(charIds, characts);
                        if (res.length !== charIds.length) {
                            const err = new Error('Missing characteristics');
                            err.wanted = charIds;
                            err.found = res;
                            cb0(err);
                        } else {
                            cb0(null, res);
                        }
                    }
                }
            );
        };

        that.__iot_findCharacteristics__ = function(serviceId, peripheral,
                                                    charIds, cb0) {
            const f = function(cbTop) {
                that.__iot_stopFindServices__();
                var service = null;
                async.waterfall([
                    function(cb1) {
                        $._.$.log && $._.$.log.debug('#1# connect');
                        try {
                            peripheral.connect((err) => cb1(err, null));
                        } catch (err) {
                            cb1(err);
                        }
                    },
                    function(_ignore, cb1) {
                        $._.$.log && $._.$.log.debug('#2# discover services');
                        try {
                            peripheral.discoverServices([], (err, data) => {
                                cb1(err, data || []);
                            });
                        } catch (err) {
                            cb1(err);
                        }
                    },
                    function(services, cb1) {
                        $._.$.log && $._.$.log.debug('#3# discover characts');
                        services = Array.isArray(services) ?
                            services :
                            [services];
                        service = utilGatt.matchServiceId(services, serviceId);
                        if (service) {
                            try {
                                if (Array.isArray(charIds)) {
                                    findSomeCharact(service, charIds, cb1);
                                } else {
                                    service.discoverCharacteristics([], cb1);
                                }
                            } catch (err) {
                                cb1(err);
                            }
                        } else {
                            cb1(new Error('no matching service ' + serviceId));
                        }
                    }
                ], function (err, chrs) {
                    $._.$.log && $._.$.log.debug('#4# queue handler');
                    if (err) {
                        $._.$.log &&
                            $._.$.log.warn(myUtils.errToPrettyStr(err));
                        try {
                            peripheral.disconnect();
                        } catch (_ign) {/*ignore*/}
                        cbTop(err);
                    } else {
                        const result = {service: service, device: peripheral,
                                        characteristics: chrs};
                        cbTop(null, result);
                    }
                });
            };

            const fTimeout = myUtils.wrapWithTimeout(f, findCharactTimeout);

            if (cb0) {
                fTimeout(cb0);
                return null;
            } else {
                return new Promise((resolve, reject) => {
                    fTimeout((err, data) => {
                        if (err) {
                            // best effort, do not block...
                            that.__iot_reset__();
                            reject(err);
                        } else {
                            resolve(data);
                        }
                    });
                });
            }
        };

        that.__iot_read__ = function(characteristic, handlerMethodName) {
            characteristic.read(function(err, data) {
                if (err) {
                    $._.$.log && $._.$.log.warn(myUtils.errToPrettyStr(err));
                } else {
                    const args = [characteristic, data];
                    $._.$.queue.process(handlerMethodName, args,
                                        {noSync: true});
                }
            });
        };

        that.__iot_dirtyRead__ = function(characteristic) {
            const bRead = characteristic.read.bind(characteristic);
            const fTimeout = myUtils.wrapWithTimeout(bRead, RWCharactTimeout);
            return new Promise((resolve, reject) => {
                fTimeout((err, data) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(data);
                    }
                });
            });
        };

        that.__iot_write__ = function(characteristic, value, withoutResponse) {
            //needs a Buffer not Uint8Array with node.js
            if ((typeof window === 'undefined') && (!Buffer.isBuffer(value))) {
                value = Buffer.from(value.buffer);
            }

            const bWrite = (cb) => {
                characteristic.write(value, !!withoutResponse, cb);
            };
            const fTimeout = myUtils.wrapWithTimeout(bWrite, RWCharactTimeout);
            return new Promise((resolve, reject) => {
                fTimeout((err, data) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(data || null);
                    }
                });
            });
        };

        that.__iot_subscribe__ = function(characteristic, handlerMethodName) {
            return new Promise((resolve, reject) => {
                if (!subscribers[characteristic.uuid]) {
                    const bSubscribe = (cb) => {
                        characteristic.subscribe(cb);
                    };
                    const fTimeout = myUtils.wrapWithTimeout(bSubscribe,
                                                             RWCharactTimeout);
                    fTimeout((err) => {
                        if (err) {
                            reject(err);
                        } else {
                            const f = function(data) {
                                const args = [characteristic, data];
                                $._.$.queue.process(handlerMethodName, args,
                                                    {noSync: true});
                            };
                            characteristic.on('data', f);
                            subscribers[characteristic.uuid] = f;
                            resolve(null);
                        }
                    });
                } else {
                    $._.$.log && $._.$.log.debug(
                        'Ignoring duplicate subscription in ' +
                            characteristic.uuid
                    );
                    resolve(null);
                }
            });
        };

        that.__iot_unsubscribe__ = function(characteristic) {
            return new Promise((resolve, reject) => {
                if (subscribers[characteristic.uuid]) {
                    const bUnsubscribe = (cb) => {
                        characteristic.unsubscribe(cb);
                    };
                    const fTimeout = myUtils.wrapWithTimeout(bUnsubscribe,
                                                             RWCharactTimeout);
                    fTimeout((err) => {
                        if (err) {
                            reject(err);
                        } else {
                            characteristic.removeListener(
                                'data', subscribers[characteristic.uuid]
                            );
                            delete subscribers[characteristic.uuid];
                            resolve(null);
                        }
                    });
                } else {
                    $._.$.log && $._.$.log.debug('Ignoring unsubscribe ' +
                                                 ' in ' + characteristic.uuid);
                    resolve(null);
                }
            });
        };

        const super__ca_shutdown__ = myUtils.superior(that, '__ca_shutdown__');
        that.__ca_shutdown__ = function(data, cb0) {
            super__ca_shutdown__(data, function(err) {
                if (err) {
                    cb0(err);
                } else {
                    that.__iot_reset__()
                        .then(() => cb0(null))
                        .catch(err => cb0(err));
                }
            });

        };

        noble.removeAllListeners('error');
        noble.on('error', function(err) {
            $._.$.log && $._.$.log.debug('Got error in BLE :' +
                                         myUtils.errToPrettyStr(err));
            that.__ca_shutdown__(null, function(err) {
                if (err) {
                    $._.$.log &&
                        $._.$.log.debug('Got error in BLE shutdown:' +
                                        myUtils.errToPrettyStr(err));
                }
            });
        });

        return [null, that];
    } catch (err) {
        return [err];
    }
};
<|endoftext|>