<commit_before>require("underscore");
require("es6-promise").polyfill();
require("@/common/polyfill");
require("jquery");

var utils = require("@/common/utils");
var commonConfig = require("@/common/config");
var uikit = require("../pages/main/uikit");
var api = require("./api");
var Selector = require("../pages/main/uikit/selector");

var isSending = false;

var dom = utils.createElementFromHTML([
	"
",
	"
",
	"
" + __("ticket.note_title") + "
",
	"
" + __("ticket.title") + "
",
	"<input type=\"text\" class=\"name\" placeholder=\"" + __("ticket.name") + "\"> *",
	"<input type=\"text\" class=\"phone\" placeholder=\"" + __("ticket.phone_number") + "\"> *",
	"<input type=\"text\" class=\"mail\" placeholder=\"" + __("ticket.email") + "\">",
	"

",
	"<textarea spellcheck=\"false\" placeholder=\"" + __("ticket.content_placeholder") + "\"></textarea>*",
	"

",
	"
" + __("common.cancel") + "
",
	"
"+ __("common.ticket") +"
",
	"
",
	"
"
].join(""));

if(utils.isMobile  || ($("body",parent.document).hasClass("window-demo") && $("#em-kefu-webim-self",parent.document).hasClass("hide"))){
	var dom = utils.createElementFromHTML([
		"
",
		"
",
		"
" + __("ticket.note_title") + "
",
		"
" + __("ticket.title") + "
",
		"<input type=\"text\" class=\"name\" placeholder=\"" + __("ticket.name") + "\"> *",
		"<input type=\"text\" class=\"phone\" placeholder=\"" + __("ticket.phone_number") + "\"> *",
		"<input type=\"text\" class=\"mail\" placeholder=\"" + __("ticket.email") + "\">",
		"

",
		"<textarea spellcheck=\"false\" placeholder=\"" + __("ticket.content_placeholder") + "\"></textarea>*",
		"

",
		"
" + __("common.cancel") + "
",
		"
"+ __("common.ticket") +"
",
		"
",
		"
"
	].join(""));
}
else if($("body",parent.document).hasClass("window-demo") && !$("#em-kefu-webim-self",parent.document).hasClass("hide")){
	var dom = utils.createElementFromHTML([
		"
",
		"
",
		"
" + __("ticket.note_title") + "
",
		"
" + __("ticket.title") + "
",
		"<input type=\"text\" class=\"name\" placeholder=\"" + __("ticket.name") + "\"> *",
		"<input type=\"text\" class=\"phone\" placeholder=\"" + __("ticket.phone_number") + "\"> *",
		"<input type=\"text\" class=\"mail\" placeholder=\"" + __("ticket.email") + "\">",
		"

",
		"<textarea spellcheck=\"false\" placeholder=\"" + __("ticket.content_placeholder") + "\"></textarea>*",
		"

",
		"
" + __("common.cancel") + "
",
		"
"+ __("common.ticket") +"
",
		"
",
		"
"
	].join(""));
}
document.body.appendChild(dom);

var content = document.querySelector("textarea");
var name = document.querySelector(".name");
var phone = document.querySelector(".phone");
var mail = document.querySelector(".mail");
var noteCategory = document.querySelector(".note-category");
var noteCategoryList = new Selector({
	list: [],
	container: noteCategory,
});
// var cancelBtn = document.querySelector(".cancel-btn");
// var confirmBtn = document.querySelector(".confirm-btn");
var cancelBtn = document.querySelector(".cancel");
var confirmBtn = document.querySelector(".confirm");
// url 传递的参数
var config = getNoteConfig().config || {};
console.log(config,"offDutyType++" )
api.update(config);
utils.addClass(document.body, config.themeClassName || "theme-1");
// 根据配置隐藏取消按钮
config.hideCloseBtn && utils.addClass(cancelBtn, "hide");

var themeCustomColor = config.themeCustomColor;
if(themeCustomColor){
	// color = config.themeColor;
	$(".theme_custom").find(".bg-color").css("cssText","background-color: " + themeCustomColor + " !important");
}
var color = config.themeColor;
if(color){
	$(".theme_custom").find(".bg-color").css("cssText","background-color: " + color + " !important");
}
if(config.offDutyType == ""){
	var closeEl = $(".ticket>.wrapper .icon-close");
	var ticketEl = $(".ticket>.wrapper .confirm");
	closeEl&&closeEl.remove();
	$(ticketEl).css("cssText","margin-left: 280px;");
}

var _getCategories = _.once(function(){
	api.getNoteCategories().then(function(list){
		var optionList = _.map(list, function(item){
			return {
				sign: item.id,
				desc: item.name
			};
		});

		if(!_.isEmpty(optionList)){
			utils.removeClass(noteCategory, "hide");
			noteCategoryList.updateList({ list: optionList });
		}
	});
});

function getNoteConfig(){
	var i;
	var len;
	var tmp = [];
	var obj = {};
	// 需要测兼容性
	var src = document.location.search;
	var arr = src.slice(1).split("&");
	for(i = 0, len = arr.length; i < len; i++){
		tmp = arr[i].split("=");
		tmp[1] = window.atob
			? window.atob(tmp[1])
			: tmp[1];
		obj[tmp[0]] = tmp.length > 1
			? JSON.parse(decodeURIComponent(tmp[1]))
			: "";
	}
	return obj;
}

function _createTicket(){
	Promise.all([
		api.getToken(),
		api.getProjectId()
	]).then(function(result){
		var token = result[0];
		var projectId = result[1];
		var sessionId = config.sessionId || "";

		api.createTicket({
			token: token,
			projectId: projectId,
			name: name.value,
			phone: phone.value,
			mail: mail.value,
			content: content.value,
			category_id: noteCategoryList.getSelectedValue(),
			session_id: sessionId,
		}).then(function(){
			isSending = false;
			uikit.showSuccess(__("ticket.send_success"));

			_clearInput();
		}, function(err){
			isSending = false;
			uikit.tip(__("ticket.send_failed_retry"));
			console.error(err);
		});
	})
	["catch"](function(err){
		uikit.tip(__("ticket.send_failed_invalid_token"));
		console.error(err);
	});
}

function _clearInput(){
	name.value = "";
	phone.value = "";
	mail.value = "";
	content.value = "";
}

function _writePreDate(preData){
	name.value = preData.name || "";
	phone.value = preData.phone || "";
	mail.value = preData.mail || "";
}

config.grayNoteCategory && _getCategories();
config.preData && _writePreDate(config.preData);

// 海外用户，不验证手机号格式 新增敦煌用户不校验格式2020-05-06
function overseasTest(){
	if(config.tenantId == "76141"){
		return !phone.value;
	}
	if(config.tenantId == "78882"){
		return !phone.value;
	}
	if(config.tenantId == "6437"){
        return !phone.value;
    }
	return !phone.value || !(/^1[3456789]\d{9}$/.test(phone.value));
}
// 添加邮箱的校验
function checkEmail(){
	var check = /^[0-9a-z]([_.0-9a-z-]{0,30}[0-9a-z])?@([0-9a-z][0-9a-z-]{0,30}[.]){1,3}[a-z]{2,4}$/i;
	if(mail.value){
		if(check.test(mail.value)){
			return true
		}else{
			return false
		}
	}
	else{
		return true
	}
}

// 留言
utils.on(confirmBtn, "click", function(){
	if(isSending){
		uikit.tip(__("ticket.is_sending"));
	}
	else if(!name.value || name.value.length > 140){
		uikit.tip(__("ticket.invalid_name"));
	}
	else if(overseasTest()){
		uikit.tip(__("ticket.invalid_phone"));
	}
	else if(!checkEmail()){
		uikit.tip(__("ticket.invalid_email"));
	}
	else if(!content.value || content.value.length > 1500){
		uikit.tip(__("ticket.invalid_content"));
	}
	else{
		isSending = true;
		setTimeout(function(){ isSending = false; }, 10000);
		_createTicket();
	}
});

// 取消
utils.on(cancelBtn, "click", function(){
	if(config.offDutyType == ""){

	}
	else{
		window.parent.postMessage({ closeNote: true }, "*");
	}
});
utils.live(".wrapper-title .icon-close","click",function(){
	// window.parent.postMessage({ closeNote: true }, "*");
	if(config.offDutyType == ""){

	}
	else{
		window.parent.postMessage({ closeNote: true }, "*");
	}
});
utils.live(".wrapper-title .icon-back-new","click",function(){
	// window.parent.postMessage({ closeNote: true }, "*");
	if(config.offDutyType == ""){

	}
	else{
		window.parent.postMessage({ closeNote: true }, "*");
	}
});
<commit_msg>CLOUD-26300【dev_webim】网页集成端，下班时间无接待访客进入留言页面时，按钮出现异常<commit_after>require("underscore");
require("es6-promise").polyfill();
require("@/common/polyfill");
require("jquery");

var utils = require("@/common/utils");
var commonConfig = require("@/common/config");
var uikit = require("../pages/main/uikit");
var api = require("./api");
var Selector = require("../pages/main/uikit/selector");
var getToHost = require("@/app/common/transfer");
var _const = require("@/common/const");

var isSending = false;

var dom = utils.createElementFromHTML([
	"
",
	"
",
	"
" + __("ticket.note_title") + "
",
	"
" + __("ticket.title") + "
",
	"<input type=\"text\" class=\"name\" placeholder=\"" + __("ticket.name") + "\"> *",
	"<input type=\"text\" class=\"phone\" placeholder=\"" + __("ticket.phone_number") + "\"> *",
	"<input type=\"text\" class=\"mail\" placeholder=\"" + __("ticket.email") + "\">",
	"

",
	"<textarea spellcheck=\"false\" placeholder=\"" + __("ticket.content_placeholder") + "\"></textarea>*",
	"

",
	"
" + __("common.cancel") + "
",
	"
"+ __("common.ticket") +"
",
	"
",
	"
"
].join(""));

if(utils.isMobile  || ($("body",parent.document).hasClass("window-demo") && $("#em-kefu-webim-self",parent.document).hasClass("hide"))){
	var dom = utils.createElementFromHTML([
		"
",
		"
",
		"
" + __("ticket.note_title") + "
",
		"
" + __("ticket.title") + "
",
		"<input type=\"text\" class=\"name\" placeholder=\"" + __("ticket.name") + "\"> *",
		"<input type=\"text\" class=\"phone\" placeholder=\"" + __("ticket.phone_number") + "\"> *",
		"<input type=\"text\" class=\"mail\" placeholder=\"" + __("ticket.email") + "\">",
		"

",
		"<textarea spellcheck=\"false\" placeholder=\"" + __("ticket.content_placeholder") + "\"></textarea>*",
		"

",
		"
" + __("common.cancel") + "
",
		"
"+ __("common.ticket") +"
",
		"
",
		"
"
	].join(""));
}
else if($("body",parent.document).hasClass("window-demo") && !$("#em-kefu-webim-self",parent.document).hasClass("hide")){
	var dom = utils.createElementFromHTML([
		"
",
		"
",
		"
" + __("ticket.note_title") + "
",
		"
" + __("ticket.title") + "
",
		"<input type=\"text\" class=\"name\" placeholder=\"" + __("ticket.name") + "\"> *",
		"<input type=\"text\" class=\"phone\" placeholder=\"" + __("ticket.phone_number") + "\"> *",
		"<input type=\"text\" class=\"mail\" placeholder=\"" + __("ticket.email") + "\">",
		"

",
		"<textarea spellcheck=\"false\" placeholder=\"" + __("ticket.content_placeholder") + "\"></textarea>*",
		"

",
		"
" + __("common.cancel") + "
",
		"
"+ __("common.ticket") +"
",
		"
",
		"
"
	].join(""));
}
document.body.appendChild(dom);
console.log(config, 'offDutyType+++')
if( $("body",parent.document).hasClass("window-demo") && config.offDutyType == ""){
	console.log("下班时间隐藏留言" )
	dom.addClass(hide);
	getToHost.listen(function(msg){
		var event = msg.event;

		switch(event){
		case _const.EVENTS.SHOW:
			console.log("收到点击消息，展开留言" )
			dom.removeClass(hide);
		case _const.EVENTS.CLOSE:

			break;
		default:
			break;
		}
	}, ["down2Im"]);
}

