<commit_before>const jsdom = require('jsdom').jsdom
const api = require('lib/db-api')

require('lib/models')()

const Topic = require('lib/models').Topic
const dbReady = require('lib/models').ready

const mapPromises = (fn) => (array) => Promise.all(array.map(fn))

function guessVersion (topic) {
  const migratedClauses = topic.clauses.filter(function (clause) {
    return !!clause.markup
  })

  // Migrated and unmigrated clauses may coexist. Better catch it early.
  if (migratedClauses.length) {
    return 3
  }

  // Handle the case when a v1 topic has summary but no clauses
  if (topic._doc && topic._doc.summary &&
    topic._doc.summary.toLowerCase().substring(0, 4) !== '<div') {
    return 1
  }

  if (topic.clauses[0] && topic.clauses[0]._doc && topic.clauses[0]._doc.clauseName) {
    // Topic %s is v1 (very old stuff with clauses)
    return 1
  } else if (topic.clauses[0] && topic.clauses[0]._doc && topic._doc.clauses[0]._doc.markup) {
    // Topic %s is v3 (wrote with a rich text editor in DemocracyOS 1.0) or already migrated
    return 3
  } else if (topic._doc && topic._doc.summary) {
    // Topic %s is v2 (wrote with a rich text editor)
    return 2
  } else {
    // Can't guess topic version
    return 3
  }
}

/**
 * Performs a topic migration to v2 supposing it has v1 structure.
 * @param topic The topic mongoose document
 * @param cb A callback function with two params: err and topic, that represents the migrated topic
 * @api private
 */

function migrateV1 (topic) {
  const data = {}

  data.clauses = topic.clauses.map((clause) => ({
    id: clause._id,
    markup: `<div>${clause._doc.text}</div>`,
    position: clause._doc.order,
    empty: false
  }))

  data.clauses.push({
    markup: `<div>${topic._doc && topic._doc.summary}</div>`,
    position: -1,
    empty: false
  })

  return Promise.all([
    Topic.collection.findOneAndUpdate({ _id: topic._id }, {
      $set: {
        clauses: data.clauses
      }
    }),
    mapPromises(function (clause) {
      const context = (clause.position === -1) ? 'summary' : 'clause'

      let reference
      if (context === 'summary') {
        reference = topic._id.toString() + '-0'
      } else {
        reference = clause._id.toString()
      }

      const query = {
        context: context,
        reference: reference
      }

      const data = {
        reference: clause._id,
        context: 'paragraph',
        topicId: topic._id
      }

      return new Promise(function (resolve, reject) {
        api.comment.update(query, data, function (err) {
          if (err) {
            console.log('Error saving comment: ' + err.toString())
          }

          resolve()
        })
      })
    })(topic.clauses)
  ])
}

function getDOM (str) {
  if (!str) return undefined
  var dom = jsdom(str)
  return dom.documentElement
}

/**
 * Performs a topic migration supposing it has v1 structure.
 * @param topic The topic mongoose document
 * @param cb A callback function with two params: err and topic, that represents the migrated topic
 * @api private
 */

function migrateV2 (topic) {
  const html = topic._doc && topic._doc.summary
  const document = getDOM(html)
  if (!document) throw Error('Bad topic _doc')
  const divs = document.getElementsByTagName('div')
  const commentsUpdates = []
  let clauses = topic.clauses

  for (var i in divs) {
    if (divs.hasOwnProperty(i)) {
      const markup = divs[i].outerHTML
      const doc = {
        markup: markup,
        position: i,
        empty: false
      }

      clauses.push(doc)

      // The newly created clause ID
      const clauseId = clauses[clauses.length - 1]._id.toString()

      // Now update its side-comments
      const reference = `${topic._id}-${parseInt(i)}`

      const query = {
        context: 'summary',
        reference: reference
      }

      const data = {
        reference: clauseId,
        context: 'paragraph',
        topicId: topic._id
      }

      commentsUpdates.push(new Promise(function (resolve, reject) {
        api.comment.update(query, data, function (err) {
          if (err) {
            console.log('Error saving comment: ' + err.toString())
          }

          resolve()
        })
      }))
    }
  }

  return Promise.all(commentsUpdates)
    .then(function () {
      return Topic.collection.findOneAndUpdate({ _id: topic._id }, {
        $set: {
          clauses: clauses
        }
      })
    })
}

exports.up = function (done) {
  const clausesVersionsAcc = { 1: 0, 2: 0, 3: 0 }

  dbReady()
    .then(() => Topic.collection
      .find({})
      .toArray()
      .then(mapPromises(function (topic) {
        var versionTopic = guessVersion(topic)
        clausesVersionsAcc[versionTopic]++

        if (versionTopic === 1) {
          return migrateV1(topic)
        } else if (versionTopic === 2) {
          return migrateV2(topic)
        } else {
          return Promise.resolve(0)
        }
      }))
      .then(function (results) {
        console.log('v1: ' + clausesVersionsAcc[1])
        console.log('v2: ' + clausesVersionsAcc[2])
        console.log('v3: ' + clausesVersionsAcc[3])
        console.log('update clauses from ' + results.filter((v) => v).length + ' topics succeded')
        done()
      })
    )
    .catch(function (err) {
      console.log('update topics clauses failed at ', err)
      throw new Error('update topics clauses failed')
    })
}

exports.down = function (done) {
  console.log('update topic clauses down migration is not implemented')
  done()
}
<commit_msg>remove lib/db-api from migration<commit_after>const jsdom = require('jsdom').jsdom

require('lib/models')()

const Topic = require('lib/models').Topic
const Comment = require('lib/models').Comment
const dbReady = require('lib/models').ready

const mapPromises = (fn) => (array) => Promise.all(array.map(fn))

function guessVersion (topic) {
  const migratedClauses = topic.clauses.filter(function (clause) {
    return !!clause.markup
  })

  // Migrated and unmigrated clauses may coexist. Better catch it early.
  if (migratedClauses.length) {
    return 3
  }

  // Handle the case when a v1 topic has summary but no clauses
  if (topic._doc && topic._doc.summary &&
    topic._doc.summary.toLowerCase().substring(0, 4) !== '<div') {
    return 1
  }

  if (topic.clauses[0] && topic.clauses[0]._doc && topic.clauses[0]._doc.clauseName) {
    // Topic %s is v1 (very old stuff with clauses)
    return 1
  } else if (topic.clauses[0] && topic.clauses[0]._doc && topic._doc.clauses[0]._doc.markup) {
    // Topic %s is v3 (wrote with a rich text editor in DemocracyOS 1.0) or already migrated
    return 3
  } else if (topic._doc && topic._doc.summary) {
    // Topic %s is v2 (wrote with a rich text editor)
    return 2
  } else {
    // Can't guess topic version
    return 3
  }
}

/**
 * Performs a topic migration to v2 supposing it has v1 structure.
 * @param topic The topic mongoose document
 * @param cb A callback function with two params: err and topic, that represents the migrated topic
 * @api private
 */

function migrateV1 (topic) {
  const data = {}

  data.clauses = topic.clauses.map((clause) => ({
    id: clause._id,
    markup: `<div>${clause._doc.text}</div>`,
    position: clause._doc.order,
    empty: false
  }))

  data.clauses.push({
    markup: `<div>${topic._doc && topic._doc.summary}</div>`,
    position: -1,
    empty: false
  })

  return Promise.all([
    Topic.collection.findOneAndUpdate({ _id: topic._id }, {
      $set: {
        clauses: data.clauses
      }
    }),
    mapPromises(function (clause) {
      const context = (clause.position === -1) ? 'summary' : 'clause'

      let reference
      if (context === 'summary') {
        reference = topic._id.toString() + '-0'
      } else {
        reference = clause._id.toString()
      }

      const query = {
        context: context,
        reference: reference
      }

      const data = {
        reference: clause._id,
        context: 'paragraph',
        topicId: topic._id
      }

      return new Promise(function (resolve, reject) {
        Comment.update(query, data, { multi: true }, function (err) {
          if (err) {
            console.log('Error saving comment: ' + err.toString())
          }

          resolve()
        })
      })
    })(topic.clauses)
  ])
}

function getDOM (str) {
  if (!str) return undefined
  var dom = jsdom(str)
  return dom.documentElement
}

/**
 * Performs a topic migration supposing it has v1 structure.
 * @param topic The topic mongoose document
 * @param cb A callback function with two params: err and topic, that represents the migrated topic
 * @api private
 */

function migrateV2 (topic) {
  const html = topic._doc && topic._doc.summary
  const document = getDOM(html)
  if (!document) throw Error('Bad topic _doc')
  const divs = document.getElementsByTagName('div')
  const commentsUpdates = []
  let clauses = topic.clauses

  for (var i in divs) {
    if (divs.hasOwnProperty(i)) {
      const markup = divs[i].outerHTML
      const doc = {
        markup: markup,
        position: i,
        empty: false
      }

      clauses.push(doc)

      // The newly created clause ID
      const clauseId = clauses[clauses.length - 1]._id.toString()

      // Now update its side-comments
      const reference = `${topic._id}-${parseInt(i)}`

      const query = {
        context: 'summary',
        reference: reference
      }

      const data = {
        reference: clauseId,
        context: 'paragraph',
        topicId: topic._id
      }

      commentsUpdates.push(new Promise(function (resolve, reject) {
        Comment.update(query, data, { multi: true }, function (err) {
          if (err) {
            console.log('Error saving comment: ' + err.toString())
          }

          resolve()
        })
      }))
    }
  }

  return Promise.all(commentsUpdates)
    .then(function () {
      return Topic.collection.findOneAndUpdate({ _id: topic._id }, {
        $set: {
          clauses: clauses
        }
      })
    })
}

exports.up = function (done) {
  const clausesVersionsAcc = { 1: 0, 2: 0, 3: 0 }

  dbReady()
    .then(() => Topic.collection
      .find({})
      .toArray()
      .then(mapPromises(function (topic) {
        var versionTopic = guessVersion(topic)
        clausesVersionsAcc[versionTopic]++

        if (versionTopic === 1) {
          return migrateV1(topic)
        } else if (versionTopic === 2) {
          return migrateV2(topic)
        } else {
          return Promise.resolve(0)
        }
      }))
      .then(function (results) {
        console.log('v1: ' + clausesVersionsAcc[1])
        console.log('v2: ' + clausesVersionsAcc[2])
        console.log('v3: ' + clausesVersionsAcc[3])
        console.log('update clauses from ' + results.filter((v) => v).length + ' topics succeded')
        done()
      })
    )
    .catch(function (err) {
      console.log('update topics clauses failed at ', err)
      throw new Error('update topics clauses failed')
    })
}