var content = document.querySelector("textarea");
var name = document.querySelector(".name");
var phone = document.querySelector(".phone");
var mail = document.querySelector(".mail");
var noteCategory = document.querySelector(".note-category");
var noteCategoryList = new Selector({
	list: [],
	container: noteCategory,
});
// var cancelBtn = document.querySelector(".cancel-btn");
// var confirmBtn = document.querySelector(".confirm-btn");
var cancelBtn = document.querySelector(".cancel");
var confirmBtn = document.querySelector(".confirm");
// url 传递的参数
var config = getNoteConfig().config || {};
console.log(config,"offDutyType++" )
api.update(config);
utils.addClass(document.body, config.themeClassName || "theme-1");
// 根据配置隐藏取消按钮
config.hideCloseBtn && utils.addClass(cancelBtn, "hide");

var themeCustomColor = config.themeCustomColor;
if(themeCustomColor){
	// color = config.themeColor;
	$(".theme_custom").find(".bg-color").css("cssText","background-color: " + themeCustomColor + " !important");
}
var color = config.themeColor;
if(color){
	$(".theme_custom").find(".bg-color").css("cssText","background-color: " + color + " !important");
}
if(config.offDutyType == ""){
	var closeEl = $(".ticket>.wrapper .icon-close");
	var ticketEl = $(".ticket>.wrapper .confirm");
	closeEl&&closeEl.remove();
	$(ticketEl).css("cssText","margin-left: 280px;");
}

var _getCategories = _.once(function(){
	api.getNoteCategories().then(function(list){
		var optionList = _.map(list, function(item){
			return {
				sign: item.id,
				desc: item.name
			};
		});

		if(!_.isEmpty(optionList)){
			utils.removeClass(noteCategory, "hide");
			noteCategoryList.updateList({ list: optionList });
		}
	});
});

function getNoteConfig(){
	var i;
	var len;
	var tmp = [];
	var obj = {};
	// 需要测兼容性
	var src = document.location.search;
	var arr = src.slice(1).split("&");
	for(i = 0, len = arr.length; i < len; i++){
		tmp = arr[i].split("=");
		tmp[1] = window.atob
			? window.atob(tmp[1])
			: tmp[1];
		obj[tmp[0]] = tmp.length > 1
			? JSON.parse(decodeURIComponent(tmp[1]))
			: "";
	}
	return obj;
}

function _createTicket(){
	Promise.all([
		api.getToken(),
		api.getProjectId()
	]).then(function(result){
		var token = result[0];
		var projectId = result[1];
		var sessionId = config.sessionId || "";

		api.createTicket({
			token: token,
			projectId: projectId,
			name: name.value,
			phone: phone.value,
			mail: mail.value,
			content: content.value,
			category_id: noteCategoryList.getSelectedValue(),
			session_id: sessionId,
		}).then(function(){
			isSending = false;
			uikit.showSuccess(__("ticket.send_success"));

			_clearInput();
		}, function(err){
			isSending = false;
			uikit.tip(__("ticket.send_failed_retry"));
			console.error(err);
		});
	})
	["catch"](function(err){
		uikit.tip(__("ticket.send_failed_invalid_token"));
		console.error(err);
	});
}

function _clearInput(){
	name.value = "";
	phone.value = "";
	mail.value = "";
	content.value = "";
}

function _writePreDate(preData){
	name.value = preData.name || "";
	phone.value = preData.phone || "";
	mail.value = preData.mail || "";
}

config.grayNoteCategory && _getCategories();
config.preData && _writePreDate(config.preData);

// 海外用户，不验证手机号格式 新增敦煌用户不校验格式2020-05-06
function overseasTest(){
	if(config.tenantId == "76141"){
		return !phone.value;
	}
	if(config.tenantId == "78882"){
		return !phone.value;
	}
	if(config.tenantId == "6437"){
        return !phone.value;
    }
	return !phone.value || !(/^1[3456789]\d{9}$/.test(phone.value));
}
// 添加邮箱的校验
function checkEmail(){
	var check = /^[0-9a-z]([_.0-9a-z-]{0,30}[0-9a-z])?@([0-9a-z][0-9a-z-]{0,30}[.]){1,3}[a-z]{2,4}$/i;
	if(mail.value){
		if(check.test(mail.value)){
			return true
		}else{
			return false
		}
	}
	else{
		return true
	}
}

// 留言
utils.on(confirmBtn, "click", function(){
	if(isSending){
		uikit.tip(__("ticket.is_sending"));
	}
	else if(!name.value || name.value.length > 140){
		uikit.tip(__("ticket.invalid_name"));
	}
	else if(overseasTest()){
		uikit.tip(__("ticket.invalid_phone"));
	}
	else if(!checkEmail()){
		uikit.tip(__("ticket.invalid_email"));
	}
	else if(!content.value || content.value.length > 1500){
		uikit.tip(__("ticket.invalid_content"));
	}
	else{
		isSending = true;
		setTimeout(function(){ isSending = false; }, 10000);
		_createTicket();
	}
});

// 取消
utils.on(cancelBtn, "click", function(){
	if(config.offDutyType == ""){

	}
	else{
		window.parent.postMessage({ closeNote: true }, "*");
	}
});
utils.live(".wrapper-title .icon-close","click",function(){
	// window.parent.postMessage({ closeNote: true }, "*");
	if(config.offDutyType == ""){

	}
	else{
		window.parent.postMessage({ closeNote: true }, "*");
	}
});
utils.live(".wrapper-title .icon-back-new","click",function(){
	// window.parent.postMessage({ closeNote: true }, "*");
	if(config.offDutyType == ""){

	}
	else{
		window.parent.postMessage({ closeNote: true }, "*");
	}
});
<|endoftext|>


<commit_before>var models = require('../../models');
var util = require('./util');
var passport = require('passport');
var log = require('../../logging');
var tagsUtil = require('./tagsUtil');
var requestUtil = require('./requestUtil');
var responseUtil = require('./responseUtil');
var multiparty = require('multiparty');
var config = require('../../config/config')
var AWS = require('aws-sdk');
var uuidv4 = require('uuid/v4');
var jsonwebtoken = require('jsonwebtoken');
var sequelize = require('sequelize');

module.exports = (app, baseUrl) => {
  var apiUrl = baseUrl + '/recordings';

  /**
   * @api {post} /api/v1/recordings Add a new recording.
   * @apiName PostRecording
   * @apiGroup Recordings
   * @apiDescription This call is used for uploads of new recordings. It
   * currently supports raw thermal video but will eventually support all
   * recording types. It takes a 'data' field which contains a JSON object
   * string that can contain any of the following fields, Note that 'type'
   * is required:
   * - type: REQUIRED, one of ('thermalRaw')
   * - duration
   * - recordingDateTime
   * - location
   * - version
   * - batteryCharging
   * - batteryLevel
   * - airplaneModeOn
   * - additionalMetadata
   * - processingMeta
   *
   * @apiUse V1DeviceAuthorizationHeader
   *
   * @apiParam {JSON} data Metadata about the recording (see above).
   * @apiParam {File} file File of the recording.
   *
   * @apiUse V1ResponseSuccess
   * @apiuse V1ResponseError
   */
  app.post(
    apiUrl,
    passport.authenticate(['jwt'], { session: false }),
    async (request, response) => {
      log.info(request.method + " Request: " + request.url);

      // Check that the request was authenticated by a device.
      if (!requestUtil.isFromADevice(request))
        return responseUtil.notFromADevice(response);
      var device = request.user;

      var recording;
      var key = uuidv4();
      var form = new multiparty.Form();
      var validData = false;
      var uploadProcess = null;
      var uploadStarted = false;
      var invalidMessages = [];

      // Make new Recording from the data field and Device.
      // TODO Stop stream if 'data' is invalid.
      form.on('field', (name, value) => {
        if (name != 'data') return; // Only parse data field.
        try {
          var data = JSON.parse(value);
          recording = models.Recording.build(data, {
            fields: models.Recording.apiSettableFields,
          });
          recording.set('rawFileKey', key);
          recording.set('DeviceId', device.id);
          recording.set('GroupId', device.GroupId);
          recording.set('processingState',
            models.Recording.processingStates[data.type][0]);
          if (typeof device.public === 'boolean')
            recording.set('public', device.public);
          validData = true;
        } catch (e) {
          log.debug(e);
          // TODO Stop stream here.
        }
      });

      // Stream file to LeoFS.
      form.on('part', (part) => {
        if (part.name != 'file') return part.resume();
        uploadStarted = true;
        log.debug('Streaming file to LeoFS.')
        uploadPromise = new Promise(function(resolve, reject) {
          var s3 = new AWS.S3({
            endpoint: config.leoFS.endpoint,
            accessKeyId: config.leoFS.publicKey,
            secretAccessKey: config.leoFS.privateKey,
          });
          s3.upload({
            Bucket: config.leoFS.bucket,
            Key: key,
            Body: part,
          }, (err, data) => {
            if (err) return reject(err);
            log.info("Finished streaming file to LeoFS Key:", key);
            resolve();
          });
        });
      });

      // Close response.
      form.on('close', async () => {
        log.info("Finished POST request.");
        if (!validData || !uploadStarted)
          return responseUtil.invalidDatapointUpload(response);

        // Check that th file uploaded to LeoFS.
        try {
          await uploadPromise;
        } catch (e) {
          return responseUtil.send(response, {
            statusCode: 500,
            success: false,
            messages: ["Failed to upload file to LeoFS"],
          });
        }
        // Validate and upload recording
        await recording.validate();
        await recording.save();
        return responseUtil.validDatapointUpload(response);
      });

      form.on('error', (e) => {
        log.error(e);
      })

      form.parse(request);
    });

  /**
   * @api {get} /api/v1/recordings Query available recordings
   * @apiName GetRecordigns
   * @apiGroup Recordings
   *
   * @apiUse V1UserAuthorizationHeader
   *
   * @apiParam {JSON} where [Sequelize where conditions](http://docs.sequelizejs.com/manual/tutorial/querying.html#where) for query
   * @apiParam {Number} offset Query result offset (for paging).
   * @apiParam {Number} limit Query result limit (for paging).
   *
   * @apiUse V1ResponseSuccess
   * @apiSuccess {Number} offset Mirrors request offset parameter.
   * @apiSuccess {Number} limit Mirrors request limit parameter.
   * @apiSuccess {Number} count Total number of records which match the query.
   * @apiSuccess {JSON} rows List of details for records which matched the query.
   *
   * @apiUse V1ResponseError
   */
  app.get(
    apiUrl,
    passport.authenticate(['jwt'], { session: false }),
    async (request, response) => {
      log.info(request.method + " Request: " + request.url);

      // Check that the request was authenticated by a User.
      if (!requestUtil.isFromAUser(request))
        return responseUtil.notFromAUser(response);

      var where = request.query.where;
      var offset = parseInt(request.query.offset);
      var limit = parseInt(request.query.limit);

      // Validate 'where', 'offset', and 'limit'
      var errorMessages = [];
      try {
        where = JSON.parse(where);
      } catch (e) {
        errorMessages.push("'where' field was not a valid JSON.")
      }
      if (isNaN(offset)) errorMessages.push("'offset' was not a number.")
      if (isNaN(limit)) errorMessages.push("'limit' was not a number.")
      if (errorMessages.length > 0) {
        return responseUtil.send(response, {
          statusCode: 400,
          success: false,
          messages: errorMessages,
        });
      }

      // Make sequelize query.
      var userGroupIds = await request.user.getGroupsIds();
      var query = {
        where: {
          "$and": [
            where, // User query
            { "$or": [{ public: true }, { GroupId: { "$in": userGroupIds } }] }
          ],
        },
        order: [
            // Sort by recordingDatetime but handle the case of the
            // timestamp being missing and fallback to sorting by id.
            [sequelize.fn("COALESCE", sequelize.col('recordingDateTime'), '1970-01-01'), "DESC"],
            ["id", "DESC"],
        ],
        include: [
          { model: models.Group },
          { model: models.Tag },
          { model: models.Device, where: {}, attributes: ["devicename", "id"] },
        ],
        limit: limit,
        offset: offset,
        attributes: models.Recording.userGetAttributes,
      };
      // Query database.
      var result = await models.Recording.findAndCount(query);

      // Send response
      return responseUtil.send(response, {
        statusCode: 200,
        success: true,
        messages: ["Completed query."],
        limit: limit,
        offset: offset,
        count: result.count,
        rows: result.rows,
      });
    });

    /**
    * @api {get} /api/v1/recordings/:id Get a recording
    * @apiName GetRecording
    * @apiGroup Recordings
    * @apiDescription This call returns metadata for a recording in JSON format
    * and a JSON Web Token (JWT) which can be used to retrieve the recorded
    * content. This is should be used with the
    * [/api/v1/signedUrl API](#api-SignedUrl-GetFile).
    *
    * @apiUse V1UserAuthorizationHeader
    *
    * @apiUse V1ResponseSuccess
    * @apiSuccess {String} downloadFileJWT JSON Web Token to use to download the
    * recording file.
    * @apiSuccess {String} downloadRawJWT JSON Web Token to use to download
    * the raw recording data.
    * @apiSuccess {JSON} recording The recording data.
    *
    * @apiUse V1ResponseError
    */
    app.get(
      apiUrl + '/:id',
      passport.authenticate(['jwt'], { session: false }),
      async (request, response) => {
        log.info(request.method + " Request: " + request.url);

        // Check that the request was authenticated by a User.
        if (!requestUtil.isFromAUser(request))
          return responseUtil.notFromAUser(response);

        var id = parseInt(request.params.id);
        if (!id)
          return responseUtil.invalidDataId(response);

        // Make sequelize query.
        var userGroupIds = await request.user.getGroupsIds();
        var attributes = models.Recording.userGetAttributes;
        attributes.push("rawFileKey");
        var query = {
          where: {
            "$and": [
              { id: id },
              { "$or": [{ public: true }, { GroupId: { "$in": userGroupIds } }] }
            ],
          },
          include: [
            { model: models.Tag, },
            { model: models.Device, where: {}, attributes: ["devicename", "id"] },
          ],
          attributes: attributes,
        };

        // Query database.
        var recording = await models.Recording.findOne(query);

        // Check the the processing is finished.
        if (recording.get('processingState') !== 'FINISHED')
          return responseUtil.send(response, {
            status_code: 204,
            success: false,
            messages: ["File has not been fully processed yet."],
          });

        downloadFileData = {
          _type: 'fileDownload',
          key: recording.fileKey,
          filename: recording.getFileName(),
          mimeType: recording.fileMimeType,
        }

        downloadRawData = null;
        if (recording.canGetRaw()) {
          downloadRawData = {
            _type: 'fileDownload',
            key: recording.rawFileKey,
            filename: "rawData",
            mimeType: null,
          };
        }
        delete recording.rawFileKey;

        return responseUtil.send(response, {
          statusCode: 200,
          success: true,
          messages: [],
          recording: recording,
          downloadFileJWT: jsonwebtoken.sign(
            downloadFileData,
            config.server.passportSecret,
            { expiresIn: 60 * 10 }
          ),
          downloadRawJWT: jsonwebtoken.sign(
            downloadRawData,
            config.server.passportSecret,
            { expiresIn: 60 * 10 }
          ),
        });
      }
    );
};
<commit_msg>Will now still return the recording if it is being processed.<commit_after>var models = require('../../models');
var util = require('./util');
var passport = require('passport');
var log = require('../../logging');
var tagsUtil = require('./tagsUtil');
var requestUtil = require('./requestUtil');
var responseUtil = require('./responseUtil');
var multiparty = require('multiparty');
var config = require('../../config/config')
var AWS = require('aws-sdk');
var uuidv4 = require('uuid/v4');
var jsonwebtoken = require('jsonwebtoken');
var sequelize = require('sequelize');

module.exports = (app, baseUrl) => {
  var apiUrl = baseUrl + '/recordings';

  /**
   * @api {post} /api/v1/recordings Add a new recording.
   * @apiName PostRecording
   * @apiGroup Recordings
   * @apiDescription This call is used for uploads of new recordings. It
   * currently supports raw thermal video but will eventually support all
   * recording types. It takes a 'data' field which contains a JSON object
   * string that can contain any of the following fields, Note that 'type'
   * is required:
   * - type: REQUIRED, one of ('thermalRaw')
   * - duration
   * - recordingDateTime
   * - location
   * - version
   * - batteryCharging
   * - batteryLevel
   * - airplaneModeOn
   * - additionalMetadata
   * - processingMeta
   *
   * @apiUse V1DeviceAuthorizationHeader
   *
   * @apiParam {JSON} data Metadata about the recording (see above).
   * @apiParam {File} file File of the recording.
   *
   * @apiUse V1ResponseSuccess
   * @apiuse V1ResponseError
   */
  app.post(
    apiUrl,
    passport.authenticate(['jwt'], { session: false }),
    async (request, response) => {
      log.info(request.method + " Request: " + request.url);

      // Check that the request was authenticated by a device.
      if (!requestUtil.isFromADevice(request))
        return responseUtil.notFromADevice(response);
      var device = request.user;

      var recording;
      var key = uuidv4();
      var form = new multiparty.Form();
      var validData = false;
      var uploadProcess = null;
      var uploadStarted = false;
      var invalidMessages = [];

      // Make new Recording from the data field and Device.
      // TODO Stop stream if 'data' is invalid.
      form.on('field', (name, value) => {
        if (name != 'data') return; // Only parse data field.
        try {
          var data = JSON.parse(value);
          recording = models.Recording.build(data, {
            fields: models.Recording.apiSettableFields,
          });
          recording.set('rawFileKey', key);
          recording.set('DeviceId', device.id);
          recording.set('GroupId', device.GroupId);
          recording.set('processingState',
            models.Recording.processingStates[data.type][0]);
          if (typeof device.public === 'boolean')
            recording.set('public', device.public);
          validData = true;
        } catch (e) {
          log.debug(e);
          // TODO Stop stream here.
        }
      });

      // Stream file to LeoFS.
      form.on('part', (part) => {
        if (part.name != 'file') return part.resume();
        uploadStarted = true;
        log.debug('Streaming file to LeoFS.')
        uploadPromise = new Promise(function(resolve, reject) {
          var s3 = new AWS.S3({
            endpoint: config.leoFS.endpoint,
            accessKeyId: config.leoFS.publicKey,
            secretAccessKey: config.leoFS.privateKey,
          });
          s3.upload({
            Bucket: config.leoFS.bucket,
            Key: key,
            Body: part,
          }, (err, data) => {
            if (err) return reject(err);
            log.info("Finished streaming file to LeoFS Key:", key);
            resolve();
          });
        });
      });

      // Close response.
      form.on('close', async () => {
        log.info("Finished POST request.");
        if (!validData || !uploadStarted)
          return responseUtil.invalidDatapointUpload(response);

        // Check that th file uploaded to LeoFS.
        try {
          await uploadPromise;
        } catch (e) {
          return responseUtil.send(response, {
            statusCode: 500,
            success: false,
            messages: ["Failed to upload file to LeoFS"],
          });
        }
        // Validate and upload recording
        await recording.validate();
        await recording.save();
        return responseUtil.validDatapointUpload(response);
      });

      form.on('error', (e) => {
        log.error(e);
      })

      form.parse(request);
    });

  /**
   * @api {get} /api/v1/recordings Query available recordings
   * @apiName GetRecordigns
   * @apiGroup Recordings
   *
   * @apiUse V1UserAuthorizationHeader
   *
   * @apiParam {JSON} where [Sequelize where conditions](http://docs.sequelizejs.com/manual/tutorial/querying.html#where) for query
   * @apiParam {Number} offset Query result offset (for paging).
   * @apiParam {Number} limit Query result limit (for paging).
   *
   * @apiUse V1ResponseSuccess
   * @apiSuccess {Number} offset Mirrors request offset parameter.
   * @apiSuccess {Number} limit Mirrors request limit parameter.
   * @apiSuccess {Number} count Total number of records which match the query.
   * @apiSuccess {JSON} rows List of details for records which matched the query.
   *
   * @apiUse V1ResponseError
   */
  app.get(
    apiUrl,
    passport.authenticate(['jwt'], { session: false }),
    async (request, response) => {
      log.info(request.method + " Request: " + request.url);

      // Check that the request was authenticated by a User.
      if (!requestUtil.isFromAUser(request))
        return responseUtil.notFromAUser(response);

      var where = request.query.where;
      var offset = parseInt(request.query.offset);
      var limit = parseInt(request.query.limit);

      // Validate 'where', 'offset', and 'limit'
      var errorMessages = [];
      try {
        where = JSON.parse(where);
      } catch (e) {
        errorMessages.push("'where' field was not a valid JSON.")
      }
      if (isNaN(offset)) errorMessages.push("'offset' was not a number.")
      if (isNaN(limit)) errorMessages.push("'limit' was not a number.")
      if (errorMessages.length > 0) {
        return responseUtil.send(response, {
          statusCode: 400,
          success: false,
          messages: errorMessages,
        });
      }

      // Make sequelize query.
      var userGroupIds = await request.user.getGroupsIds();
      var query = {
        where: {
          "$and": [
            where, // User query
            { "$or": [{ public: true }, { GroupId: { "$in": userGroupIds } }] }
          ],
        },
        order: [
            // Sort by recordingDatetime but handle the case of the
            // timestamp being missing and fallback to sorting by id.
            [sequelize.fn("COALESCE", sequelize.col('recordingDateTime'), '1970-01-01'), "DESC"],
            ["id", "DESC"],
        ],
        include: [
          { model: models.Group },
          { model: models.Tag },
          { model: models.Device, where: {}, attributes: ["devicename", "id"] },
        ],
        limit: limit,
        offset: offset,
        attributes: models.Recording.userGetAttributes,
      };
      // Query database.
      var result = await models.Recording.findAndCount(query);

      // Send response
      return responseUtil.send(response, {
        statusCode: 200,
        success: true,
        messages: ["Completed query."],
        limit: limit,
        offset: offset,
        count: result.count,
        rows: result.rows,
      });
    });

    /**
    * @api {get} /api/v1/recordings/:id Get a recording
    * @apiName GetRecording
    * @apiGroup Recordings
    * @apiDescription This call returns metadata for a recording in JSON format
    * and a JSON Web Token (JWT) which can be used to retrieve the recorded
    * content. This is should be used with the
    * [/api/v1/signedUrl API](#api-SignedUrl-GetFile).
    *
    * @apiUse V1UserAuthorizationHeader
    *
    * @apiUse V1ResponseSuccess
    * @apiSuccess {String} downloadFileJWT JSON Web Token to use to download the
    * recording file.
    * @apiSuccess {String} downloadRawJWT JSON Web Token to use to download
    * the raw recording data.
    * @apiSuccess {JSON} recording The recording data.
    *
    * @apiUse V1ResponseError
    */
    app.get(
      apiUrl + '/:id',
      passport.authenticate(['jwt'], { session: false }),
      async (request, response) => {
        log.info(request.method + " Request: " + request.url);

        // Check that the request was authenticated by a User.
        if (!requestUtil.isFromAUser(request))
          return responseUtil.notFromAUser(response);

        var id = parseInt(request.params.id);
        if (!id)
          return responseUtil.invalidDataId(response);

        // Make sequelize query.
        var userGroupIds = await request.user.getGroupsIds();
        var attributes = models.Recording.userGetAttributes;
        attributes.push("rawFileKey");
        var query = {
          where: {
            "$and": [
              { id: id },
              { "$or": [{ public: true }, { GroupId: { "$in": userGroupIds } }] }
            ],
          },
          include: [
            { model: models.Tag, },
            { model: models.Device, where: {}, attributes: ["devicename", "id"] },
          ],
          attributes: attributes,
        };

        // Query database.
        var recording = await models.Recording.findOne(query);

        downloadFileData = {
          _type: 'fileDownload',
          key: recording.fileKey,
          filename: recording.getFileName(),
          mimeType: recording.fileMimeType,
        }

        downloadRawData = null;
        if (recording.canGetRaw()) {
          downloadRawData = {
            _type: 'fileDownload',
            key: recording.rawFileKey,
            filename: "rawData",
            mimeType: null,
          };
        }
        delete recording.rawFileKey;

        return responseUtil.send(response, {
          statusCode: 200,
          success: true,
          messages: [],
          recording: recording,
          downloadFileJWT: jsonwebtoken.sign(
            downloadFileData,
            config.server.passportSecret,
            { expiresIn: 60 * 10 }
          ),
          downloadRawJWT: jsonwebtoken.sign(
            downloadRawData,
            config.server.passportSecret,
            { expiresIn: 60 * 10 }
          ),
        });
      }
    );
};
<|endoftext|>