exports.down = function (done) {
  console.log('update topic clauses down migration is not implemented')
  done()
}
<|endoftext|>


<commit_before>// db.Base extensions. Currently purely about base methods needed for JSON snapshots

'use strict';

var memoize  = require('memoizee/plain')
  , ensureDb = require('dbjs/valid-dbjs');

module.exports = memoize(function (db) {
	// JSON resolvers, used to retrieve serializable data snapshots
	ensureDb(db).Base.prototype.__descriptorPrototype__.setProperties({
		labelToJSON: function (ignore) {
			return this.dynamicLabelKey ? this.object.get(this.dynamicLabelKey) : this.label;
		},
		valueToJSON: function (ignore) {
			var value = this.object.get(this.key), result;
			if (value && value.forEach && !this.nested && this.multiple) {
				result = [];
				value.forEach(function (value) { result.push(this.type.valueToJSON(value, this)); }, this);
				return result;
			}
			return this.type.valueToJSON(value, this);
		},
		fieldToJSON: function (ignore) {
			return {
				label: this.labelToJSON(),
				value: this.valueToJSON(),
				id: this.__valueId__
			};
		}
	});

	db.Base.prototype.defineProperties({
		isEmpty: { type: db.Function },
		toJSON: { type: db.Function }
	});

	return db.Base.defineProperties({
		// Whether provided value should be considered empty
		isValueEmpty: { type: db.Function, value: function (value) {
			if (value == null) return true;
			if (typeof value.isEmpty !== 'function') return false;
			return value.isEmpty();
		} },
		// Returns JSON representation of provided value (in many cases it's same as value)
		valueToJSON: { type: db.Function, value: function (value, descriptor) {
			if (value == null) return value;
			if (this.database.isObjectType(this)) {
				if (typeof value.toJSON === 'function') return value.toJSON(descriptor);
				return value.toString(descriptor);
			}
			return (new this(value)).toString(descriptor);
		} }
	});
}, { normalizer: require('memoizee/normalizers/get-1')() });
<commit_msg>Support nested map in isValueEmpty<commit_after>// db.Base extensions. Currently purely about base methods needed for JSON snapshots

'use strict';

var memoize  = require('memoizee/plain')
  , ensureDb = require('dbjs/valid-dbjs');

module.exports = memoize(function (db) {
	// JSON resolvers, used to retrieve serializable data snapshots
	ensureDb(db).Base.prototype.__descriptorPrototype__.setProperties({
		labelToJSON: function (ignore) {
			return this.dynamicLabelKey ? this.object.get(this.dynamicLabelKey) : this.label;
		},
		valueToJSON: function (ignore) {
			var value = this.object.get(this.key), result;
			if (value && value.forEach && !this.nested && this.multiple) {
				result = [];
				value.forEach(function (value) { result.push(this.type.valueToJSON(value, this)); }, this);
				return result;
			}
			return this.type.valueToJSON(value, this);
		},
		fieldToJSON: function (ignore) {
			return {
				label: this.labelToJSON(),
				value: this.valueToJSON(),
				id: this.__valueId__
			};
		}
	});

	db.Base.prototype.defineProperties({
		isEmpty: { type: db.Function },
		toJSON: { type: db.Function }
	});

	return db.Base.defineProperties({
		// Whether provided value should be considered empty
		isValueEmpty: { type: db.Function, value: function (value) {
			if (value == null) return true;
			if (value.owner && (value.key === 'map')) {
				var NestedMap = this.database.NestedMap;
				if (NestedMap && (value.owner instanceof NestedMap)) return value.owner.isEmpty();
			}
			if (typeof value.isEmpty !== 'function') return false;
			return value.isEmpty();
		} },
		// Returns JSON representation of provided value (in many cases it's same as value)
		valueToJSON: { type: db.Function, value: function (value, descriptor) {
			if (value == null) return value;
			if (this.database.isObjectType(this)) {
				if (typeof value.toJSON === 'function') return value.toJSON(descriptor);
				return value.toString(descriptor);
			}
			return (new this(value)).toString(descriptor);
		} }
	});
}, { normalizer: require('memoizee/normalizers/get-1')() });
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 iWARES Solution Provider
 *
 * 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.
 *
 */

/**
 * @file	staple.js
 * @author	Eric.Tsai
 *
 */

(function() {

// Collect all meta data.
var metaTags = window.document.head.querySelectorAll("meta");
var meta = { };
for (var i = 0, tag; tag = metaTags[i]; ++i)
	meta[tag.name] = tag.content;
window.document.head.meta = meta;

// Build a query string which will be used to bust the http cache.
var bust = sessionStorage && sessionStorage['$bust'];

if (!bust) {
	bust = location.search.match(/[\?\&]_bust=([^\&]+)/i);
	bust = (bust && bust.length > 1) ? decodeURIComponent(bust[1]) : meta['version'];
	if (bust == 'timestamp')
		bust = (new Date()).getTime();
	try {
		sessionStorage['$bust'] = bust;
	} catch (e) {
		// Do nothing.
	}
}

// Config bust.
requirejs.config({ urlArgs: '_bust=' + (bust || ''), waitSeconds: 15 });

// Load noumenon of the framework.
require(['noumenon'], function (noumenon) { });

})();
<commit_msg>using meta "version-code" instead of "version" as bust.<commit_after>/*
 * Copyright (C) 2015 iWARES Solution Provider
 *
 * 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.
 *
 */

/**
 * @file	staple.js
 * @author	Eric.Tsai
 *
 */

(function() {

// Collect all meta data.
var metaTags = window.document.head.querySelectorAll("meta");
var meta = { };
for (var i = 0, tag; tag = metaTags[i]; ++i)
	meta[tag.name] = tag.content;
window.document.head.meta = meta;

// Build a query string which will be used to bust the http cache.
var bust = sessionStorage && sessionStorage['$bust'];

if (!bust) {
	bust = location.search.match(/[\?\&]_bust=([^\&]+)/i);
	bust = (bust && bust.length > 1) ? decodeURIComponent(bust[1]) : meta['version-code'];
	if (bust == 'timestamp')
		bust = (new Date()).getTime();
	try {
		sessionStorage['$bust'] = bust;
	} catch (e) {
		// Do nothing.
	}
}

// Config bust.
requirejs.config({ urlArgs: bust ? '_bust=' + bust : undefined, waitSeconds: 15 });

// Load noumenon of the framework.
require(['noumenon'], function (noumenon) { });

})();
<|endoftext|>


<commit_before>/*
 *
 * Copyright 2015, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

'use strict';

var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var grpc = require('..');
var testProto = grpc.load(__dirname + '/test.proto').grpc.testing;
var GoogleAuth = require('google-auth-library');

var assert = require('assert');

var AUTH_SCOPE = 'https://www.googleapis.com/auth/xapi.zoo';
var AUTH_SCOPE_RESPONSE = 'xapi.zoo';
var AUTH_USER = ('155450119199-3psnrh1sdr3d8cpj1v46naggf81mhdnk' +
    '@developer.gserviceaccount.com');
var COMPUTE_ENGINE_USER = ('155450119199-r5aaqa2vqoa9g5mv2m6s3m1l293rlmel' +
    '@developer.gserviceaccount.com');

/**
 * Create a buffer filled with size zeroes
 * @param {number} size The length of the buffer
 * @return {Buffer} The new buffer
 */
function zeroBuffer(size) {
  var zeros = new Buffer(size);
  zeros.fill(0);
  return zeros;
}

/**
 * Run the empty_unary test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function emptyUnary(client, done) {
  var call = client.emptyCall({}, function(err, resp) {
    assert.ifError(err);
  });
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
}

/**
 * Run the large_unary test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function largeUnary(client, done) {
  var arg = {
    response_type: 'COMPRESSABLE',
    response_size: 314159,
    payload: {
      body: zeroBuffer(271828)
    }
  };
  var call = client.unaryCall(arg, function(err, resp) {
    assert.ifError(err);
    assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
    assert.strictEqual(resp.payload.body.length, 314159);
  });
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
}

/**
 * Run the client_streaming test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function clientStreaming(client, done) {
  var call = client.streamingInputCall(function(err, resp) {
    assert.ifError(err);
    assert.strictEqual(resp.aggregated_payload_size, 74922);
  });
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
  var payload_sizes = [27182, 8, 1828, 45904];
  for (var i = 0; i < payload_sizes.length; i++) {
    call.write({payload: {body: zeroBuffer(payload_sizes[i])}});
  }
  call.end();
}

/**
 * Run the server_streaming test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function serverStreaming(client, done) {
  var arg = {
    response_type: 'COMPRESSABLE',
    response_parameters: [
      {size: 31415},
      {size: 9},
      {size: 2653},
      {size: 58979}
    ]
  };
  var call = client.streamingOutputCall(arg);
  var resp_index = 0;
  call.on('data', function(value) {
    assert(resp_index < 4);
    assert.strictEqual(value.payload.type, 'COMPRESSABLE');
    assert.strictEqual(value.payload.body.length,
                       arg.response_parameters[resp_index].size);
    resp_index += 1;
  });
  call.on('end', function() {
    assert.strictEqual(resp_index, 4);
    if (done) {
      done();
    }
  });
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
  });
}

/**
 * Run the ping_pong test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function pingPong(client, done) {
  var payload_sizes = [27182, 8, 1828, 45904];
  var response_sizes = [31415, 9, 2653, 58979];
  var call = client.fullDuplexCall();
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
  var index = 0;
  call.write({
      response_type: 'COMPRESSABLE',
      response_parameters: [
        {size: response_sizes[index]}
      ],
      payload: {body: zeroBuffer(payload_sizes[index])}
  });
  call.on('data', function(response) {
    assert.strictEqual(response.payload.type, 'COMPRESSABLE');
    assert.equal(response.payload.body.length, response_sizes[index]);
    index += 1;
    if (index === 4) {
      call.end();
    } else {
      call.write({
        response_type: 'COMPRESSABLE',
        response_parameters: [
          {size: response_sizes[index]}
        ],
        payload: {body: zeroBuffer(payload_sizes[index])}
      });
    }
  });
}

/**
 * Run the empty_stream test.
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function emptyStream(client, done) {
  var call = client.fullDuplexCall();
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
  call.on('data', function(value) {
    assert.fail(value, null, 'No data should have been received', '!==');
  });
  call.end();
}

/**
 * Run the cancel_after_begin test.
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function cancelAfterBegin(client, done) {
  var call = client.streamingInputCall(function(err, resp) {
    assert.strictEqual(err.code, grpc.status.CANCELLED);
    done();
  });
  call.cancel();
}

/**
 * Run the cancel_after_first_response test.
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function cancelAfterFirstResponse(client, done) {
  var call = client.fullDuplexCall();
  call.write({
      response_type: 'COMPRESSABLE',
      response_parameters: [
        {size: 31415}
      ],
      payload: {body: zeroBuffer(27182)}
  });
  call.on('data', function(data) {
    call.cancel();
  });
  call.on('error', function(error) {
    assert.strictEqual(error.code, grpc.status.CANCELLED);
    done();
  });
}

function timeoutOnSleepingServer(client, done) {
  var deadline = new Date();
  deadline.setMilliseconds(deadline.getMilliseconds() + 1);
  var call = client.fullDuplexCall(null, deadline);
  call.write({
    payload: {body: zeroBuffer(27182)}
  });
  call.on('error', function(error) {
    assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED);
    done();
  });
}

/**
 * Run one of the authentication tests.
 * @param {string} expected_user The expected username in the response
 * @param {Client} client The client to test against
 * @param {?string} scope The scope to apply to the credentials
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function authTest(expected_user, scope, client, done) {
  (new GoogleAuth()).getApplicationDefault(function(err, credential) {
    assert.ifError(err);
    if (credential.createScopedRequired() && scope) {
      credential = credential.createScoped(scope);
    }
    client.updateMetadata = grpc.getGoogleAuthDelegate(credential);
    var arg = {
      response_type: 'COMPRESSABLE',
      response_size: 314159,
      payload: {
        body: zeroBuffer(271828)
      },
      fill_username: true,
      fill_oauth_scope: true
    };
    var call = client.unaryCall(arg, function(err, resp) {
      assert.ifError(err);
      assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
      assert.strictEqual(resp.payload.body.length, 314159);
      assert.strictEqual(resp.username, expected_user);
      assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
    });
    call.on('status', function(status) {
      assert.strictEqual(status.code, grpc.status.OK);
      if (done) {
        done();
      }
    });
  });
}

/**
 * Map from test case names to test functions
 */
var test_cases = {
  empty_unary: emptyUnary,
  large_unary: largeUnary,
  client_streaming: clientStreaming,
  server_streaming: serverStreaming,
  ping_pong: pingPong,
  empty_stream: emptyStream,
  cancel_after_begin: cancelAfterBegin,
  cancel_after_first_response: cancelAfterFirstResponse,
  timeout_on_sleeping_server: timeoutOnSleepingServer,
  compute_engine_creds: _.partial(authTest, COMPUTE_ENGINE_USER, null),
  service_account_creds: _.partial(authTest, AUTH_USER, AUTH_SCOPE),
  jwt_token_creds: _.partial(authTest, AUTH_USER, null)
};

/**
 * Execute a single test case.
 * @param {string} address The address of the server to connect to, in the
 *     format 'hostname:port'
 * @param {string} host_overrirde The hostname of the server to use as an SSL
 *     override
 * @param {string} test_case The name of the test case to run
 * @param {bool} tls Indicates that a secure channel should be used
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function runTest(address, host_override, test_case, tls, test_ca, done) {
  // TODO(mlumish): enable TLS functionality
  var options = {};
  if (tls) {
    var ca_path;
    if (test_ca) {
      ca_path = path.join(__dirname, '../test/data/ca.pem');
    } else {
      ca_path = process.env.SSL_CERT_FILE;
    }
    var ca_data = fs.readFileSync(ca_path);
    var creds = grpc.Credentials.createSsl(ca_data);
    options.credentials = creds;
    if (host_override) {
      options['grpc.ssl_target_name_override'] = host_override;
    }
  }
  var client = new testProto.TestService(address, options);

  test_cases[test_case](client, done);
}

if (require.main === module) {
  var parseArgs = require('minimist');
  var argv = parseArgs(process.argv, {
    string: ['server_host', 'server_host_override', 'server_port', 'test_case',
             'use_tls', 'use_test_ca']
  });
  runTest(argv.server_host + ':' + argv.server_port, argv.server_host_override,
          argv.test_case, argv.use_tls === 'true', argv.use_test_ca === 'true',
          function () {
            console.log('OK:', argv.test_case);
          });
}

/**
 * See docs for runTest
 */
exports.runTest = runTest;
<commit_msg>Added oauth2_auth_token and per_rpc_creds Node interop tests<commit_after>/*
 *
 * Copyright 2015, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

'use strict';

var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var grpc = require('..');
var testProto = grpc.load(__dirname + '/test.proto').grpc.testing;
var GoogleAuth = require('google-auth-library');

var assert = require('assert');

var AUTH_SCOPE = 'https://www.googleapis.com/auth/xapi.zoo';
var AUTH_SCOPE_RESPONSE = 'xapi.zoo';
var AUTH_USER = ('155450119199-3psnrh1sdr3d8cpj1v46naggf81mhdnk' +
    '@developer.gserviceaccount.com');
var COMPUTE_ENGINE_USER = ('155450119199-r5aaqa2vqoa9g5mv2m6s3m1l293rlmel' +
    '@developer.gserviceaccount.com');

/**
 * Create a buffer filled with size zeroes
 * @param {number} size The length of the buffer
 * @return {Buffer} The new buffer
 */
function zeroBuffer(size) {
  var zeros = new Buffer(size);
  zeros.fill(0);
  return zeros;
}

/**
 * Run the empty_unary test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function emptyUnary(client, done) {
  var call = client.emptyCall({}, function(err, resp) {
    assert.ifError(err);
  });
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
}

/**
 * Run the large_unary test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function largeUnary(client, done) {
  var arg = {
    response_type: 'COMPRESSABLE',
    response_size: 314159,
    payload: {
      body: zeroBuffer(271828)
    }
  };
  var call = client.unaryCall(arg, function(err, resp) {
    assert.ifError(err);
    assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
    assert.strictEqual(resp.payload.body.length, 314159);
  });
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
}

/**
 * Run the client_streaming test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function clientStreaming(client, done) {
  var call = client.streamingInputCall(function(err, resp) {
    assert.ifError(err);
    assert.strictEqual(resp.aggregated_payload_size, 74922);
  });
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
  var payload_sizes = [27182, 8, 1828, 45904];
  for (var i = 0; i < payload_sizes.length; i++) {
    call.write({payload: {body: zeroBuffer(payload_sizes[i])}});
  }
  call.end();
}

/**
 * Run the server_streaming test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function serverStreaming(client, done) {
  var arg = {
    response_type: 'COMPRESSABLE',
    response_parameters: [
      {size: 31415},
      {size: 9},
      {size: 2653},
      {size: 58979}
    ]
  };
  var call = client.streamingOutputCall(arg);
  var resp_index = 0;
  call.on('data', function(value) {
    assert(resp_index < 4);
    assert.strictEqual(value.payload.type, 'COMPRESSABLE');
    assert.strictEqual(value.payload.body.length,
                       arg.response_parameters[resp_index].size);
    resp_index += 1;
  });
  call.on('end', function() {
    assert.strictEqual(resp_index, 4);
    if (done) {
      done();
    }
  });
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
  });
}

/**
 * Run the ping_pong test
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function pingPong(client, done) {
  var payload_sizes = [27182, 8, 1828, 45904];
  var response_sizes = [31415, 9, 2653, 58979];
  var call = client.fullDuplexCall();
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
  var index = 0;
  call.write({
      response_type: 'COMPRESSABLE',
      response_parameters: [
        {size: response_sizes[index]}
      ],
      payload: {body: zeroBuffer(payload_sizes[index])}
  });
  call.on('data', function(response) {
    assert.strictEqual(response.payload.type, 'COMPRESSABLE');
    assert.equal(response.payload.body.length, response_sizes[index]);
    index += 1;
    if (index === 4) {
      call.end();
    } else {
      call.write({
        response_type: 'COMPRESSABLE',
        response_parameters: [
          {size: response_sizes[index]}
        ],
        payload: {body: zeroBuffer(payload_sizes[index])}
      });
    }
  });
}

/**
 * Run the empty_stream test.
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function emptyStream(client, done) {
  var call = client.fullDuplexCall();
  call.on('status', function(status) {
    assert.strictEqual(status.code, grpc.status.OK);
    if (done) {
      done();
    }
  });
  call.on('data', function(value) {
    assert.fail(value, null, 'No data should have been received', '!==');
  });
  call.end();
}

/**
 * Run the cancel_after_begin test.
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function cancelAfterBegin(client, done) {
  var call = client.streamingInputCall(function(err, resp) {
    assert.strictEqual(err.code, grpc.status.CANCELLED);
    done();
  });
  call.cancel();
}

/**
 * Run the cancel_after_first_response test.
 * @param {Client} client The client to test against
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function cancelAfterFirstResponse(client, done) {
  var call = client.fullDuplexCall();
  call.write({
      response_type: 'COMPRESSABLE',
      response_parameters: [
        {size: 31415}
      ],
      payload: {body: zeroBuffer(27182)}
  });
  call.on('data', function(data) {
    call.cancel();
  });
  call.on('error', function(error) {
    assert.strictEqual(error.code, grpc.status.CANCELLED);
    done();
  });
}

function timeoutOnSleepingServer(client, done) {
  var deadline = new Date();
  deadline.setMilliseconds(deadline.getMilliseconds() + 1);
  var call = client.fullDuplexCall(null, deadline);
  call.write({
    payload: {body: zeroBuffer(27182)}
  });
  call.on('error', function(error) {
    assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED);
    done();
  });
}

/**
 * Run one of the authentication tests.
 * @param {string} expected_user The expected username in the response
 * @param {Client} client The client to test against
 * @param {?string} scope The scope to apply to the credentials
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function authTest(expected_user, scope, client, done) {
  (new GoogleAuth()).getApplicationDefault(function(err, credential) {
    assert.ifError(err);
    if (credential.createScopedRequired() && scope) {
      credential = credential.createScoped(scope);
    }
    client.updateMetadata = grpc.getGoogleAuthDelegate(credential);
    var arg = {
      response_type: 'COMPRESSABLE',
      response_size: 314159,
      payload: {
        body: zeroBuffer(271828)
      },
      fill_username: true,
      fill_oauth_scope: true
    };
    var call = client.unaryCall(arg, function(err, resp) {
      assert.ifError(err);
      assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
      assert.strictEqual(resp.payload.body.length, 314159);
      assert.strictEqual(resp.username, expected_user);
      assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
    });
    call.on('status', function(status) {
      assert.strictEqual(status.code, grpc.status.OK);
      if (done) {
        done();
      }
    });
  });
}

function oauth2Test(expected_user, scope, per_rpc, client, done) {
  (new GoogleAuth()).getApplicationDefault(function(err, credential) {
    assert.ifError(err);
    var arg = {
      response_type: 'COMPRESSABLE',
      response_size: 314159,
      payload: {
        body: zeroBuffer(271828)
      },
      fill_username: true,
      fill_oauth_scope: true
    };
    credential = credential.createScoped(scope);
    credential.getAccessToken(function(err, token) {
      assert.ifError(err);
      var updateMetadata = function(authURI, metadata, callback) {
        metadata = _.clone(metadata);
        if (metadata.Authorization) {
          metadata.Authorization = _.clone(metadata.Authorization);
        } else {
          metadata.Authorization = [];
        }
        metadata.Authorization.push('Bearer ' + token);
        callback(null, metadata);
      };
      var makeTestCall = function(error, client_metadata) {
        assert.ifError(error);
        var call = client.unaryCall(arg, function(err, resp) {
          assert.ifError(err);
          assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
          assert.strictEqual(resp.payload.body.length, 314159);
          assert.strictEqual(resp.username, expected_user);
          assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
        });
        call.on('status', function(status) {
          assert.strictEqual(status.code, grpc.status.OK);
          if (done) {
            done();
          }
        });
      };
      if (per_rpc) {
        updateMetadata('', {}, makeTestCall);
      } else {
        client.updateMetadata = updateMetadata;
        makeTestCall(null, {});
      }

    });
  });
}

/**
 * Map from test case names to test functions
 */