<commit_before>/**
 *
 * Copyright 2018 Google LLC
 *
 * 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
 *
 *     https://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.
 *
 */

const assert = require('assert');
const execSync = require('child_process').execSync;
const commandExists = require('command-exists').sync;
const fs = require('fs');
const path = require('path');
const removeDirectory = require('./common.js').removeDirectory;
const GENERATED_CODE_PATH = require('./common.js').GENERATED_CODE_PATH;
const mockXmlHttpRequest = require('mock-xmlhttprequest');

var MockXMLHttpRequest;

describe('protoc generated code', function() {
  const genCodePath = path.resolve(__dirname, './echo_pb.js');
  const genCodeCmd = 'protoc -I=./test/protos echo.proto ' +
                     '--js_out=import_style=commonjs:./test';

  before(function() {
    if (!commandExists('protoc')) {
      assert.fail('protoc is not installed');
    }
  });

  beforeEach(function() {
    if (fs.existsSync(genCodePath)) {
      fs.unlinkSync(genCodePath);
    }
  });

  afterEach(function() {
    if (fs.existsSync(genCodePath)) {
      fs.unlinkSync(genCodePath);
    }
  });

  it('should exist', function() {
    execSync(genCodeCmd);
    assert.equal(true, fs.existsSync(genCodePath));
  });

  it('should import', function() {
    execSync(genCodeCmd);
    const {EchoRequest} = require(genCodePath);
    var req = new EchoRequest();
    req.setMessage('abc');
    assert.equal('abc', req.getMessage());
  });
});

describe('grpc-web generated code (commonjs+grpcwebtext)', function() {
  const oldXMLHttpRequest = global.XMLHttpRequest;

  const protoGenCodePath = path.resolve(__dirname, './echo_pb.js');
  const genCodePath = path.resolve(__dirname, './echo_grpc_web_pb.js');

  const genCodeCmd =
    'protoc -I=./test/protos echo.proto ' +
    '--js_out=import_style=commonjs:./test ' +
    '--grpc-web_out=import_style=commonjs,mode=grpcwebtext:./test';

  before(function() {
    ['protoc', 'protoc-gen-grpc-web'].map(prog => {
      if (!commandExists(prog)) {
        assert.fail(`${prog} is not installed`);
      }
    });
  });

  beforeEach(function() {
    if (fs.existsSync(protoGenCodePath)) {
      fs.unlinkSync(protoGenCodePath);
    }
    if (fs.existsSync(genCodePath)) {
      fs.unlinkSync(genCodePath);
    }
    MockXMLHttpRequest = mockXmlHttpRequest.newMockXhr()
    global.XMLHttpRequest = MockXMLHttpRequest;
  });

  afterEach(function() {
    if (fs.existsSync(protoGenCodePath)) {
      fs.unlinkSync(protoGenCodePath);
    }
    if (fs.existsSync(genCodePath)) {
      fs.unlinkSync(genCodePath);
    }
    global.XMLHttpRequest = oldXMLHttpRequest;
  });

  it('should exist', function() {
    execSync(genCodeCmd);
    assert.equal(true, fs.existsSync(genCodePath));
  });

  it('should import', function() {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    var echoService = new EchoServiceClient('Bla', null, null);
    assert.equal('function', typeof echoService.echo);
  });

  it('should send unary request', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {EchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new EchoRequest();
    request.setMessage('aaa');
    MockXMLHttpRequest.onSend = function(xhr) {
      assert.equal('POST', xhr.method);
      // a single 'aaa' string, encoded
      assert.equal('AAAAAAUKA2FhYQ==', xhr.body);
      assert.equal('MyHostname/grpc.gateway.testing.EchoService/Echo',
                   xhr.url);
      assert.equal(
        'accept: application/grpc-web-text\r\n' +
        'content-type: application/grpc-web-text\r\n' +
        'custom-header-1: value1\r\n' +
        'x-grpc-web: 1\r\n' +
        'x-user-agent: grpc-web-javascript/0.1\r\n',
        xhr.requestHeaders.getAll());
      done();
    };
    echoService.echo(request, {'custom-header-1':'value1'});
  });

  it('should receive unary response', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {EchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new EchoRequest();
    request.setMessage('aaa');
    MockXMLHttpRequest.onSend = function(xhr) {
      xhr.respond(200, {'Content-Type': 'application/grpc-web-text'},
                  // a single data frame with 'aaa' message, encoded
                  'AAAAAAUKA2FhYQ==');
    };
    echoService.echo(request, {'custom-header-1':'value1'},
                     function(err, response) {
                       assert.equal('aaa', response.getMessage());
                       done();
                     });
  });

  it('should receive streaming response', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {ServerStreamingEchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new ServerStreamingEchoRequest();
    request.setMessage('aaa');
    request.setMessageCount(3);
    MockXMLHttpRequest.onSend = function(xhr) {
      xhr.respond(200, {'Content-Type': 'application/grpc-web-text'},
                  // 3 'aaa' messages in 3 data frames, encoded
                  'AAAAAAUKA2FhYQAAAAAFCgNhYWEAAAAABQoDYWFh');
    };
    var numMessagesReceived = 0;
    var p = new Promise(function(resolve, reject) {
      var stream =
        echoService.serverStreamingEcho(request,
                                        {'custom-header-1':'value1'});
      stream.on('data', function(response) {
        numMessagesReceived++;
        assert.equal('aaa', response.getMessage());
      });
      stream.on('end', function() {
        resolve();
      });
    });
    p.then(function(res) {
      assert.equal(3, numMessagesReceived);
      done();
    });
  });

  it('should receive trailing metadata', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {EchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new EchoRequest();
    request.setMessage('aaa');
    MockXMLHttpRequest.onSend = function(xhr) {
      xhr.respond(200, {'Content-Type': 'application/grpc-web-text'},
                  // a single data frame with an 'aaa' message, followed by,
                  // a trailer frame with content 'grpc-status:0'
                  'AAAAAAUKA2FhYYAAAAAPZ3JwYy1zdGF0dXM6MA0K');
    };
    var call = echoService.echo(request, {'custom-header-1':'value1'},
                                function(err, response) {
                                  assert.equal('aaa', response.getMessage());
                                });
    call.on('status', function(status) {
      assert.equal('object', typeof status.metadata);
      assert.equal(true, 'grpc-status' in status.metadata);
      assert.equal(0, parseInt(status.metadata['grpc-status']));
      done();
    });
  });

  it('should receive error', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {EchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new EchoRequest();
    request.setMessage('aaa');
    MockXMLHttpRequest.onSend = function(xhr) {
      xhr.respond(200, {'Content-Type': 'application/grpc-web-text'},
                  // a trailer frame with content 'grpc-status:10'
                  'gAAAABBncnBjLXN0YXR1czoxMA0K');
    };
    var call = echoService.echo(request, {'custom-header-1':'value1'},
                                function(err, response) {
                                  assert.equal(10, err.code);
                                  done();
                                });
  });
});