var test_cases = {
  empty_unary: emptyUnary,
  large_unary: largeUnary,
  client_streaming: clientStreaming,
  server_streaming: serverStreaming,
  ping_pong: pingPong,
  empty_stream: emptyStream,
  cancel_after_begin: cancelAfterBegin,
  cancel_after_first_response: cancelAfterFirstResponse,
  timeout_on_sleeping_server: timeoutOnSleepingServer,
  compute_engine_creds: _.partial(authTest, COMPUTE_ENGINE_USER, null),
  service_account_creds: _.partial(authTest, AUTH_USER, AUTH_SCOPE),
  jwt_token_creds: _.partial(authTest, AUTH_USER, null),
  oauth2_auth_token: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, false),
  per_rpc_creds: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, true)
};

/**
 * Execute a single test case.
 * @param {string} address The address of the server to connect to, in the
 *     format 'hostname:port'
 * @param {string} host_overrirde The hostname of the server to use as an SSL
 *     override
 * @param {string} test_case The name of the test case to run
 * @param {bool} tls Indicates that a secure channel should be used
 * @param {function} done Callback to call when the test is completed. Included
 *     primarily for use with mocha
 */
function runTest(address, host_override, test_case, tls, test_ca, done) {
  // TODO(mlumish): enable TLS functionality
  var options = {};
  if (tls) {
    var ca_path;
    if (test_ca) {
      ca_path = path.join(__dirname, '../test/data/ca.pem');
    } else {
      ca_path = process.env.SSL_CERT_FILE;
    }
    var ca_data = fs.readFileSync(ca_path);
    var creds = grpc.Credentials.createSsl(ca_data);
    options.credentials = creds;
    if (host_override) {
      options['grpc.ssl_target_name_override'] = host_override;
    }
  }
  var client = new testProto.TestService(address, options);

  test_cases[test_case](client, done);
}

if (require.main === module) {
  var parseArgs = require('minimist');
  var argv = parseArgs(process.argv, {
    string: ['server_host', 'server_host_override', 'server_port', 'test_case',
             'use_tls', 'use_test_ca']
  });
  runTest(argv.server_host + ':' + argv.server_port, argv.server_host_override,
          argv.test_case, argv.use_tls === 'true', argv.use_test_ca === 'true',
          function () {
            console.log('OK:', argv.test_case);
          });
}

/**
 * See docs for runTest
 */
exports.runTest = runTest;
<|endoftext|>


<commit_before>const ReactDOMServer = require('react-dom/server');
const detectDevice = require('../utils/detectDevice');
const detectLocale = require('../utils/detectLocale');
const generateHtmlSnippets = require('../utils/generateHtmlSnippets');
const getCsrfToken = require('../utils/getCsrfToken');
const getLocaleFromCookie = require('../utils/getLocaleFromCookie');
const getLocaleFromHeader = require('../utils/getLocaleFromHeader');
const getLocaleFromUrl = require('../utils/getLocaleFromUrl');
const paths = require('../../config/paths');

module.exports = function createAppOnServer(config) {
  return (req, res) => {
    const { cookies } = req;

    // try to find locale from url, header and cookies
    const localeFromUrl = getLocaleFromUrl(req.originalUrl, config.app.locale.supported);
    const localeFromHeader = getLocaleFromHeader(
      req.headers['accept-language'],
      config.app.locale.supported,
    );
    const localeFromCookie = getLocaleFromCookie(req.cookies.lang, config.app.locale.supported);

    // (auto) detect locale
    const locale = detectLocale(
      config.app.locale.default,
      localeFromUrl,
      localeFromHeader,
      localeFromCookie,
      config.app.locale.autoDetect,
    );

    // if cookies are enabled, set language cookie
    if (config.cookies && !localeFromCookie) {
      // Add lang cookie to cookies object and to response
      cookies.lang = locale;
      res.cookie('lang', locale, { maxAge: 2628000 * 60 * 1000 }); // 5 years lifetime
    }

    // (auto) detect device
    const device = detectDevice(
      req.headers['user-agent'],
      req.cookies.view,
      config.app.device.autoDetect,
    );

    // set csrf token
    const csrfToken = getCsrfToken(req.cookies.csrf, config.app.csrfToken);

    // if cookies and csrf token are enabled, set csrf token cookie
    if (config.cookies && config.app.csrfToken && req.cookies.csrf === undefined) {
      // Add lang cookie to cookies object and to response
      cookies.csrf = csrfToken;
      res.cookie('csrf', csrfToken, { httpOnly: true });
    }

    // create meta data object
    const meta = {
      ssr: config.ssr,
      url: req.url,
      hostname: req.hostname,
      csrfToken,
      device,
      locale: {
        supported: config.app.locale.supported,
        default: config.app.locale.default,
        current: locale,
        sources: {
          url: localeFromUrl,
          header: localeFromHeader,
          cookie: localeFromCookie,
        },
      },
    };

    const metaServerOnly = {
      cookies,
    };

    // Do not cache webpack-manifest.json and all files in /app folder in development
    // See https://github.com/ProAI/react-kickstarter/issues/4
    if (process.env.APP_MODE === 'development') {
      delete require.cache[paths.webpackManifest];

      Object.keys(require.cache).forEach(id => {
        if (/[/\\]app[/\\]/.test(id)) delete require.cache[id];
      });
    }

    // define render, redirect and error function for hydrate function
    const render = (component, getData, ...args) => {
      // eslint-disable-next-line
      const assets = require(paths.webpackManifest);
      const content = component ? ReactDOMServer.renderToString(component) : '';
      // eslint-disable-next-line
      const renderHtml = require(paths.appHtml).default;

      const htmlSnippets = generateHtmlSnippets(
        meta,
        content,
        assets,
        getData ? getData() : {},
        config.devBuild.dll,
      );

      const html = renderHtml(meta, htmlSnippets, ...args);

      res.status(200).send(html);
    };
    const redirect = path => {
      res.redirect(path);
    };
    const error = message => {
      res.status(404);
      if (message) {
        res.send(message);
      }
    };

    // Render page on client if server side rendering is disabled.
    // Initial state should be empty in this case.
    if (!config.ssr) {
      render();
      return;
    }

    // get hydrate function and hydrate
    // eslint-disable-next-line
    const hydrate = require(paths.appServerEntry).default;
    hydrate({ ...metaServerOnly, ...meta }, { error, redirect, render });
  };
};
<commit_msg>Add mockHostname to meta<commit_after>const ReactDOMServer = require('react-dom/server');
const detectDevice = require('../utils/detectDevice');
const detectLocale = require('../utils/detectLocale');
const generateHtmlSnippets = require('../utils/generateHtmlSnippets');
const getCsrfToken = require('../utils/getCsrfToken');
const getLocaleFromCookie = require('../utils/getLocaleFromCookie');
const getLocaleFromHeader = require('../utils/getLocaleFromHeader');
const getLocaleFromUrl = require('../utils/getLocaleFromUrl');
const paths = require('../../config/paths');