describe('grpc-web generated code (closure+grpcwebtext)', function() {
  const oldXMLHttpRequest = global.XMLHttpRequest;

  const compiledCodePath = path.resolve(__dirname, './generated/compiled.js');
  const genCodeCmd =
    'protoc -I=./test/protos echo.proto ' +
    '--js_out=import_style=closure:./test/generated ' +
    '--grpc-web_out=import_style=closure,mode=grpcwebtext:./test/generated';
  const cwd = process.cwd();
  const jsPaths = [
    ".",
    "../../../javascript",
    "../../../third_party/closure-library",
    "../../../third_party/grpc/third_party/protobuf/js",
  ].map(jsPath => path.relative(cwd, path.resolve(__dirname, jsPath)));
  const closureArgs = [].concat(
    jsPaths.map(jsPath => `--js=${jsPath}`),
    [
      `--entry_point=goog:proto.grpc.gateway.testing.EchoAppClient`,
      `--dependency_mode=PRUNE`,
      `--js_output_file ./test/generated/compiled.js`,
      `--output_wrapper="%output%module.exports = proto.grpc.gateway.testing;"`,
    ]
  );
  const closureCmd = "google-closure-compiler " + closureArgs.join(' ');

  before(function() {
    ['protoc', 'protoc-gen-grpc-web'].map(prog => {
      if (!commandExists(prog)) {
        assert.fail(`${prog} is not installed`);
      }
    });
    if (!fs.existsSync(path.resolve(
      __dirname, '../../../javascript/net/grpc/web/grpcwebclientbase.js'))) {
      this.skip();
    }
    removeDirectory(path.resolve(__dirname, GENERATED_CODE_PATH));
    fs.mkdirSync(path.resolve(__dirname, GENERATED_CODE_PATH));
  });

  beforeEach(function() {
    MockXMLHttpRequest = mockXmlHttpRequest.newMockXhr()
    global.XMLHttpRequest = MockXMLHttpRequest;
  });

  afterEach(function() {
    global.XMLHttpRequest = oldXMLHttpRequest;
  });

  after(function() {
    removeDirectory(path.resolve(__dirname, GENERATED_CODE_PATH));
  });

  it('should exist', function() {
    execSync(genCodeCmd);
    execSync(closureCmd);
    assert.equal(true, fs.existsSync(compiledCodePath));
  });

  it('should import', function() {
    var compiled = require(compiledCodePath);
    echoAppClient = new compiled.EchoAppClient();
    assert.equal('function', typeof echoAppClient.echo);
  });

  it('should send unary request', function(done) {
    var compiled = require(compiledCodePath);
    echoAppClient = new compiled.EchoAppClient();
    MockXMLHttpRequest.onSend = function(xhr) {
      assert.equal("AAAAAAUKA2FiYw==", xhr.body);
      done();
    }
    echoAppClient.echo('abc', () => {});
  });

  it('should receive unary response', function(done) {
    var compiled = require(compiledCodePath);
    echoAppClient = new compiled.EchoAppClient();
    MockXMLHttpRequest.onSend = function(xhr) {
      assert.equal("AAAAAAUKA2FiYw==", xhr.body);
      xhr.respond(200, {'Content-Type': 'application/grpc-web-text'},
                  "AAAAAAUKA2FiYw==");
    }
    echoAppClient.echo('abc', function(err, response) {
      assert.equal("abc", response.getMessage());
      done();
    });
  });
});

describe('grpc-web generated code (commonjs+dts)', function() {
  const protoGenCodePath = path.resolve(__dirname, './echo_pb.js');
  const protoDtsGenCodePath = path.resolve(__dirname, './echo_pb.d.ts');
  const genCodePath = path.resolve(__dirname, './echo_grpc_web_pb.js');
  const genDtsCodePath = path.resolve(__dirname, './echo_grpc_web_pb.d.ts');

  const genCodeCmd =
    'protoc -I=./test/protos echo.proto ' +
    '--js_out=import_style=commonjs:./test ' +
    '--grpc-web_out=import_style=commonjs+dts,mode=grpcwebtext:./test';

  before(function() {
    ['protoc', 'protoc-gen-grpc-web'].map(prog => {
      if (!commandExists(prog)) {
        assert.fail(`${prog} is not installed`);
      }
    });
  });

  beforeEach(function() {
    [protoGenCodePath, protoDtsGenCodePath,
     genCodePath, genDtsCodePath].map(f => {
       if (fs.existsSync(f)) {
         fs.unlinkSync(f);
       }
     });
  });

  afterEach(function() {
    [protoGenCodePath, protoDtsGenCodePath,
     genCodePath, genDtsCodePath].map(f => {
       if (fs.existsSync(f)) {
         fs.unlinkSync(f);
       }
     });
  });

  it('should exist', function() {
    execSync(genCodeCmd);
    assert.equal(true, fs.existsSync(genCodePath));
    assert.equal(true, fs.existsSync(genDtsCodePath));
    assert.equal(true, fs.existsSync(protoGenCodePath));
    assert.equal(true, fs.existsSync(protoDtsGenCodePath));
  });

  it('should compile', function() {
    execSync(genCodeCmd);
    execSync(`tsc ${genDtsCodePath}`);
    execSync(`tsc ${protoDtsGenCodePath}`);
  })
});
<commit_msg>Update tests<commit_after>/**
 *
 * Copyright 2018 Google LLC
 *
 * 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
 *
 *     https://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.
 *
 */

const assert = require('assert');
const execSync = require('child_process').execSync;
const commandExists = require('command-exists').sync;
const fs = require('fs');
const path = require('path');
const removeDirectory = require('./common.js').removeDirectory;
const GENERATED_CODE_PATH = require('./common.js').GENERATED_CODE_PATH;
const mockXmlHttpRequest = require('mock-xmlhttprequest');

var MockXMLHttpRequest;

describe('protoc generated code', function() {
  const genCodePath = path.resolve(__dirname, './echo_pb.js');
  const genCodeCmd = 'protoc -I=./test/protos echo.proto ' +
                     '--js_out=import_style=commonjs:./test';

  before(function() {
    if (!commandExists('protoc')) {
      assert.fail('protoc is not installed');
    }
  });

  beforeEach(function() {
    if (fs.existsSync(genCodePath)) {
      fs.unlinkSync(genCodePath);
    }
  });

  afterEach(function() {
    if (fs.existsSync(genCodePath)) {
      fs.unlinkSync(genCodePath);
    }
  });

  it('should exist', function() {
    execSync(genCodeCmd);
    assert.equal(true, fs.existsSync(genCodePath));
  });

  it('should import', function() {
    execSync(genCodeCmd);
    const {EchoRequest} = require(genCodePath);
    var req = new EchoRequest();
    req.setMessage('abc');
    assert.equal('abc', req.getMessage());
  });
});

describe('grpc-web generated code (commonjs+grpcwebtext)', function() {
  const oldXMLHttpRequest = global.XMLHttpRequest;

  const protoGenCodePath = path.resolve(__dirname, './echo_pb.js');
  const genCodePath = path.resolve(__dirname, './echo_grpc_web_pb.js');

  const genCodeCmd =
    'protoc -I=./test/protos echo.proto ' +
    '--js_out=import_style=commonjs:./test ' +
    '--grpc-web_out=import_style=commonjs,mode=grpcwebtext:./test';

  before(function() {
    ['protoc', 'protoc-gen-grpc-web'].map(prog => {
      if (!commandExists(prog)) {
        assert.fail(`${prog} is not installed`);
      }
    });
  });

  beforeEach(function() {
    if (fs.existsSync(protoGenCodePath)) {
      fs.unlinkSync(protoGenCodePath);
    }
    if (fs.existsSync(genCodePath)) {
      fs.unlinkSync(genCodePath);
    }
    MockXMLHttpRequest = mockXmlHttpRequest.newMockXhr()
    global.XMLHttpRequest = MockXMLHttpRequest;
  });

  afterEach(function() {
    if (fs.existsSync(protoGenCodePath)) {
      fs.unlinkSync(protoGenCodePath);
    }
    if (fs.existsSync(genCodePath)) {
      fs.unlinkSync(genCodePath);
    }
    global.XMLHttpRequest = oldXMLHttpRequest;
  });

  it('should exist', function() {
    execSync(genCodeCmd);
    assert.equal(true, fs.existsSync(genCodePath));
  });

  it('should import', function() {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    var echoService = new EchoServiceClient('Bla', null, null);
    assert.equal('function', typeof echoService.echo);
  });

  it('should send unary request', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {EchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new EchoRequest();
    request.setMessage('aaa');
    MockXMLHttpRequest.onSend = function(xhr) {
      assert.equal('POST', xhr.method);
      // a single 'aaa' string, encoded
      assert.equal('AAAAAAUKA2FhYQ==', xhr.body);
      assert.equal('MyHostname/grpc.gateway.testing.EchoService/Echo',
                   xhr.url);
      assert.equal(
        'accept: application/grpc-web-text\r\n' +
        'content-type: application/grpc-web-text\r\n' +
        'custom-header-1: value1\r\n' +
        'x-grpc-web: 1\r\n' +
        'x-user-agent: grpc-web-javascript/0.1\r\n',
        xhr.requestHeaders.getAll());
      done();
    };
    echoService.echo(request, {'custom-header-1':'value1'});
  });

  it('should receive unary response', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {EchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new EchoRequest();
    request.setMessage('aaa');
    MockXMLHttpRequest.onSend = function(xhr) {
      xhr.respond(200, {'Content-Type': 'application/grpc-web-text'},
                  // a single data frame with 'aaa' message, encoded
                  'AAAAAAUKA2FhYQ==');
    };
    echoService.echo(request, {'custom-header-1':'value1'},
                     function(err, response) {
                       assert.equal('aaa', response.getMessage());
                       done();
                     });
  });

  it('should receive streaming response', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {ServerStreamingEchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new ServerStreamingEchoRequest();
    request.setMessage('aaa');
    request.setMessageCount(3);
    MockXMLHttpRequest.onSend = function(xhr) {
      xhr.respond(200, {'Content-Type': 'application/grpc-web-text'},
                  // 3 'aaa' messages in 3 data frames, encoded
                  'AAAAAAUKA2FhYQAAAAAFCgNhYWEAAAAABQoDYWFh');
    };
    var numMessagesReceived = 0;
    var p = new Promise(function(resolve, reject) {
      var stream =
        echoService.serverStreamingEcho(request,
                                        {'custom-header-1':'value1'});
      stream.on('data', function(response) {
        numMessagesReceived++;
        assert.equal('aaa', response.getMessage());
      });
      stream.on('end', function() {
        resolve();
      });
    });
    p.then(function(res) {
      assert.equal(3, numMessagesReceived);
      done();
    });
  });

  it('should receive trailing metadata', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {EchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new EchoRequest();
    request.setMessage('aaa');
    MockXMLHttpRequest.onSend = function(xhr) {
      xhr.respond(
        200, {'Content-Type': 'application/grpc-web-text'},
        // a single data frame with an 'aaa' message, followed by,
        // a trailer frame with content 'grpc-status: 0\d\ax-custom-1: ababab'
        'AAAAAAUKA2FhYYAAAAAkZ3JwYy1zdGF0dXM6IDANCngtY3VzdG9tLTE6IGFiYWJhYg0K');
    };
    var call = echoService.echo(request, {'custom-header-1':'value1'},
                                function(err, response) {
                                  assert.equal('aaa', response.getMessage());
                                });
    call.on('status', function(status) {
      assert.equal('object', typeof status.metadata);
      assert.equal(false, 'grpc-status' in status.metadata);
      assert.equal(true, 'x-custom-1' in status.metadata);
      assert.equal('ababab', status.metadata['x-custom-1']);
      done();
    });
  });

  it('should receive error', function(done) {
    execSync(genCodeCmd);
    const {EchoServiceClient} = require(genCodePath);
    const {EchoRequest} = require(protoGenCodePath);
    var echoService = new EchoServiceClient('MyHostname', null, null);
    var request = new EchoRequest();
    request.setMessage('aaa');
    MockXMLHttpRequest.onSend = function(xhr) {
      xhr.respond(200, {'Content-Type': 'application/grpc-web-text'},
                  // a trailer frame with content 'grpc-status:10'
                  'gAAAABBncnBjLXN0YXR1czoxMA0K');
    };
    var call = echoService.echo(request, {'custom-header-1':'value1'},
                                function(err, response) {
                                  assert.equal(10, err.code);
                                  done();
                                });
  });
});