module.exports = function createAppOnServer(config) {
  return (req, res) => {
    const { cookies } = req;

    // try to find locale from url, header and cookies
    const localeFromUrl = getLocaleFromUrl(req.originalUrl, config.app.locale.supported);
    const localeFromHeader = getLocaleFromHeader(
      req.headers['accept-language'],
      config.app.locale.supported,
    );
    const localeFromCookie = getLocaleFromCookie(req.cookies.lang, config.app.locale.supported);

    // (auto) detect locale
    const locale = detectLocale(
      config.app.locale.default,
      localeFromUrl,
      localeFromHeader,
      localeFromCookie,
      config.app.locale.autoDetect,
    );

    // if cookies are enabled, set language cookie
    if (config.cookies && !localeFromCookie) {
      // Add lang cookie to cookies object and to response
      cookies.lang = locale;
      res.cookie('lang', locale, { maxAge: 2628000 * 60 * 1000 }); // 5 years lifetime
    }

    // (auto) detect device
    const device = detectDevice(
      req.headers['user-agent'],
      req.cookies.view,
      config.app.device.autoDetect,
    );

    // set csrf token
    const csrfToken = getCsrfToken(req.cookies.csrf, config.app.csrfToken);

    // if cookies and csrf token are enabled, set csrf token cookie
    if (config.cookies && config.app.csrfToken && req.cookies.csrf === undefined) {
      // Add lang cookie to cookies object and to response
      cookies.csrf = csrfToken;
      res.cookie('csrf', csrfToken, { httpOnly: true });
    }

    // create meta data object
    const meta = {
      ssr: config.ssr,
      url: req.url,
      hostname: req.hostname,
      mockHostname: process.env.MOCK_HOST,
      csrfToken,
      device,
      locale: {
        supported: config.app.locale.supported,
        default: config.app.locale.default,
        current: locale,
        sources: {
          url: localeFromUrl,
          header: localeFromHeader,
          cookie: localeFromCookie,
        },
      },
    };

    const metaServerOnly = {
      cookies,
    };

    // Do not cache webpack-manifest.json and all files in /app folder in development
    // See https://github.com/ProAI/react-kickstarter/issues/4
    if (process.env.APP_MODE === 'development') {
      delete require.cache[paths.webpackManifest];

      Object.keys(require.cache).forEach(id => {
        if (/[/\\]app[/\\]/.test(id)) delete require.cache[id];
      });
    }

    // define render, redirect and error function for hydrate function
    const render = (component, getData, ...args) => {
      // eslint-disable-next-line
      const assets = require(paths.webpackManifest);
      const content = component ? ReactDOMServer.renderToString(component) : '';
      // eslint-disable-next-line
      const renderHtml = require(paths.appHtml).default;

      const htmlSnippets = generateHtmlSnippets(
        meta,
        content,
        assets,
        getData ? getData() : {},
        config.devBuild.dll,
      );

      const html = renderHtml(meta, htmlSnippets, ...args);

      res.status(200).send(html);
    };
    const redirect = path => {
      res.redirect(path);
    };
    const error = message => {
      res.status(404);
      if (message) {
        res.send(message);
      }
    };

    // Render page on client if server side rendering is disabled.
    // Initial state should be empty in this case.
    if (!config.ssr) {
      render();
      return;
    }

    // get hydrate function and hydrate
    // eslint-disable-next-line
    const hydrate = require(paths.appServerEntry).default;
    hydrate({ ...metaServerOnly, ...meta }, { error, redirect, render });
  };
};
<|endoftext|>


<commit_before>'use strict'

const Events = require('events')
const fs = require('fs')
const path = require('path')

const volumes = getVolumes()

module.exports = class Garmin extends Events {
  startWatching() {
    this.update()
    fs.watch(volumes, {persistent: true, recursive: false}, () => this.update())
  }

  update() {
    findDevices()
      .then(devices => this.setDevices(devices))
      .then(devices => Promise.all(devices.map(findTracks)))
      .then(deviceTracks => sortTracks(deviceTracks))
      .then(tracks => this.setTracks(tracks))
      .catch(error => console.error(error))
  }

  setDevices(devices) {
    this.devices = devices
    return devices
  }

  setTracks(tracks) {
    // Falsey tracks means no device connected
    // empty array means no track on the device
    if (this.devices && this.devices.length)
      this.tracks = tracks
    else
      this.tracks = null
    this.emit('update', tracks)
  }
}

function findDevices() {
  return readdir(volumes)
    .then(devices => Promise.all([devices, Promise.all(devices.map(isGarmin))]))
    .then(results => {
      const devices = results[0]
      const isGarmin = results[1]
      return devices.filter((device, i) => isGarmin[i])
    })
}

function findTracks(device) {
  const gpxDir = path.join(device, 'Garmin', 'GPX')
  return readdir(gpxDir)
    .then(entries => Promise.all([entries, Promise.all(entries.map(stat))]))
    .then(results => {
      const entries = results[0]
      const stat = results[1]
      return entries
        .map((entry, i) => ({path: entry, stat: stat[i]}))
        .filter(entry => entry.stat.isFile())
        .map(entry => ({path: entry.path, birthtime: entry.stat.birthtime}))
    })
}

function sortTracks(deviceTracks) {
  return deviceTracks
    .reduce((a, b) => a.concat(b), [])
    .sort((a, b) => b.birthtime.getTime() - a.birthtime.getTime())
}

function isGarmin(device) {
  return containsChild(device, 'Garmin')
    .then(isGarmin => {
      return isGarmin && containsChild(path.join(device, 'Garmin'), 'GPX')
    })
}

function containsChild(parent, child) {
  return stat(parent)
    .then(stat => { console.log('isDirectory', parent, stat.isDirectory()); return stat })
    .then(stat => stat.isDirectory() && retryReaddir(parent))
    .then(entries => {
      console.log('Entries for', parent, entries)
      return entries && entries.some(entry => path.basename(entry) == child)
    })
    .then(val => {console.log('containsChild', parent, child, val); return val})
}

function readdir(dir) {
  return new Promise((resolve, reject) => {
    fs.readdir(dir, (err, result) => {
      if (err)
        reject(err)
      else
        resolve(result.map(entry => path.join(dir, entry)))
    })
  })
}

function stat(path) {
  return new Promise((resolve, reject) => {
    fs.stat(path, (err, result) => {
      if (err)
        reject(err)
      else
        resolve(result)
    })
  })
}

function retry(fn, count, isRetriable) {
  return fn().catch(e => {
    if (count > 0 && isRetriable(e))
      return sleep(100, () => retry(fn, count - 1, isRetriable))
    else
      return Promise.reject(e)
  })
}

function sleep(time, fn) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      fn().then(resolve, reject)
    }, time)
  })
}

function retryReaddir(path) {
  // For some reason permissions are not set up right after a device is connected
  return retry(() => readdir(path), 10, e => e.code == 'EACCES')
}

function getVolumes() {
  if (process.platform == 'darwin')
    return '/Volumes'
  else
    return '/media/' + process.env.USER
}
<commit_msg>Increase readdir retry timeout<commit_after>'use strict'

const Events = require('events')
const fs = require('fs')
const path = require('path')

const volumes = getVolumes()

module.exports = class Garmin extends Events {
  startWatching() {
    this.update()
    fs.watch(volumes, {persistent: true, recursive: false}, () => this.update())
  }

  update() {
    findDevices()
      .then(devices => this.setDevices(devices))
      .then(devices => Promise.all(devices.map(findTracks)))
      .then(deviceTracks => sortTracks(deviceTracks))
      .then(tracks => this.setTracks(tracks))
      .catch(error => console.error(error))
  }

  setDevices(devices) {
    this.devices = devices
    return devices
  }

  setTracks(tracks) {
    // Falsey tracks means no device connected
    // empty array means no track on the device
    if (this.devices && this.devices.length)
      this.tracks = tracks
    else
      this.tracks = null
    this.emit('update', tracks)
  }
}

function findDevices() {
  return readdir(volumes)
    .then(devices => Promise.all([devices, Promise.all(devices.map(isGarmin))]))
    .then(results => {
      const devices = results[0]
      const isGarmin = results[1]
      return devices.filter((device, i) => isGarmin[i])
    })
}

function findTracks(device) {
  const gpxDir = path.join(device, 'Garmin', 'GPX')
  return readdir(gpxDir)
    .then(entries => Promise.all([entries, Promise.all(entries.map(stat))]))
    .then(results => {
      const entries = results[0]
      const stat = results[1]
      return entries
        .map((entry, i) => ({path: entry, stat: stat[i]}))
        .filter(entry => entry.stat.isFile())
        .map(entry => ({path: entry.path, birthtime: entry.stat.birthtime}))
    })
}

function sortTracks(deviceTracks) {
  return deviceTracks
    .reduce((a, b) => a.concat(b), [])
    .sort((a, b) => b.birthtime.getTime() - a.birthtime.getTime())
}

function isGarmin(device) {
  return containsChild(device, 'Garmin')
    .then(isGarmin => {
      return isGarmin && containsChild(path.join(device, 'Garmin'), 'GPX')
    })
}

function containsChild(parent, child) {
  return stat(parent)
    .then(stat => { console.log('isDirectory', parent, stat.isDirectory()); return stat })
    .then(stat => stat.isDirectory() && retryReaddir(parent))
    .then(entries => {
      console.log('Entries for', parent, entries)
      return entries && entries.some(entry => path.basename(entry) == child)
    })
    .then(val => {console.log('containsChild', parent, child, val); return val})
}

function readdir(dir) {
  return new Promise((resolve, reject) => {
    fs.readdir(dir, (err, result) => {
      if (err)
        reject(err)
      else
        resolve(result.map(entry => path.join(dir, entry)))
    })
  })
}

function stat(path) {
  return new Promise((resolve, reject) => {
    fs.stat(path, (err, result) => {
      if (err)
        reject(err)
      else
        resolve(result)
    })
  })
}

function retry(fn, count, isRetriable) {
  return fn().catch(e => {
    if (count > 0 && isRetriable(e))
      return sleep(100, () => retry(fn, count - 1, isRetriable))
    else
      return Promise.reject(e)
  })
}

function sleep(time, fn) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      fn().then(resolve, reject)
    }, time)
  })
}

function retryReaddir(path) {
  // For some reason permissions are not set up right after a device is connected
  return retry(() => readdir(path), 50, e => e.code == 'EACCES')
}

function getVolumes() {
  if (process.platform == 'darwin')
    return '/Volumes'
  else
    return '/media/' + process.env.USER
}
<|endoftext|>


<commit_before>/* global enchant, Event, Class, Sprite */

function main() {
    var DISPLAY_WIDTH = 360;
    var DISPLAY_HEIGHT = 360;

    // 表示領域をデバイスの中央に設定します
    (function () {
        var doc = document.documentElement;
        var width = doc.clientWidth;
        var height = doc.clientHeight;
        var canvasAspect = DISPLAY_WIDTH / DISPLAY_HEIGHT;
        var windowAspect = doc.clientWidth / doc.clientHeight;
        var bodyStyle = document.getElementsByTagName("body")[0].style;
        var newWidth = (canvasAspect < windowAspect) ? height * canvasAspect : width;
        var newHeight = (windowAspect < canvasAspect) ? width / canvasAspect : height;
        bodyStyle.width = newWidth + "px";
        bodyStyle.height = newHeight + "px";
    })();

    /**
     * 引数の basename ("00" など) を、実際のファイル名 ("sound/00.ogg" など) に変換します。
     * 各ブラウザがサポートしているフォーマットの違いを吸収するため、
     * IE と iOS については .mp3, その他のブラウザは .ogg 形式を返します。
     *
     * @param {type} name ファイルの basename
     * @returns {String} ファイル名
     */
    var getSoundFilename = (function () {
        var ext = null;
        var checkMp3ByBrowser = function (browser, useragent) {
            if (browser === "ie") {
                return true;
            }
            if (/iPhone/.test(useragent)) {
                return true;
            }
            if (/iPad/.test(useragent)) {
                return true;
            }
            return false;
        };
        var func = function (name) {
            if (!ext) {
                ext = checkMp3ByBrowser(enchant.ENV.BROWSER, navigator.userAgent) ? ".mp3" : ".ogg";
            }
            return "sound/" + name + ext;
        };
        return func;
    })();

    /**
     * 指定された名前に対応する Sound オブジェクトを返します。
     * @param {String} name
     * @returns {Sound}
     */
    var getSoundByName = function (name) {
        return core.assets[getSoundFilename(name)];
    };

    enchant();
    var core = new Core(DISPLAY_WIDTH, DISPLAY_HEIGHT);
    ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"].map(function (filename) {
        core.preload(getSoundFilename(filename));
    });
    core.preload("img/keys.png", "img/volume-base.png", "img/volume-button.png", "img/volume-level.png");
    core.fps = 60;
    core.onload = function () {
        var KEY_WIDTH = 40;
        var KEY_HEIGHT = 200;
        var KEY_BLACK_HEIGHT = 120;
        var KEYBOARD_LEFT = (DISPLAY_WIDTH / 2) - (4 * KEY_WIDTH);
        var KEYBOARD_TOP = (DISPLAY_HEIGHT / 2) - (KEY_HEIGHT / 2);
        var VOLUME_TOP = KEYBOARD_TOP + KEY_HEIGHT + 10;
        var VOLUME_WIDTH = 160;

        var volume = 0.5;

        var playSE = function (name) {
            var se = getSoundByName(name);
            switch (enchant.ENV.BROWSER) {
                case "firefox":
                    se.play();
                    se.volume = volume;
                    break;
                case "ie":
                    // IE 10 以下では clone() の負荷が高く遅延が目立つため、既存の Sound オブジェクトを再利用します。
                    // 既存の Sound が再生中の場合は一度 stop します。
                    if (/MSIE/.test(navigator.userAgent)) {
                        se.stop();
                        se.play();
                        se.volume = volume;
                    } else {
                        var newSE = se.clone();
                        newSE.play();
                        newSE.volume = volume;
                    }
                    break;
                default:
                    var newSE = se.clone();
                    newSE.play();
                    newSE.volume = volume;
                    break;
            }
        };

        var Note = Class.create(Sprite, {
            initialize: function (index, soundName) {
                Sprite.call(this, KEY_WIDTH, KEY_HEIGHT);
                this.image = core.assets["img/keys.png"];
                this.frame = (index % 2) ? 2 : 0;
                this.x = KEYBOARD_LEFT + (index * KEY_WIDTH / 2);
                this.y = KEYBOARD_TOP;
                this.soundName = soundName;
                this.isPressed = false;
            },
            push: function () {
                if (this.isPressed) {
                    return;
                }
                if (this.frame % 2 === 0) {
                    this.frame++;
                }
                playSE(this.soundName);
                this.isPressed = true;
            },
            release: function () {
                this.isPressed = false;
                if (this.frame % 2) {
                    this.frame--;
                }
            }
        });
        var noop = function () {};
        var blankNote = {push: noop, release: noop};

        var getDistance = function (x1, y1, x2, y2) {
            return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
        };
        var Finger = Class.create({
            initialize: function (x, y) {
                this.x = x;
                this.y = y;
                var n = this.getTouchingNote(x, y);
                n.push();
                this.note = n;
            },
            getTouchingNote: function (x, y) {
                var localX = x - KEYBOARD_LEFT;
                var whiteIndex = Math.floor(localX / KEY_WIDTH);
                if (whiteIndex < 0 || 8 <= whiteIndex) {
                    return blankNote;
                }
                if (KEYBOARD_TOP + KEY_BLACK_HEIGHT < y) {
                    return notes.white[whiteIndex];
                }
                var blackIndex = Math.floor((localX - (KEY_WIDTH / 2)) / KEY_WIDTH);
                var foundIndex = [0, 1, 3, 4, 5].indexOf(blackIndex);
                return (foundIndex !== -1) ? notes.black[foundIndex] : notes.white[whiteIndex];
            }
        });
        var nullFinger = {
            x: 0,
            y: 0,
            note: blankNote,
            getTouchingNote: function () {
                return blankNote;
            }
        };
        var Hand = (function () {
            var getFingerIndex = function (hand, x, y) {
                var distanceList = hand.fingers.map(function (f) {
                    return getDistance(x, y, f.x, f.y);
                });
                return distanceList.indexOf(Math.min.apply(null, distanceList));
            };
            return Class.create({
                initialize: function () {
                    this.fingers = [];
                },
                touch: function (x, y) {
                    var finger = new Finger(x, y);
                    this.fingers.push(finger);
                },
                swipe: function (x, y) {
                    var index = getFingerIndex(this, x, y);
                    if (index === -1) {
                        return;
                    }
                    var finger = this.fingers[index];
                    var currentNote = finger.note;
                    var newNote = finger.getTouchingNote(x, y);
                    if (currentNote === newNote) {
                        return;
                    }
                    if (newNote === blankNote) {
                        this.fingers.splice(index, 1);
                    } else {
                        finger.note = newNote;
                        newNote.push();
                    }
                    if (!this.isTouching(currentNote)) {
                        currentNote.release();
                    }
                },
                release: function (x, y) {
                    var index = getFingerIndex(this, x, y);
                    if (index === -1) {
                        return nullFinger;
                    }
                    var finger = this.fingers[index];
                    var note = finger.note;
                    this.fingers.splice(index, 1);
                    if (!this.isTouching(note)) {
                        note.release();
                    }
                    return finger;
                },
                isTouching: function (note) {
                    for (var i = 0; i < this.fingers.length; i++) {
                        if (this.fingers[i].note === note) {
                            return true;
                        }
                    }
                    return false;
                }
            });
        })();
        var hand = new Hand();

        /**
         * 最後に離された Finger です。(誤発火防止対策）
         * @type Finger
         */
        var previousFinger = nullFinger;

        /**
         * 最後にキーが離された時のフレーム数です。（誤発火防止対策）
         * @type Number
         */
        var lastReleasedTime = 0;

        var background = (function () {
            var sprite = new Sprite(DISPLAY_WIDTH, DISPLAY_HEIGHT);
            sprite.image = (function () {
                var surface = new Surface(DISPLAY_WIDTH, DISPLAY_HEIGHT);
                var context = surface.context;
                context.fillStyle = (function () {
                    var grad = context.createLinearGradient(0, 0, 0, DISPLAY_HEIGHT);
                    grad.addColorStop(0, "#666666");
                    grad.addColorStop(1, "#333333");
                    return grad;
                })();
                context.fillRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
                return surface;
            })();
            return sprite;
        })();

        var notes = (function () {
            var whiteArgs = {0: "00", 2: "02", 4: "04", 6: "05", 8: "07", 10: "09", 12: "11", 14: "12"};
            var blackArgs = {1: "01", 3: "03", 7: "06", 9: "08", 11: "10"};
            var notes = {white: [], black: []};
            for (var i in whiteArgs) {
                notes.white.push(new Note(i, whiteArgs[i]));
            }
            for (var i in blackArgs) {
                notes.black.push(new Note(i, blackArgs[i]));
            }
            return notes;
        })();

        var keyboard = (function () {
            var sprite = new Sprite(8 * KEY_WIDTH, KEY_HEIGHT);
            sprite.x = KEYBOARD_LEFT;
            sprite.y = KEYBOARD_TOP;

            /**
             * 発火したイベントが誤検知かどうかを判定します。
             * 同じ場所で短期間 (1 フレーム以内) に連打された場合は誤検知とみなします。
             *
             * @param {Event} e 検知したイベント
             * @returns {Boolean} 誤検知の場合は false, そうでない場合は true
             */
            var validateEvent = function (e) {
                var prevX = previousFinger.x;
                var prevY = previousFinger.y;
                if (5 < getDistance(e.x, e.y, prevX, prevY)) {
                    return true;
                }
                return (1 < sprite.age - lastReleasedTime);
            };

            sprite.addEventListener(Event.TOUCH_START, function (e) {
                if (!validateEvent(e)) {
                    return;
                }
                hand.touch(e.x, e.y);
            });
            sprite.addEventListener(Event.TOUCH_MOVE, function (e) {
                if (!validateEvent(e)) {
                    return;
                }
                hand.swipe(e.x, e.y);
            });
            sprite.addEventListener(Event.TOUCH_END, function (e) {
                if (!validateEvent(e)) {
                    return;
                }
                var finger = hand.release(e.x, e.y);
                previousFinger = finger;
                lastReleasedTime = sprite.age;
            });
            return sprite;
        })();
        var volumeBase = (function () {
            var width = VOLUME_WIDTH;
            var height = 40;
            var sprite = new Sprite(width, height);
            sprite.image = core.assets["img/volume-base.png"];
            sprite.x = (DISPLAY_WIDTH / 2) - (width / 2);
            sprite.y = VOLUME_TOP;
            return sprite;
        })();
        var volumeButton = (function () {
            var width = 64;
            var height = 64;
            var sprite = new Sprite(width, height);
            sprite.image = core.assets["img/volume-button.png"];
            sprite.x = (DISPLAY_WIDTH / 2) - (width / 2);
            sprite.y = VOLUME_TOP - 12;
            var baseHalf = VOLUME_WIDTH / 2;
            var buttonHalf = width / 2;
            var xMin = (DISPLAY_WIDTH / 2) - baseHalf;
            var xMax = (DISPLAY_WIDTH / 2) + baseHalf;
            var getVolumeLevelFrame = function (volume) {
                if (volume === 0) {
                    return 0;
                }
                if (volume < 0.15) {
                    return 1;
                }
                if (volume < 0.45) {
                    return 2;
                }
                if (volume < 0.7) {
                    return 3;
                }
                return 4;
            };
            sprite.addEventListener(Event.TOUCH_MOVE, function (e) {
                var x = e.x;
                var newX = Math.max(Math.min(x, xMax), xMin);
                this.x = newX - buttonHalf;
                volume = (newX - xMin) / (xMax - xMin);
                volumeLevel.frame = getVolumeLevelFrame(volume);
            });
            return sprite;
        })();
        var volumeLevel = (function () {
            var width = 64;
            var height = 64;
            var sprite = new Sprite(width, height);
            sprite.image = core.assets["img/volume-level.png"];
            sprite.x = (DISPLAY_WIDTH / 2) - (VOLUME_WIDTH / 2) - width;
            sprite.y = VOLUME_TOP - 12;
            sprite.frame = 3;
            return sprite;
        })();

        var scene = core.rootScene;
        scene.addChild(background);
        notes.white.map(function (key) {
            scene.addChild(key);
        });
        notes.black.map(function (key) {
            scene.addChild(key);
        });
        scene.addChild(keyboard);
        scene.addChild(volumeLevel);
        scene.addChild(volumeBase);
        scene.addChild(volumeButton);
    };
    core.start();
}