describe('grpc-web generated code (closure+grpcwebtext)', function() {
  const oldXMLHttpRequest = global.XMLHttpRequest;

  const compiledCodePath = path.resolve(__dirname, './generated/compiled.js');
  const genCodeCmd =
    'protoc -I=./test/protos echo.proto ' +
    '--js_out=import_style=closure:./test/generated ' +
    '--grpc-web_out=import_style=closure,mode=grpcwebtext:./test/generated';
  const cwd = process.cwd();
  const jsPaths = [
    ".",
    "../../../javascript",
    "../../../third_party/closure-library",
    "../../../third_party/grpc/third_party/protobuf/js",
  ].map(jsPath => path.relative(cwd, path.resolve(__dirname, jsPath)));
  const closureArgs = [].concat(
    jsPaths.map(jsPath => `--js=${jsPath}`),
    [
      `--entry_point=goog:proto.grpc.gateway.testing.EchoAppClient`,
      `--dependency_mode=PRUNE`,
      `--js_output_file ./test/generated/compiled.js`,
      `--output_wrapper="%output%module.exports = proto.grpc.gateway.testing;"`,
    ]
  );
  const closureCmd = "google-closure-compiler " + closureArgs.join(' ');

  before(function() {
    ['protoc', 'protoc-gen-grpc-web'].map(prog => {
      if (!commandExists(prog)) {
        assert.fail(`${prog} is not installed`);
      }
    });
    if (!fs.existsSync(path.resolve(
      __dirname, '../../../javascript/net/grpc/web/grpcwebclientbase.js'))) {
      this.skip();
    }
    removeDirectory(path.resolve(__dirname, GENERATED_CODE_PATH));
    fs.mkdirSync(path.resolve(__dirname, GENERATED_CODE_PATH));
  });

  beforeEach(function() {
    MockXMLHttpRequest = mockXmlHttpRequest.newMockXhr()
    global.XMLHttpRequest = MockXMLHttpRequest;
  });

  afterEach(function() {
    global.XMLHttpRequest = oldXMLHttpRequest;
  });

  after(function() {
    removeDirectory(path.resolve(__dirname, GENERATED_CODE_PATH));
  });

  it('should exist', function() {
    execSync(genCodeCmd);
    execSync(closureCmd);
    assert.equal(true, fs.existsSync(compiledCodePath));
  });

  it('should import', function() {
    var compiled = require(compiledCodePath);
    echoAppClient = new compiled.EchoAppClient();
    assert.equal('function', typeof echoAppClient.echo);
  });

  it('should send unary request', function(done) {
    var compiled = require(compiledCodePath);
    echoAppClient = new compiled.EchoAppClient();
    MockXMLHttpRequest.onSend = function(xhr) {
      assert.equal("AAAAAAUKA2FiYw==", xhr.body);
      done();
    }
    echoAppClient.echo('abc', () => {});
  });

  it('should receive unary response', function(done) {
    var compiled = require(compiledCodePath);
    echoAppClient = new compiled.EchoAppClient();
    MockXMLHttpRequest.onSend = function(xhr) {
      assert.equal("AAAAAAUKA2FiYw==", xhr.body);
      xhr.respond(200, {'Content-Type': 'application/grpc-web-text'},
                  "AAAAAAUKA2FiYw==");
    }
    echoAppClient.echo('abc', function(err, response) {
      assert.equal("abc", response.getMessage());
      done();
    });
  });
});

describe('grpc-web generated code (commonjs+dts)', function() {
  const protoGenCodePath = path.resolve(__dirname, './echo_pb.js');
  const protoDtsGenCodePath = path.resolve(__dirname, './echo_pb.d.ts');
  const genCodePath = path.resolve(__dirname, './echo_grpc_web_pb.js');
  const genDtsCodePath = path.resolve(__dirname, './echo_grpc_web_pb.d.ts');

  const genCodeCmd =
    'protoc -I=./test/protos echo.proto ' +
    '--js_out=import_style=commonjs:./test ' +
    '--grpc-web_out=import_style=commonjs+dts,mode=grpcwebtext:./test';

  before(function() {
    ['protoc', 'protoc-gen-grpc-web'].map(prog => {
      if (!commandExists(prog)) {
        assert.fail(`${prog} is not installed`);
      }
    });
  });

  beforeEach(function() {
    [protoGenCodePath, protoDtsGenCodePath,
     genCodePath, genDtsCodePath].map(f => {
       if (fs.existsSync(f)) {
         fs.unlinkSync(f);
       }
     });
  });

  afterEach(function() {
    [protoGenCodePath, protoDtsGenCodePath,
     genCodePath, genDtsCodePath].map(f => {
       if (fs.existsSync(f)) {
         fs.unlinkSync(f);
       }
     });
  });

  it('should exist', function() {
    execSync(genCodeCmd);
    assert.equal(true, fs.existsSync(genCodePath));
    assert.equal(true, fs.existsSync(genDtsCodePath));
    assert.equal(true, fs.existsSync(protoGenCodePath));
    assert.equal(true, fs.existsSync(protoDtsGenCodePath));
  });

  it('should compile', function() {
    execSync(genCodeCmd);
    execSync(`tsc ${genDtsCodePath}`);
    execSync(`tsc ${protoDtsGenCodePath}`);
  })
});
<|endoftext|>


<commit_before>/*
 * 2016-10-18 16:00:50 fyc构建
 *
 * 2016-11-5 10:21:27 拆分提示,添加path必须为数组类型
 *
 * 2016-11-16 09:45:01 修改提示错误问题
 *
 * 2016-12-11 09:50:44 修复提示错
 * */
(function(global, factory) {
	global._require = factory(global);
})(typeof window !== 'undefined' ? window : this, function(global) {
		//模块列表
	var	modules = {
			//模块路径
			modulePaths: [],
			//模块id名
			moduleIdNames: [],
			//已经引入的模块
			isLoadmodulePaths: [],
			//是重复调用use
			isUse: false,
			//模块接口函数
			moduleFns: {},
			//模块注释
			modulesComment:{}
		},
		//判断模块是否加载
		isLoadEnd = [];

	var _$ = (function() {
		var getEls = (function() {
			return function(el) {
				return document.getElementsByTagName(el)
			};
		})();

		var getEl = (function() {
			return function(el) {
				return document.getElementById(el)
			};
		})();
		return {
			getEls: getEls,
			getEl: getEl
		}
	})();

	function setComment(id,comment){
		if(comment && typeof comment == 'string'){
			modules.modulesComment[id] = comment;
		}
	}

	//兼容性IE8
	(function compatibility(){
		//不兼容IE8代理数据
		if(navigator.userAgent.indexOf('MSIE 8.0') == -1){
			//共享变量
			Object.defineProperty(global,'G',{
				value:{},
				enumerable: true,
				configurable: false,
				writable:false
			});
		}
		//兼容IE8中 的indexOf
		if(!Array.prototype.indexOf) {
			Array.prototype.indexOf = function(val) {
				for(var index = 0, len = this.length; index < len; index++) {
					if(this[index] === val) {
						return index;
					}
				}
				return -1;
			}
		}
	})();

	/*引入模块文件*/
	function _require(id) {
		if(typeof id === 'string') {
			//从模块中获取对象
			var moduleIndex = modules.moduleIdNames.indexOf(id);
			if(moduleIndex === -1) {
				_require.error(0, id);
			} else {
				//返回暴露的接口
				return new modules.moduleFns[id]();
			}
		} else {
			_require.error(1);
		}
	}

	/*模块构建*/
	_require.define = function(id, fn,comment) {
		//id不能为数字
		if(typeof id === 'string') {
			if(typeof fn === 'function') {
				//是否已存在同样id的模块名
				if(modules.moduleIdNames.indexOf(id) === -1) {
					//储存模块名
					modules.moduleIdNames.push(id);
					//模块函数推入到临时的数组内,在调用结束后执行
					modules.moduleFns[id] = fn;
					//存在注释的话添加注释到的模块列表中
					setComment(id,comment);
					/*--------------------------错误警告--------------------------*/
				} else {
					_require.error(2, id);
				}
			} else {
				_require.error(3);
			}
		} else {
			_require.error(4);
		}
	}

	/*开始模块构建初始化引入模块文件*/
	_require.use = function(callback) {
		if(_require.config instanceof Object) {
			var modulePaths = _require.config.path;
			if(modulePaths instanceof Array) {
				var len = modulePaths.length,
					i = 0;
				//把模块引入到文件中
				for(; i < len; i++) {
					//判定是否又重复引入的模块
					if(isLoadModules(modulePaths[i])) {
						var script = document.createElement('script');
						script.src = modulePaths[i];
						_$.getEls('head')[0].appendChild(script);
						//把模块文件索引推送到模块列表,方便获取
						modules.modulePaths.push(modulePaths[i]);
						//已加载的模块
						modules.isLoadmodulePaths.push(modulePaths[i]);
						//监听所有的script加载情况
						isLoadEnd.push(false);
						//加载模块成功
						script.onload = (function(i) {
							return function() {
								isLoadEnd[i] = true;
								//判断所有的模块加载是否完毕
								if(isLoadEnd.indexOf(false) != -1) {
									return;
								}
								//所有模块加载完毕后的回调函数
								callback();
							}
						})(i);
						//加载模块错误
						script.onerror = (function(i) {
							return function() {
								_require.error(5,i);
							}
						})(i);
					}

				}
				if(modules.isUse) {
					callback();
				}
			} else {
				_require.error(6);
			}
			/*--------------------------错误警告--------------------------*/
		} else {
			_require.error(7);
		}
	}

	_require.error = function(errorStatus, id) {
		switch(errorStatus) {
			case 0:
				console.warn('找不到' + id + '模块!');
				break;
			case 1:
				console.warn('文件引用id必须为字符串!');
				break;
			case 2:
				throw('存在相同' + id + '的模块!');
				break;
			case 3:
				console.warn('传入的第二参数不是函数类型');
				break;
			case 4:
				console.warn('模块id必须为字符串!');
				break;
			case 5:
				throw(modules.modulePaths[id] + '模块加载有误!');
				break;
			case 6:
				console.warn('_require.config配置路径类型为数组!');
				break;
			case 7:
				console.warn('_require.config配置有误!');
				break;
			default:
				;
		}
	}

	//模块列表
	_require.modulesList = modules.moduleIdNames;
	//模块注释信息查询
	_require.modulesComment = modules.modulesComment;

	//判定是否多次使用use，以及多次引入相同的模块
	function isLoadModules(path) {
		var i = 0,
			len = modules.isLoadmodulePaths.length;
		for(; i < len; i++) {
			if(modules.isLoadmodulePaths[i] === path) {
				modules.isUse = true;
				return false;
			}
		}
		return true;
	}

	_require.version = '1.0.6';

	return _require;
});<commit_msg>没有注释也会存在提示信息，不返回undefined<commit_after>/*
 * 2016-10-18 16:00:50 fyc构建
 *
 * 2016-11-5 10:21:27 拆分提示,添加path必须为数组类型
 *
 * 2016-11-16 09:45:01 修改提示错误问题
 *
 * 2016-12-11 09:50:44 修复提示错
 * */