window.onload = main;
<commit_msg>Adjust alignment of volume control area<commit_after>/* global enchant, Event, Class, Sprite */

function main() {
    var DISPLAY_WIDTH = 360;
    var DISPLAY_HEIGHT = 360;

    // 表示領域をデバイスの中央に設定します
    (function () {
        var doc = document.documentElement;
        var width = doc.clientWidth;
        var height = doc.clientHeight;
        var canvasAspect = DISPLAY_WIDTH / DISPLAY_HEIGHT;
        var windowAspect = doc.clientWidth / doc.clientHeight;
        var bodyStyle = document.getElementsByTagName("body")[0].style;
        var newWidth = (canvasAspect < windowAspect) ? height * canvasAspect : width;
        var newHeight = (windowAspect < canvasAspect) ? width / canvasAspect : height;
        bodyStyle.width = newWidth + "px";
        bodyStyle.height = newHeight + "px";
    })();

    /**
     * 引数の basename ("00" など) を、実際のファイル名 ("sound/00.ogg" など) に変換します。
     * 各ブラウザがサポートしているフォーマットの違いを吸収するため、
     * IE と iOS については .mp3, その他のブラウザは .ogg 形式を返します。
     *
     * @param {type} name ファイルの basename
     * @returns {String} ファイル名
     */
    var getSoundFilename = (function () {
        var ext = null;
        var checkMp3ByBrowser = function (browser, useragent) {
            if (browser === "ie") {
                return true;
            }
            if (/iPhone/.test(useragent)) {
                return true;
            }
            if (/iPad/.test(useragent)) {
                return true;
            }
            return false;
        };
        var func = function (name) {
            if (!ext) {
                ext = checkMp3ByBrowser(enchant.ENV.BROWSER, navigator.userAgent) ? ".mp3" : ".ogg";
            }
            return "sound/" + name + ext;
        };
        return func;
    })();

    /**
     * 指定された名前に対応する Sound オブジェクトを返します。
     * @param {String} name
     * @returns {Sound}
     */
    var getSoundByName = function (name) {
        return core.assets[getSoundFilename(name)];
    };

    enchant();
    var core = new Core(DISPLAY_WIDTH, DISPLAY_HEIGHT);
    ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"].map(function (filename) {
        core.preload(getSoundFilename(filename));
    });
    core.preload("img/keys.png", "img/volume-base.png", "img/volume-button.png", "img/volume-level.png");
    core.fps = 60;
    core.onload = function () {
        var KEY_WIDTH = 40;
        var KEY_HEIGHT = 200;
        var KEY_BLACK_HEIGHT = 120;
        var KEYBOARD_LEFT = (DISPLAY_WIDTH / 2) - (4 * KEY_WIDTH);
        var KEYBOARD_TOP = (DISPLAY_HEIGHT / 2) - (KEY_HEIGHT / 2);
        var VOLUME_TOP = KEYBOARD_TOP + KEY_HEIGHT + 32;
        var VOLUME_WIDTH = 160;

        var volume = 0.5;

        var playSE = function (name) {
            var se = getSoundByName(name);
            switch (enchant.ENV.BROWSER) {
                case "firefox":
                    se.play();
                    se.volume = volume;
                    break;
                case "ie":
                    // IE 10 以下では clone() の負荷が高く遅延が目立つため、既存の Sound オブジェクトを再利用します。
                    // 既存の Sound が再生中の場合は一度 stop します。
                    if (/MSIE/.test(navigator.userAgent)) {
                        se.stop();
                        se.play();
                        se.volume = volume;
                    } else {
                        var newSE = se.clone();
                        newSE.play();
                        newSE.volume = volume;
                    }
                    break;
                default:
                    var newSE = se.clone();
                    newSE.play();
                    newSE.volume = volume;
                    break;
            }
        };

        var Note = Class.create(Sprite, {
            initialize: function (index, soundName) {
                Sprite.call(this, KEY_WIDTH, KEY_HEIGHT);
                this.image = core.assets["img/keys.png"];
                this.frame = (index % 2) ? 2 : 0;
                this.x = KEYBOARD_LEFT + (index * KEY_WIDTH / 2);
                this.y = KEYBOARD_TOP;
                this.soundName = soundName;
                this.isPressed = false;
            },
            push: function () {
                if (this.isPressed) {
                    return;
                }
                if (this.frame % 2 === 0) {
                    this.frame++;
                }
                playSE(this.soundName);
                this.isPressed = true;
            },
            release: function () {
                this.isPressed = false;
                if (this.frame % 2) {
                    this.frame--;
                }
            }
        });
        var noop = function () {};
        var blankNote = {push: noop, release: noop};

        var getDistance = function (x1, y1, x2, y2) {
            return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
        };
        var Finger = Class.create({
            initialize: function (x, y) {
                this.x = x;
                this.y = y;
                var n = this.getTouchingNote(x, y);
                n.push();
                this.note = n;
            },
            getTouchingNote: function (x, y) {
                var localX = x - KEYBOARD_LEFT;
                var whiteIndex = Math.floor(localX / KEY_WIDTH);
                if (whiteIndex < 0 || 8 <= whiteIndex) {
                    return blankNote;
                }
                if (KEYBOARD_TOP + KEY_BLACK_HEIGHT < y) {
                    return notes.white[whiteIndex];
                }
                var blackIndex = Math.floor((localX - (KEY_WIDTH / 2)) / KEY_WIDTH);
                var foundIndex = [0, 1, 3, 4, 5].indexOf(blackIndex);
                return (foundIndex !== -1) ? notes.black[foundIndex] : notes.white[whiteIndex];
            }
        });
        var nullFinger = {
            x: 0,
            y: 0,
            note: blankNote,
            getTouchingNote: function () {
                return blankNote;
            }
        };
        var Hand = (function () {
            var getFingerIndex = function (hand, x, y) {
                var distanceList = hand.fingers.map(function (f) {
                    return getDistance(x, y, f.x, f.y);
                });
                return distanceList.indexOf(Math.min.apply(null, distanceList));
            };
            return Class.create({
                initialize: function () {
                    this.fingers = [];
                },
                touch: function (x, y) {
                    var finger = new Finger(x, y);
                    this.fingers.push(finger);
                },
                swipe: function (x, y) {
                    var index = getFingerIndex(this, x, y);
                    if (index === -1) {
                        return;
                    }
                    var finger = this.fingers[index];
                    var currentNote = finger.note;
                    var newNote = finger.getTouchingNote(x, y);
                    if (currentNote === newNote) {
                        return;
                    }
                    if (newNote === blankNote) {
                        this.fingers.splice(index, 1);
                    } else {
                        finger.note = newNote;
                        newNote.push();
                    }
                    if (!this.isTouching(currentNote)) {
                        currentNote.release();
                    }
                },
                release: function (x, y) {
                    var index = getFingerIndex(this, x, y);
                    if (index === -1) {
                        return nullFinger;
                    }
                    var finger = this.fingers[index];
                    var note = finger.note;
                    this.fingers.splice(index, 1);
                    if (!this.isTouching(note)) {
                        note.release();
                    }
                    return finger;
                },
                isTouching: function (note) {
                    for (var i = 0; i < this.fingers.length; i++) {
                        if (this.fingers[i].note === note) {
                            return true;
                        }
                    }
                    return false;
                }
            });
        })();
        var hand = new Hand();

        /**
         * 最後に離された Finger です。(誤発火防止対策）
         * @type Finger
         */
        var previousFinger = nullFinger;

        /**
         * 最後にキーが離された時のフレーム数です。（誤発火防止対策）
         * @type Number
         */
        var lastReleasedTime = 0;

        var background = (function () {
            var sprite = new Sprite(DISPLAY_WIDTH, DISPLAY_HEIGHT);
            sprite.image = (function () {
                var surface = new Surface(DISPLAY_WIDTH, DISPLAY_HEIGHT);
                var context = surface.context;
                context.fillStyle = (function () {
                    var grad = context.createLinearGradient(0, 0, 0, DISPLAY_HEIGHT);
                    grad.addColorStop(0, "#666666");
                    grad.addColorStop(1, "#333333");
                    return grad;
                })();
                context.fillRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
                return surface;
            })();
            return sprite;
        })();

        var notes = (function () {
            var whiteArgs = {0: "00", 2: "02", 4: "04", 6: "05", 8: "07", 10: "09", 12: "11", 14: "12"};
            var blackArgs = {1: "01", 3: "03", 7: "06", 9: "08", 11: "10"};
            var notes = {white: [], black: []};
            for (var i in whiteArgs) {
                notes.white.push(new Note(i, whiteArgs[i]));
            }
            for (var i in blackArgs) {
                notes.black.push(new Note(i, blackArgs[i]));
            }
            return notes;
        })();

        var keyboard = (function () {
            var sprite = new Sprite(8 * KEY_WIDTH, KEY_HEIGHT);
            sprite.x = KEYBOARD_LEFT;
            sprite.y = KEYBOARD_TOP;

            /**
             * 発火したイベントが誤検知かどうかを判定します。
             * 同じ場所で短期間 (1 フレーム以内) に連打された場合は誤検知とみなします。
             *
             * @param {Event} e 検知したイベント
             * @returns {Boolean} 誤検知の場合は false, そうでない場合は true
             */
            var validateEvent = function (e) {
                var prevX = previousFinger.x;
                var prevY = previousFinger.y;
                if (5 < getDistance(e.x, e.y, prevX, prevY)) {
                    return true;
                }
                return (1 < sprite.age - lastReleasedTime);
            };

            sprite.addEventListener(Event.TOUCH_START, function (e) {
                if (!validateEvent(e)) {
                    return;
                }
                hand.touch(e.x, e.y);
            });
            sprite.addEventListener(Event.TOUCH_MOVE, function (e) {
                if (!validateEvent(e)) {
                    return;
                }
                hand.swipe(e.x, e.y);
            });
            sprite.addEventListener(Event.TOUCH_END, function (e) {
                if (!validateEvent(e)) {
                    return;
                }
                var finger = hand.release(e.x, e.y);
                previousFinger = finger;
                lastReleasedTime = sprite.age;
            });
            return sprite;
        })();
        var volumeBase = (function () {
            var width = VOLUME_WIDTH;
            var height = 40;
            var sprite = new Sprite(width, height);
            sprite.image = core.assets["img/volume-base.png"];
            sprite.x = (DISPLAY_WIDTH / 2) - (width / 2);
            sprite.y = VOLUME_TOP - (height / 2);
            return sprite;
        })();
        var volumeButton = (function () {
            var width = 64;
            var height = 64;
            var sprite = new Sprite(width, height);
            sprite.image = core.assets["img/volume-button.png"];
            sprite.x = (DISPLAY_WIDTH / 2) - (width / 2);
            sprite.y = VOLUME_TOP - (height / 2);
            var baseHalf = VOLUME_WIDTH / 2;
            var buttonHalf = width / 2;
            var xMin = (DISPLAY_WIDTH / 2) - baseHalf;
            var xMax = (DISPLAY_WIDTH / 2) + baseHalf;
            var getVolumeLevelFrame = function (volume) {
                if (volume === 0) {
                    return 0;
                }
                if (volume < 0.15) {
                    return 1;
                }
                if (volume < 0.45) {
                    return 2;
                }
                if (volume < 0.7) {
                    return 3;
                }
                return 4;
            };
            sprite.addEventListener(Event.TOUCH_MOVE, function (e) {
                var x = e.x;
                var newX = Math.max(Math.min(x, xMax), xMin);
                this.x = newX - buttonHalf;
                volume = (newX - xMin) / (xMax - xMin);
                volumeLevel.frame = getVolumeLevelFrame(volume);
            });
            return sprite;
        })();
        var volumeLevel = (function () {
            var width = 64;
            var height = 64;
            var sprite = new Sprite(width, height);
            sprite.image = core.assets["img/volume-level.png"];
            sprite.x = (DISPLAY_WIDTH / 2) - (VOLUME_WIDTH / 2) - width;
            sprite.y = VOLUME_TOP - (height / 2);
            sprite.frame = 3;
            return sprite;
        })();

        var scene = core.rootScene;
        scene.addChild(background);
        notes.white.map(function (key) {
            scene.addChild(key);
        });
        notes.black.map(function (key) {
            scene.addChild(key);
        });
        scene.addChild(keyboard);
        scene.addChild(volumeLevel);
        scene.addChild(volumeBase);
        scene.addChild(volumeButton);
    };
    core.start();
}

window.onload = main;
<|endoftext|>


<commit_before>'use strict';

/*
 * The "main" module of the whole app, so to speak...
 * Mostly handles setting up configuration variables
 * and wiring up the states, controllers and templates
 */

angular.module('sardroid', ['ionic', 'ngStorage', 'login', 'pascalprecht.translate', 'logout', 'home', 'contacts', 'userprofile', 'call', 'peerhandler', 'drawinghandler', 'audiohandler'])

.run(function($ionicPlatform, $rootScope, $ionicSideMenuDelegate) {
        $ionicPlatform.ready(function() {
            // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
            // for form inputs)
            if(window.cordova && window.cordova.plugins.Keyboard) {
                cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
            }
            if(window.StatusBar) {
                StatusBar.styleDefault();
            }

            // WARNING! Mega hack to disable side menu login
            // TODO: Fix this somehow?
            $rootScope.$on('$stateChangeSuccess',
                function(event, toState, toParams, fromState, fromParams){
                    if (toState.name === 'login' ){
                        $rootScope.showRightMenu = false;
                        $ionicSideMenuDelegate._instances[0].right.isEnabled = false;
                    }
                    else {
                        $rootScope.showRightMenu = true;
                        $ionicSideMenuDelegate.canDragContent(true);
                        $ionicSideMenuDelegate._instances[0].right.isEnabled = true;
                    }

                    if (toState.name === 'call') {
                        $ionicSideMenuDelegate.canDragContent(false);
                    }
                    else {
                        $ionicSideMenuDelegate.canDragContent(true);
                    }

                });
            // Disable showing right menu by default
            $rootScope.showRightMenu = false;
            $ionicSideMenuDelegate._instances[0].right.isEnabled = false;

            // Set up configuration variables we can use anywhere in Angular
            $rootScope.config = {
                peerjs: {
                    host: 'mattij.com',
                    port: 9000,
                    path: '/peerjs',
                    debug: 3,
                    secure: true,
                    config: {'iceServers': [
                        { 'url': 'stun:stun.l.google.com:19302' },
                        { 'url': 'stun:stun1.l.google.com:19302' },
                        { 'url': 'stun:stun2.l.google.com:19302' },
                        { 'url': 'stun:stun3.l.google.com:19302' }
                    ]}},
                drawings: {
                    size: {
                        width:  0.56,
                        height: 0.44
                    },
                    remoteColor: 'red',
                    localColor:  'blue',
                    brushWidth:   5,
                    drawingRemoveTime: 2000
                }
            };
})
}).config(function($stateProvider, $urlRouterProvider, $translateProvider) {
    $stateProvider
        .state('login', {
            cache: false,
            url: '/login',
            templateUrl: 'templates/login.html',
            controller: 'LoginCtrl'})
        .state('tabs', {
            url: '/tab',
            abstract: true,
            templateUrl: 'templates/tabs.html'
        })
        .state('userprofile', {
            cache: false,
            url: '/userprofile',
            templateUrl: 'templates/userprofile.html',
            controller: 'UserProfileCtrl',
            params: { user: null }
        })
         .state('call', {
            cache: false,
            url: '/call',
            templateUrl: 'templates/call.html',
            controller: 'CallCtrl',
            params: { user: null }
         })
        .state('tabs.home', {
            url: '/home',
            views: {
                'home-tab': {
                    templateUrl: 'templates/home.html',
                    controller: 'HomeCtrl'
                }
            }
        })
        .state('tabs.contacts', {
            url: '/contacts',
            views: {
                 'contacts-tab': {
                    templateUrl: 'templates/contacts.html',
                    controller: 'ContactsCtrl'
                }
            }
    });

    $translateProvider.
        useStaticFilesLoader({
            prefix: 'res/locales/',
            suffix: '.json'
        })
        .registerAvailableLanguageKeys(['en', 'fi'], {
        'en': 'en', 'en_GB': 'en', 'en_US': 'en',
        'fi': 'fi'
        })
        .preferredLanguage('en')
        .fallbackLanguage('en')
        .determinePreferredLanguage()
        .useSanitizeValueStrategy('sanitize')

    $urlRouterProvider.otherwise('/login');

});

<commit_msg>Change local drawing color<commit_after>'use strict';

/*
 * The "main" module of the whole app, so to speak...
 * Mostly handles setting up configuration variables
 * and wiring up the states, controllers and templates
 */

angular.module('sardroid', ['ionic', 'ngStorage', 'login', 'pascalprecht.translate', 'logout', 'home', 'contacts', 'userprofile', 'call', 'peerhandler', 'drawinghandler', 'audiohandler'])

.run(function($ionicPlatform, $rootScope, $ionicSideMenuDelegate) {
        $ionicPlatform.ready(function() {
            // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
            // for form inputs)
            if(window.cordova && window.cordova.plugins.Keyboard) {
                cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
            }
            if(window.StatusBar) {
                StatusBar.styleDefault();
            }

            // WARNING! Mega hack to disable side menu login
            // TODO: Fix this somehow?
            $rootScope.$on('$stateChangeSuccess',
                function(event, toState, toParams, fromState, fromParams){
                    if (toState.name === 'login' ){
                        $rootScope.showRightMenu = false;
                        $ionicSideMenuDelegate._instances[0].right.isEnabled = false;
                    }
                    else {
                        $rootScope.showRightMenu = true;
                        $ionicSideMenuDelegate.canDragContent(true);
                        $ionicSideMenuDelegate._instances[0].right.isEnabled = true;
                    }

                    if (toState.name === 'call') {
                        $ionicSideMenuDelegate.canDragContent(false);
                    }
                    else {
                        $ionicSideMenuDelegate.canDragContent(true);
                    }

                });
            // Disable showing right menu by default
            $rootScope.showRightMenu = false;
            $ionicSideMenuDelegate._instances[0].right.isEnabled = false;

            // Set up configuration variables we can use anywhere in Angular
            $rootScope.config = {
                peerjs: {
                    host: 'mattij.com',
                    port: 9000,
                    path: '/peerjs',
                    debug: 3,
                    secure: true,
                    config: {'iceServers': [
                        { 'url': 'stun:stun.l.google.com:19302' },
                        { 'url': 'stun:stun1.l.google.com:19302' },
                        { 'url': 'stun:stun2.l.google.com:19302' },
                        { 'url': 'stun:stun3.l.google.com:19302' }
                    ]}},
                drawings: {
                    size: {
                        width:  0.56,
                        height: 0.44
                    },
                    remoteColor: 'red',
                    localColor:  '#387ef5',
                    brushWidth:   5,
                    drawingRemoveTime: 2000
                }
            };
})
}).config(function($stateProvider, $urlRouterProvider, $translateProvider) {
    $stateProvider
        .state('login', {
            cache: false,
            url: '/login',
            templateUrl: 'templates/login.html',
            controller: 'LoginCtrl'})
        .state('tabs', {
            url: '/tab',
            abstract: true,
            templateUrl: 'templates/tabs.html'
        })
        .state('userprofile', {
            cache: false,
            url: '/userprofile',
            templateUrl: 'templates/userprofile.html',
            controller: 'UserProfileCtrl',
            params: { user: null }
        })
         .state('call', {
            cache: false,
            url: '/call',
            templateUrl: 'templates/call.html',
            controller: 'CallCtrl',
            params: { user: null }
         })
        .state('tabs.home', {
            url: '/home',
            views: {
                'home-tab': {
                    templateUrl: 'templates/home.html',
                    controller: 'HomeCtrl'
                }
            }
        })
        .state('tabs.contacts', {
            url: '/contacts',
            views: {
                 'contacts-tab': {
                    templateUrl: 'templates/contacts.html',
                    controller: 'ContactsCtrl'
                }
            }
    });

    $translateProvider.
        useStaticFilesLoader({
            prefix: 'res/locales/',
            suffix: '.json'
        })
        .registerAvailableLanguageKeys(['en', 'fi'], {
        'en': 'en', 'en_GB': 'en', 'en_US': 'en',
        'fi': 'fi'
        })
        .preferredLanguage('en')
        .fallbackLanguage('en')
        .determinePreferredLanguage()
        .useSanitizeValueStrategy('sanitize')

    $urlRouterProvider.otherwise('/login');

});

<|endoftext|>