(function(global, factory) {
	global._require = factory(global);
})(typeof window !== 'undefined' ? window : this, function(global) {
		//模块列表
	var	modules = {
			//模块路径
			modulePaths: [],
			//模块id名
			moduleIdNames: [],
			//已经引入的模块
			isLoadmodulePaths: [],
			//是重复调用use
			isUse: false,
			//模块接口函数
			moduleFns: {},
			//模块注释
			modulesComment:{}
		},
		//判断模块是否加载
		isLoadEnd = [];

	var _$ = (function() {
		var getEls = (function() {
			return function(el) {
				return document.getElementsByTagName(el)
			};
		})();

		var getEl = (function() {
			return function(el) {
				return document.getElementById(el)
			};
		})();
		return {
			getEls: getEls,
			getEl: getEl
		}
	})();

	function setComment(id,comment){
		if(comment && typeof comment == 'string'){
			modules.modulesComment[id] = comment;
		}else{
			modules.modulesComment[id] = '该模块没有设置注释';
		}
	}

	//兼容性IE8
	(function compatibility(){
		//不兼容IE8代理数据
		if(navigator.userAgent.indexOf('MSIE 8.0') == -1){
			//共享变量
			Object.defineProperty(global,'G',{
				value:{},
				enumerable: true,
				configurable: false,
				writable:false
			});
		}
		//兼容IE8中 的indexOf
		if(!Array.prototype.indexOf) {
			Array.prototype.indexOf = function(val) {
				for(var index = 0, len = this.length; index < len; index++) {
					if(this[index] === val) {
						return index;
					}
				}
				return -1;
			}
		}
	})();

	/*引入模块文件*/
	function _require(id) {
		if(typeof id === 'string') {
			//从模块中获取对象
			var moduleIndex = modules.moduleIdNames.indexOf(id);
			if(moduleIndex === -1) {
				_require.error(0, id);
			} else {
				//返回暴露的接口
				return new modules.moduleFns[id]();
			}
		} else {
			_require.error(1);
		}
	}

	/*模块构建*/
	_require.define = function(id, fn,comment) {
		//id不能为数字
		if(typeof id === 'string') {
			if(typeof fn === 'function') {
				//是否已存在同样id的模块名
				if(modules.moduleIdNames.indexOf(id) === -1) {
					//储存模块名
					modules.moduleIdNames.push(id);
					//模块函数推入到临时的数组内,在调用结束后执行
					modules.moduleFns[id] = fn;
					//存在注释的话添加注释到的模块列表中
					setComment(id,comment);
					/*--------------------------错误警告--------------------------*/
				} else {
					_require.error(2, id);
				}
			} else {
				_require.error(3);
			}
		} else {
			_require.error(4);
		}
	}

	/*开始模块构建初始化引入模块文件*/
	_require.use = function(callback) {
		if(_require.config instanceof Object) {
			var modulePaths = _require.config.path;
			if(modulePaths instanceof Array) {
				var len = modulePaths.length,
					i = 0;
				//把模块引入到文件中
				for(; i < len; i++) {
					//判定是否又重复引入的模块
					if(isLoadModules(modulePaths[i])) {
						var script = document.createElement('script');
						script.src = modulePaths[i];
						_$.getEls('head')[0].appendChild(script);
						//把模块文件索引推送到模块列表,方便获取
						modules.modulePaths.push(modulePaths[i]);
						//已加载的模块
						modules.isLoadmodulePaths.push(modulePaths[i]);
						//监听所有的script加载情况
						isLoadEnd.push(false);
						//加载模块成功
						script.onload = (function(i) {
							return function() {
								isLoadEnd[i] = true;
								//判断所有的模块加载是否完毕
								if(isLoadEnd.indexOf(false) != -1) {
									return;
								}
								//所有模块加载完毕后的回调函数
								callback();
							}
						})(i);
						//加载模块错误
						script.onerror = (function(i) {
							return function() {
								_require.error(5,i);
							}
						})(i);
					}

				}
				if(modules.isUse) {
					callback();
				}
			} else {
				_require.error(6);
			}
			/*--------------------------错误警告--------------------------*/
		} else {
			_require.error(7);
		}
	}

	_require.error = function(errorStatus, id) {
		switch(errorStatus) {
			case 0:
				console.warn('找不到' + id + '模块!');
				break;
			case 1:
				console.warn('文件引用id必须为字符串!');
				break;
			case 2:
				throw('存在相同' + id + '的模块!');
				break;
			case 3:
				console.warn('传入的第二参数不是函数类型');
				break;
			case 4:
				console.warn('模块id必须为字符串!');
				break;
			case 5:
				throw(modules.modulePaths[id] + '模块加载有误!');
				break;
			case 6:
				console.warn('_require.config配置路径类型为数组!');
				break;
			case 7:
				console.warn('_require.config配置有误!');
				break;
			default:
				;
		}
	}

	//模块列表
	_require.modulesList = modules.moduleIdNames;
	//模块注释信息查询
	_require.modulesComment = modules.modulesComment;

	//判定是否多次使用use，以及多次引入相同的模块
	function isLoadModules(path) {
		var i = 0,
			len = modules.isLoadmodulePaths.length;
		for(; i < len; i++) {
			if(modules.isLoadmodulePaths[i] === path) {
				modules.isUse = true;
				return false;
			}
		}
		return true;
	}

	_require.version = '1.0.6';

	return _require;
});<|endoftext|>


<commit_before>//    abc_tunebook.js: splits a string representing ABC Music Notation into individual tunes.
//    Copyright (C) 2010 Paul Rosen (paul at paulrosen dot net)
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

/*global document, Raphael */
/*global window */

if (!window.ABCJS)
	window.ABCJS = {};

(function() {
ABCJS.numberOfTunes = function(abc) {
	var tunes = abc.split("\nX:");
	var num = tunes.length;
	if (num === 0) num = 1;
	return num;
};

ABCJS.TuneBook = function(book) {
	var This = this;
	var directives = "";
	book = window.ABCJS.parse.strip(book);
	var tunes = book.split("\nX:");
	for (var i = 1; i < tunes.length; i++)	// Put back the X: that we lost when splitting the tunes.
		tunes[i] = "X:" + tunes[i];
	// Keep track of the character position each tune starts with.
	var pos = 0;
	This.tunes = [];
	window.ABCJS.parse.each(tunes, function(tune) {
		This.tunes.push({ abc: tune, startPos: pos});
		pos += tune.length;
	});
	if (This.tunes.length > 1 && !window.ABCJS.parse.startsWith(This.tunes[0].abc, 'X:')) {	// If there is only one tune, the X: might be missing, otherwise assume the top of the file is "intertune"
		// There could be file-wide directives in this, if so, we need to insert it into each tune. We can probably get away with
		// just looking for file-wide directives here (before the first tune) and inserting them at the bottom of each tune, since
		// the tune is parsed all at once. The directives will be seen before the engraver begins processing.
		var dir = This.tunes.shift();
		var arrDir = dir.abc.split('\n');
		window.ABCJS.parse.each(arrDir, function(line) {
			if (window.ABCJS.parse.startsWith(line, '%%'))
				directives += line + '\n';
		});
	}
	This.header = directives;

	// Now, the tune ends at a blank line, so truncate it if needed. There may be "intertune" stuff.
	window.ABCJS.parse.each(This.tunes, function(tune) {
		var end = tune.abc.indexOf('\n\n');
		if (end > 0)
			tune.abc = tune.abc.substring(0, end);
		tune.pure = tune.abc;
		tune.abc = directives + tune.abc;

		// for the user's convenience, parse and store the title separately. The title is between the first T: and the next \n
		var title = tune.pure.split("T:");
		if (title.length > 1) {
			title = title[1].split("\n");
			tune.title = title[0].replace(/^\s+|\s+$/g, '');;
		} else
			tune.title = "";

		// for the user's convenience, parse and store the id separately. The id is between the first X: and the next \n
		var id = tune.pure.substring(2,tune.pure.indexOf("\n"));
		tune.id = id.replace(/^\s+|\s+$/g, '');
	});
};

ABCJS.TuneBook.prototype.getTuneById = function (id) {
	for (var i = 0; i < this.tunes.length; i++) {
		if (this.tunes[i].id === id)
			return this.tunes[i];
	}
	return null;
};

ABCJS.TuneBook.prototype.getTuneByTitle = function (title) {
	for (var i = 0; i < this.tunes.length; i++) {
		if (this.tunes[i].title === title)
			return this.tunes[i];
	}
	return null;
};

function renderEngine(callback, output, abc, parserParams, renderParams) {
	var isArray = function(testObject) {
		return testObject && !(testObject.propertyIsEnumerable('length')) && typeof testObject === 'object' && typeof testObject.length === 'number';
	};

	// check and normalize input parameters
	if (output === undefined || abc === undefined)
		return;
	if (!isArray(output))
		output = [ output ];
	if (parserParams === undefined)
		parserParams = {};
	if (renderParams === undefined)
		renderParams = {};
	var currentTune = renderParams.startingTune ? renderParams.startingTune : 0;

	// parse the abc string
	var book = new ABCJS.TuneBook(abc);
	var abcParser = new window.ABCJS.parse.Parse();

	// output each tune, if it exists. Otherwise clear the div.
	for (var i = 0; i < output.length; i++) {
		var div = output[i];
		if (typeof(div) === "string")
			div = document.getElementById(div);
		if (div) {
			div.innerHTML = "";
			if (currentTune < book.tunes.length) {
				abcParser.parse(book.tunes[currentTune].abc, parserParams);
				var tune = abcParser.getTune();
				callback(div, tune);
			}
		}
		currentTune++;
	}
}

// A quick way to render a tune from javascript when interactivity is not required.
// This is used when a javascript routine has some abc text that it wants to render
// in a div or collection of divs. One tune or many can be rendered.
//
// parameters:
//		output: an array of divs that the individual tunes are rendered to.
//			If the number of tunes exceeds the number of divs in the array, then
//			only the first tunes are rendered. If the number of divs exceeds the number
//			of tunes, then the unused divs are cleared. The divs can be passed as either
//			elements or strings of ids. If ids are passed, then the div MUST exist already.
//			(if a single element is passed, then it is an implied array of length one.)
//			(if a null is passed for an element, or the element doesn't exist, then that tune is skipped.)
//		abc: text representing a tune or an entire tune book in ABC notation.
//		renderParams: hash of:
//			startingTune: an index, starting at zero, representing which tune to start rendering at.
//				(If this element is not present, then rendering starts at zero.)
//			width: 800 by default. The width in pixels of the output paper
ABCJS.renderAbc = function(output, abc, parserParams, engraverParams, renderParams) {
	function callback(div, tune) {
		var width = renderParams ? renderParams.width ? renderParams.width : 800 : 800;
		var paper = Raphael(div, width, 400);
		if (engraverParams === undefined)
			engraverParams = {};
		var engraver_controller = new ABCJS.write.Printer(paper, engraverParams);
		engraver_controller.printABC(tune);
	}

	renderEngine(callback, output, abc, parserParams, renderParams);
};

// A quick way to render a tune from javascript when interactivity is not required.
// This is used when a javascript routine has some abc text that it wants to render
// in a div or collection of divs. One tune or many can be rendered.
//
// parameters:
//		output: an array of divs that the individual tunes are rendered to.
//			If the number of tunes exceeds the number of divs in the array, then
//			only the first tunes are rendered. If the number of divs exceeds the number
//			of tunes, then the unused divs are cleared. The divs can be passed as either
//			elements or strings of ids. If ids are passed, then the div MUST exist already.
//			(if a single element is passed, then it is an implied array of length one.)
//			(if a null is passed for an element, or the element doesn't exist, then that tune is skipped.)
//		abc: text representing a tune or an entire tune book in ABC notation.
//		renderParams: hash of:
//			startingTune: an index, starting at zero, representing which tune to start rendering at.
//				(If this element is not present, then rendering starts at zero.)
ABCJS.renderMidi = function(output, abc, parserParams, midiParams, renderParams) {
	function callback(div, tune) {
		if (midiParams === undefined)
			midiParams = {};
		var midiwriter = new ABCJS.midi.MidiWriter(div, midiParams);
		midiwriter.writeABC(tune);
	}

	renderEngine(callback, output, abc, parserParams, renderParams);
};
})();
<commit_msg>Return tune object on renderABC so that it can be manipulated further.<commit_after>//    abc_tunebook.js: splits a string representing ABC Music Notation into individual tunes.
//    Copyright (C) 2010 Paul Rosen (paul at paulrosen dot net)
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

/*global document, Raphael */
/*global window, ABCJS */

if (!window.ABCJS)
	window.ABCJS = {};

(function() {
ABCJS.numberOfTunes = function(abc) {
	var tunes = abc.split("\nX:");
	var num = tunes.length;
	if (num === 0) num = 1;
	return num;
};

ABCJS.TuneBook = function(book) {
	var This = this;
	var directives = "";
	book = window.ABCJS.parse.strip(book);
	var tunes = book.split("\nX:");
	for (var i = 1; i < tunes.length; i++)	// Put back the X: that we lost when splitting the tunes.
		tunes[i] = "X:" + tunes[i];
	// Keep track of the character position each tune starts with.
	var pos = 0;
	This.tunes = [];
	window.ABCJS.parse.each(tunes, function(tune) {
		This.tunes.push({ abc: tune, startPos: pos});
		pos += tune.length;
	});
	if (This.tunes.length > 1 && !window.ABCJS.parse.startsWith(This.tunes[0].abc, 'X:')) {	// If there is only one tune, the X: might be missing, otherwise assume the top of the file is "intertune"
		// There could be file-wide directives in this, if so, we need to insert it into each tune. We can probably get away with
		// just looking for file-wide directives here (before the first tune) and inserting them at the bottom of each tune, since
		// the tune is parsed all at once. The directives will be seen before the engraver begins processing.
		var dir = This.tunes.shift();
		var arrDir = dir.abc.split('\n');
		window.ABCJS.parse.each(arrDir, function(line) {
			if (window.ABCJS.parse.startsWith(line, '%%'))
				directives += line + '\n';
		});
	}
	This.header = directives;

	// Now, the tune ends at a blank line, so truncate it if needed. There may be "intertune" stuff.
	window.ABCJS.parse.each(This.tunes, function(tune) {
		var end = tune.abc.indexOf('\n\n');
		if (end > 0)
			tune.abc = tune.abc.substring(0, end);
		tune.pure = tune.abc;
		tune.abc = directives + tune.abc;

		// for the user's convenience, parse and store the title separately. The title is between the first T: and the next \n
		var title = tune.pure.split("T:");
		if (title.length > 1) {
			title = title[1].split("\n");
			tune.title = title[0].replace(/^\s+|\s+$/g, '');
		} else
			tune.title = "";

		// for the user's convenience, parse and store the id separately. The id is between the first X: and the next \n
		var id = tune.pure.substring(2,tune.pure.indexOf("\n"));
		tune.id = id.replace(/^\s+|\s+$/g, '');
	});
};

ABCJS.TuneBook.prototype.getTuneById = function (id) {
	for (var i = 0; i < this.tunes.length; i++) {
		if (this.tunes[i].id === id)
			return this.tunes[i];
	}
	return null;
};

ABCJS.TuneBook.prototype.getTuneByTitle = function (title) {
	for (var i = 0; i < this.tunes.length; i++) {
		if (this.tunes[i].title === title)
			return this.tunes[i];
	}
	return null;
};

function renderEngine(callback, output, abc, parserParams, renderParams) {
	var ret = [];
	var isArray = function(testObject) {
		return testObject && !(testObject.propertyIsEnumerable('length')) && typeof testObject === 'object' && typeof testObject.length === 'number';
	};

	// check and normalize input parameters
	if (output === undefined || abc === undefined)
		return;
	if (!isArray(output))
		output = [ output ];
	if (parserParams === undefined)
		parserParams = {};
	if (renderParams === undefined)
		renderParams = {};
	var currentTune = renderParams.startingTune ? renderParams.startingTune : 0;

	// parse the abc string
	var book = new ABCJS.TuneBook(abc);
	var abcParser = new window.ABCJS.parse.Parse();

	// output each tune, if it exists. Otherwise clear the div.
	for (var i = 0; i < output.length; i++) {
		var div = output[i];
		if (typeof(div) === "string")
			div = document.getElementById(div);
		if (div) {
			div.innerHTML = "";
			if (currentTune < book.tunes.length) {
				abcParser.parse(book.tunes[currentTune].abc, parserParams);
				var tune = abcParser.getTune();
				ret.push(tune);
				callback(div, tune);
			}
		}
		currentTune++;
	}
	return ret;
}

// A quick way to render a tune from javascript when interactivity is not required.
// This is used when a javascript routine has some abc text that it wants to render
// in a div or collection of divs. One tune or many can be rendered.
//
// parameters:
//		output: an array of divs that the individual tunes are rendered to.
//			If the number of tunes exceeds the number of divs in the array, then
//			only the first tunes are rendered. If the number of divs exceeds the number
//			of tunes, then the unused divs are cleared. The divs can be passed as either
//			elements or strings of ids. If ids are passed, then the div MUST exist already.
//			(if a single element is passed, then it is an implied array of length one.)
//			(if a null is passed for an element, or the element doesn't exist, then that tune is skipped.)
//		abc: text representing a tune or an entire tune book in ABC notation.
//		renderParams: hash of:
//			startingTune: an index, starting at zero, representing which tune to start rendering at.
//				(If this element is not present, then rendering starts at zero.)
//			width: 800 by default. The width in pixels of the output paper
ABCJS.renderAbc = function(output, abc, parserParams, engraverParams, renderParams) {
	function callback(div, tune) {
		var width = renderParams ? renderParams.width ? renderParams.width : 800 : 800;
		var paper = Raphael(div, width, 400);
		if (engraverParams === undefined)
			engraverParams = {};
		var engraver_controller = new ABCJS.write.Printer(paper, engraverParams);
		engraver_controller.printABC(tune);
	}

	return renderEngine(callback, output, abc, parserParams, renderParams);
};

// A quick way to render a tune from javascript when interactivity is not required.
// This is used when a javascript routine has some abc text that it wants to render
// in a div or collection of divs. One tune or many can be rendered.
//
// parameters:
//		output: an array of divs that the individual tunes are rendered to.
//			If the number of tunes exceeds the number of divs in the array, then
//			only the first tunes are rendered. If the number of divs exceeds the number
//			of tunes, then the unused divs are cleared. The divs can be passed as either
//			elements or strings of ids. If ids are passed, then the div MUST exist already.
//			(if a single element is passed, then it is an implied array of length one.)
//			(if a null is passed for an element, or the element doesn't exist, then that tune is skipped.)
//		abc: text representing a tune or an entire tune book in ABC notation.
//		renderParams: hash of:
//			startingTune: an index, starting at zero, representing which tune to start rendering at.
//				(If this element is not present, then rendering starts at zero.)
ABCJS.renderMidi = function(output, abc, parserParams, midiParams, renderParams) {
	function callback(div, tune) {
		if (midiParams === undefined)
			midiParams = {};
		var midiwriter = new ABCJS.midi.MidiWriter(div, midiParams);
		midiwriter.writeABC(tune);
	}

	return renderEngine(callback, output, abc, parserParams, renderParams);
};
})();
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  name: 'ClientRuntimeException',
  package: 'foam.core',
  implements: [ 'foam.core.ExceptionInterface' ],
  javaExtends: 'RuntimeException',

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(
          `
            public ClientRuntimeException(String message) {
              super(message);
            }

            public ClientRuntimeException(Throwable cause) {
              super(cause);
            }
          `
        );
      }
    }
  ],

  methods: [
    {
        // TODO: cloning this property from ExceptionInterface creates a bug.
        name: 'getClientRethrowException',
        documentation:
        `If an exception is intended to go to the client, this
        returns an exception object; it returns null otherwise.`,
        type: 'RuntimeException',
        visibility: 'public',
        javaCode: `return this;`
    }
  ]
})
<commit_msg>Added super(message, cause) to ClientRuntimeException<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  name: 'ClientRuntimeException',
  package: 'foam.core',
  implements: [ 'foam.core.ExceptionInterface' ],
  javaExtends: 'RuntimeException',

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(
          `
            public ClientRuntimeException(String message) {
              super(message);
            }

            public ClientRuntimeException(Throwable cause) {
              super(cause);
            }

            public ClientRuntimeException(String message, Throwable cause) {
              super(message, cause);
            }
          `
        );
      }
    }
  ],

  methods: [
    {
        // TODO: cloning this property from ExceptionInterface creates a bug.
        name: 'getClientRethrowException',
        documentation:
        `If an exception is intended to go to the client, this
        returns an exception object; it returns null otherwise.`,
        type: 'RuntimeException',
        visibility: 'public',
        javaCode: `return this;`
    }
  ]
})
<|endoftext|>