<commit_before>"use strict";

var
  Socket = require('net').Socket,
  EventEmitter = require('events').EventEmitter,
  Promise = require('a-promise'),
  RedisProto = require('redis-proto'),
  Commands = require('../Dist/Commands.js');
class Redis extends EventEmitter{
  constructor(){
    super();

    let Me = this;
    this.Socket = new Socket();
    this.Socket.on('data', function(Buffer){
      try {
        Buffer = RedisProto.Decode(Buffer.toString());
        if(Me.Expecting) Me.Expecting[0](Buffer);
        else if(Buffer.length === 3 && Buffer[0] === 'message'){
          Me.emit('message', Buffer[1], Buffer[2]);
          Me.emit('message:' + Buffer[1], Buffer[2]);
        }
      } catch (err){
        // Assign new Trace
        err = new Error(err.message);
        if(Me.Expecting) Me.Expecting[1](err);
        else Me.emit('error', err);
      }
      Me.Expecting = null;
    });
    this.Expecting = null;
  }
  connect(Host, Port){
    Host = Host || '127.0.0.1';
    Port = Port || 6379;
    let Me = this;
    return new Promise(function(Resolve){
      Me.Socket.connect(Port, Host, Resolve);
    });
  }
  ref(){
    this.Socket.ref();
  }
  unref(){
    this.Socket.unref();
  }
  close(){
    this.Socket.close();
  }
}
Commands.forEach(function(Entry){
  let Execute = function(){
    Array.prototype.unshift.call(arguments, Entry);
    let Encoded = RedisProto.Encode(arguments);
    let Me = this;
    return new Promise(function(Resolve, Reject){
      Me.Socket.write(Encoded + "\r\n", 'utf8', function(){
        Me.Expecting = [Resolve, Reject];
      });
    });
  };
  Redis.prototype[Entry] = Execute;
  Redis.prototype[Entry.toLowerCase()] = Execute;
});
module.exports = Redis;<commit_msg>:left_right_arrow: Add end as an alias for close<commit_after>"use strict";

var
  Socket = require('net').Socket,
  EventEmitter = require('events').EventEmitter,
  Promise = require('a-promise'),
  RedisProto = require('redis-proto'),
  Commands = require('../Dist/Commands.js');
class Redis extends EventEmitter{
  constructor(){
    super();

    let Me = this;
    this.Socket = new Socket();
    this.Socket.on('data', function(Buffer){
      try {
        Buffer = RedisProto.Decode(Buffer.toString());
        if(Me.Expecting) Me.Expecting[0](Buffer);
        else if(Buffer.length === 3 && Buffer[0] === 'message'){
          Me.emit('message', Buffer[1], Buffer[2]);
          Me.emit('message:' + Buffer[1], Buffer[2]);
        }
      } catch (err){
        // Assign new Trace
        err = new Error(err.message);
        if(Me.Expecting) Me.Expecting[1](err);
        else Me.emit('error', err);
      }
      Me.Expecting = null;
    });
    this.Expecting = null;
  }
  connect(Host, Port){
    Host = Host || '127.0.0.1';
    Port = Port || 6379;
    let Me = this;
    return new Promise(function(Resolve){
      Me.Socket.connect(Port, Host, Resolve);
    });
  }
  ref(){
    this.Socket.ref();
  }
  unref(){
    this.Socket.unref();
  }
  close(){
    this.Socket.close();
  }
}
Commands.forEach(function(Entry){
  let Execute = function(){
    Array.prototype.unshift.call(arguments, Entry);
    let Encoded = RedisProto.Encode(arguments);
    let Me = this;
    return new Promise(function(Resolve, Reject){
      Me.Socket.write(Encoded + "\r\n", 'utf8', function(){
        Me.Expecting = [Resolve, Reject];
      });
    });
  };
  Redis.prototype[Entry] = Execute;
  Redis.prototype[Entry.toLowerCase()] = Execute;
});

Redis.prototype.end = Redis.prototype.close;

module.exports = Redis;<|endoftext|>


<commit_before>var _ = require('underscore');
var nconf = require('nconf');

var h = require('../helper');
var config = require('../config');
var log = require('../log');
var session = require('../session');

var cmd = {
  command: 'config [key] [value]',
  desc:    'show or set configurations',
  builder: {
    all: {
      alias:    'a',
      type:     'boolean',
      describe: 'Show all user configuration',
      default:  false
    },
    delete: {
      alias:    'd',
      type:     'boolean',
      describe: 'Delete configuration',
      default:  false
    }
  }
};

function prettyConfig(cfg) {
  return JSON.stringify(cfg, null, 2);
}

function loadConfig(showall) {
  var cfg = showall ? config.getAll(true) : nconf.get();
  return _.omit(cfg, 'type');
}

function saveConfig() {
  require('fs').writeFileSync(h.getConfigFile(), prettyConfig(loadConfig(false)));
}

cmd.handler = function(argv) {
  session.argv = argv;
  nconf.file('local', h.getConfigFile());

  // show all
  if (argv.key === undefined)
    return log.info(prettyConfig(loadConfig(argv.all)));

  var v = nconf.get(argv.key);

  // delete
  if (argv.delete) {
    if (v === undefined) return log.error('Key not found: ' + argv.key);
    nconf.clear(argv.key);
    return saveConfig();
  }

  // show
  if (argv.value === undefined) {
    if (v === undefined) return log.error('Key not found: ' + argv.key);
    return log.info(prettyConfig(v));
  }

  // set
  if (argv.value === 'true') argv.value = true;
  if (argv.value === 'false') argv.value = false;
  nconf.set(argv.key, argv.value);
  return saveConfig();
};

module.exports = cmd;
<commit_msg>[Config] use correct type of config value.<commit_after>var _ = require('underscore');
var nconf = require('nconf');

var h = require('../helper');
var config = require('../config');
var log = require('../log');
var session = require('../session');

var cmd = {
  command: 'config [key] [value]',
  desc:    'show or set configurations',
  builder: {
    all: {
      alias:    'a',
      type:     'boolean',
      describe: 'Show all user configuration',
      default:  false
    },
    delete: {
      alias:    'd',
      type:     'boolean',
      describe: 'Delete configuration',
      default:  false
    }
  }
};

function prettyConfig(cfg) {
  return JSON.stringify(cfg, null, 2);
}

function loadConfig(showall) {
  var cfg = showall ? config.getAll(true) : nconf.get();
  return _.omit(cfg, 'type');
}

function saveConfig() {
  require('fs').writeFileSync(h.getConfigFile(), prettyConfig(loadConfig(false)));
}

cmd.handler = function(argv) {
  session.argv = argv;
  nconf.file('local', h.getConfigFile());

  // show all
  if (argv.key === undefined)
    return log.info(prettyConfig(loadConfig(argv.all)));

  var v = nconf.get(argv.key);

  // delete
  if (argv.delete) {
    if (v === undefined) return log.error('Key not found: ' + argv.key);
    nconf.clear(argv.key);
    return saveConfig();
  }

  // show
  if (argv.value === undefined) {
    if (v === undefined) return log.error('Key not found: ' + argv.key);
    return log.info(prettyConfig(v));
  }

  // set
  nconf.set(argv.key, JSON.parse(argv.value));
  return saveConfig();
};

module.exports = cmd;
<|endoftext|>


<commit_before>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

module('Integration | Component | paper reset button', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function(assert) {
    // Set any properties with this.set('myProperty', 'value');
    // Handle any actions with this.on('myAction', function(val) { ... });

    await render(hbs`{{paper-reset-button}}`);

    assert.equal(this.$().text().trim(), '');

    // Template block usage:
    await render(hbs`
      {{#paper-reset-button}}
        template block text
      {{/paper-reset-button}}
    `);

    assert.equal(this.$().text().trim(), 'template block text');
  });
});
<commit_msg>refactor jquery from tests (#1017)<commit_after>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

module('Integration | Component | paper reset button', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function(assert) {
    // Set any properties with this.set('myProperty', 'value');
    // Handle any actions with this.on('myAction', function(val) { ... });

    await render(hbs`{{paper-reset-button}}`);

    assert.dom(this.element).hasText('');

    // Template block usage:
    await render(hbs`
      {{#paper-reset-button}}
        template block text
      {{/paper-reset-button}}
    `);

    assert.dom(this.element).hasText('template block text');
  });
});
<|endoftext|>


<commit_before>// Copyright 2020, University of Colorado Boulder

/**
 * Generates JS modules from resources such as images/strings/audio/etc.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Jonathan Olson (PhET Interactive Simulations)
 */
'use strict';

const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
const createMipmap = require( './createMipmap' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const loadFileAsDataURI = require( '../common/loadFileAsDataURI' );
const os = require( 'os' );
const updateCopyrightForGeneratedFile = require( './updateCopyrightForGeneratedFile' );

// disable lint in compiled files, because it increases the linting time
const HEADER = '/* eslint-disable */';

// supported image types, not case-sensitive
const IMAGE_SUFFIXES = [ '.png', '.jpg', '.cur' ];

// supported sound file types, not case-sensitive
const SOUND_SUFFIXES = [ '.mp3', '.wav' ];

/**
 * String replacement
 * @param {string} string - the string which will be searched
 * @param {string} search - the text to be replaced
 * @param {string} replacement - the new text
 * @returns {string}
 */
const replace = ( string, search, replacement ) => string.split( search ).join( replacement );

/**
 * Gets the relative path to the root based on the depth of a resource
 *
 * @returns {string}
 */
const expandDots = abspath => {

  // Finds the depths of a directory relative to the root of where grunt.recurse was called from (a repo root)
  const depth = abspath.split( '/' ).length - 2;
  let parentDirectory = '';
  for ( let i = 0; i < depth; i++ ) {
    parentDirectory = parentDirectory + '../';
  }
  return parentDirectory;
};

/**
 * Output with an OS-specific EOL sequence, see https://github.com/phetsims/chipper/issues/908
 * @param string
 * @returns {string}
 */
const fixEOL = string => replace( string, '\n', os.EOL );

/**
 * Transform an image file to a JS file that loads the image.
 * @param {string} abspath - the absolute path of the image
 */
const modulifyImage = async abspath => {

  const dataURI = loadFileAsDataURI( abspath );

  const contents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';
const image = new Image();
const unlock = simLauncher.createLock( image );
image.onload = unlock;
image.src = '${dataURI}';
export default image;`;

  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( contents ) );
};

/**
 * Transform an image file to a JS file that loads the image as a mipmap.
 * @param {string} abspath - the absolute path of the image
 */
const modulifyMipmap = async abspath => {

  // Defaults. NOTE: using the default settings because we have not run into a need, see
  // https://github.com/phetsims/chipper/issues/820 and https://github.com/phetsims/chipper/issues/945
  const config = {
    level: 4, // maximum level
    quality: 98
  };

  const mipmaps = await createMipmap( abspath, config.level, config.quality );
  const entry = mipmaps.map( ( { width, height, url } ) => ( { width: width, height: height, url: url } ) );

  const mipmapContents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';
const mipmaps = ${JSON.stringify( entry, null, 2 )};
mipmaps.forEach( mipmap => {
  mipmap.img = new Image();
  const unlock = simLauncher.createLock( mipmap.img );
  mipmap.img.onload = unlock;
  mipmap.img.src = mipmap.url; // trigger the loading of the image for its level
  mipmap.canvas = document.createElement( 'canvas' );
  mipmap.canvas.width = mipmap.width;
  mipmap.canvas.height = mipmap.height;
  const context = mipmap.canvas.getContext( '2d' );
  mipmap.updateCanvas = () => {
    if ( mipmap.img.complete && ( typeof mipmap.img.naturalWidth === 'undefined' || mipmap.img.naturalWidth > 0 ) ) {
      context.drawImage( mipmap.img, 0, 0 );
      delete mipmap.updateCanvas;
    }
  };
} );
export default mipmaps;`;
  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( mipmapContents ) );
};

/**
 * Decode a sound file into a Web Audio AudioBuffer.
 * @param {string} abspath - the absolute path of the sound file
 */
const modulifySound = abspath => {

  // load the sound file
  const dataURI = loadFileAsDataURI( abspath );

  // output the contents of the file that will define the sound in JS format
  const contents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';
import base64SoundToByteArray from '${expandDots( abspath )}tambo/js/base64SoundToByteArray.js';
import WrappedAudioBuffer from '${expandDots( abspath )}tambo/js/WrappedAudioBuffer.js';
import phetAudioContext from '${expandDots( abspath )}tambo/js/phetAudioContext.js';

const soundURI = '${dataURI}';
const soundByteArray = base64SoundToByteArray( phetAudioContext, soundURI );
const unlock = simLauncher.createLock( soundURI );
const wrappedAudioBuffer = new WrappedAudioBuffer();
const onDecodeSuccess = decodedAudio => {
  wrappedAudioBuffer.audioBuffer = decodedAudio;
  wrappedAudioBuffer.loadedProperty.set( true );
  unlock();
};
const onDecodeError = decodeError => {
  console.warn( 'decode of audio data failed, using stubbed sound, error: ' + decodeError );
  wrappedAudioBuffer.audioBuffer = phetAudioContext.createBuffer( 1, 0, phetAudioContext.sampleRate );
  wrappedAudioBuffer.loadedProperty.set( true );
  unlock();
};
phetAudioContext.decodeAudioData( soundByteArray.buffer, onDecodeSuccess, onDecodeError );
export default wrappedAudioBuffer;`;

  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( contents ) );
};

/**
 * Convert .png => _png_mipmap.js, etc.
 *
 * @param {string} abspath - the absolute path
 * @param {string} suffix - the new suffix, such as '.js'
 * @returns {string}
 */
const convertSuffix = ( abspath, suffix ) => {
  const lastDotIndex = abspath.lastIndexOf( '.' );
  return abspath.substring( 0, lastDotIndex ) + '_' + abspath.substring( lastDotIndex + 1 ) + suffix;
};

/**
 * Determines the suffix from a filename, everything after the final '.'
 *
 * @param {string} filename
 * @returns {string}
 */
const getSuffix = filename => {
  const index = filename.lastIndexOf( '.' );
  return filename.substring( index );
};

/**
 * Creates a *.js file corresponding to matching resources such as images or sounds.
 * @param {string} abspath
 * @param {string} rootdir
 * @param {string} subdir
 * @param {string} filename
 */
const modulifyFile = async ( abspath, rootdir, subdir, filename ) => {

  if ( subdir && ( subdir.startsWith( 'images' ) ||

                   // for brand
                   subdir.startsWith( 'phet/images' ) ||
                   subdir.startsWith( 'phet-io/images' ) ||
                   subdir.startsWith( 'adapted-from-phet/images' ) )
       && IMAGE_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifyImage( abspath );
  }

  if ( subdir && ( subdir.startsWith( 'mipmaps' ) ||

                   // for brand
                   subdir.startsWith( 'phet/mipmaps' ) ||
                   subdir.startsWith( 'phet-io/mipmaps' ) ||
                   subdir.startsWith( 'adapted-from-phet/mipmaps' ) )
       && IMAGE_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifyMipmap( abspath );
  }

  if ( subdir && subdir.startsWith( 'sounds' ) && SOUND_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifySound( abspath );
  }
};

/**
 * Creates the string module at js/${_.camelCase( repo )}Strings.js for repos that need it.
 * @public
 *
 * @param {string} repo
 */
const createStringModule = async repo => {
  const packageObject = grunt.file.readJSON( `../${repo}/package.json` );
  const stringModuleFile = `../${repo}/js/${_.camelCase( repo )}Strings.js`;
  const namespace = _.camelCase( repo );
  fs.writeFileSync( stringModuleFile, fixEOL( `// Copyright ${new Date().getFullYear()}, University of Colorado Boulder

/**
 * Auto-generated from modulify, DO NOT manually modify.
 */

import getStringModule from '../../chipper/js/getStringModule.js';
import ${namespace} from './${namespace}.js';

const ${namespace}Strings = getStringModule( '${packageObject.phet.requirejsNamespace}' );

${namespace}.register( '${namespace}Strings', ${namespace}Strings );

export default ${namespace}Strings;
` ) );
  await updateCopyrightForGeneratedFile( repo, stringModuleFile );
};

/**
 * Creates the namespace module at js/${_.camelCase( repo )}.js for repos that need it.
 * @public
 *
 * @param {string} repo
 */
const createNamespaceModule = async repo => {
  const namespace = _.camelCase( repo );
  const namespaceFile = `../${repo}/js/${namespace}.js`;
  fs.writeFileSync( namespaceFile, fixEOL( `// Copyright ${new Date().getFullYear()}, University of Colorado Boulder

/**
 * Creates the namespace for this simulation.
 */

// modules
import Namespace from '../../phet-core/js/Namespace.js';

export default new Namespace( '${namespace}' );
` ) );
  await updateCopyrightForGeneratedFile( repo, namespaceFile );
};

/**
 * Entry point for modulify, which transforms all of the resources in a repo to *.js files.
 * @param {string} repo - the name of a repo, such as 'joist'
 */
const modulify = async repo => {
  console.log( `modulifying ${repo}` );
  const relativeFiles = [];
  grunt.file.recurse( `../${repo}`, async ( abspath, rootdir, subdir, filename ) => {
    relativeFiles.push( { abspath: abspath, rootdir: rootdir, subdir: subdir, filename: filename } );
  } );

  for ( let i = 0; i < relativeFiles.length; i++ ) {
    const entry = relativeFiles[ i ];
    await modulifyFile( entry.abspath, entry.rootdir, entry.subdir, entry.filename );
  }

  // Create the namespace file, if it did not already exist
  const packageObject = grunt.file.readJSON( `../${repo}/package.json` );
  if ( fs.existsSync( `../${repo}/${repo}-strings_en.json` ) && packageObject.phet && packageObject.phet.requirejsNamespace ) {
    await createStringModule( repo );

    const namespace = _.camelCase( repo );
    const namespaceFile = `../${repo}/js/${namespace}.js`;
    if ( !fs.existsSync( namespaceFile ) ) {
      await createNamespaceModule( repo );
    }
  }
};

module.exports = modulify;<commit_msg>simplified wrapped audio buffers, see https://github.com/phetsims/tambo/issues/100<commit_after>// Copyright 2020, University of Colorado Boulder

/**
 * Generates JS modules from resources such as images/strings/audio/etc.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Jonathan Olson (PhET Interactive Simulations)
 */
'use strict';

const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
const createMipmap = require( './createMipmap' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const loadFileAsDataURI = require( '../common/loadFileAsDataURI' );
const os = require( 'os' );
const updateCopyrightForGeneratedFile = require( './updateCopyrightForGeneratedFile' );

// disable lint in compiled files, because it increases the linting time
const HEADER = '/* eslint-disable */';

// supported image types, not case-sensitive
const IMAGE_SUFFIXES = [ '.png', '.jpg', '.cur' ];

// supported sound file types, not case-sensitive
const SOUND_SUFFIXES = [ '.mp3', '.wav' ];

/**
 * String replacement
 * @param {string} string - the string which will be searched
 * @param {string} search - the text to be replaced
 * @param {string} replacement - the new text
 * @returns {string}
 */
const replace = ( string, search, replacement ) => string.split( search ).join( replacement );

/**
 * Gets the relative path to the root based on the depth of a resource
 *
 * @returns {string}
 */
const expandDots = abspath => {

  // Finds the depths of a directory relative to the root of where grunt.recurse was called from (a repo root)
  const depth = abspath.split( '/' ).length - 2;
  let parentDirectory = '';
  for ( let i = 0; i < depth; i++ ) {
    parentDirectory = parentDirectory + '../';
  }
  return parentDirectory;
};

/**
 * Output with an OS-specific EOL sequence, see https://github.com/phetsims/chipper/issues/908
 * @param string
 * @returns {string}
 */
const fixEOL = string => replace( string, '\n', os.EOL );

/**
 * Transform an image file to a JS file that loads the image.
 * @param {string} abspath - the absolute path of the image
 */
const modulifyImage = async abspath => {

  const dataURI = loadFileAsDataURI( abspath );

  const contents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';
const image = new Image();
const unlock = simLauncher.createLock( image );
image.onload = unlock;
image.src = '${dataURI}';
export default image;`;

  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( contents ) );
};

/**
 * Transform an image file to a JS file that loads the image as a mipmap.
 * @param {string} abspath - the absolute path of the image
 */
const modulifyMipmap = async abspath => {

  // Defaults. NOTE: using the default settings because we have not run into a need, see
  // https://github.com/phetsims/chipper/issues/820 and https://github.com/phetsims/chipper/issues/945
  const config = {
    level: 4, // maximum level
    quality: 98
  };

  const mipmaps = await createMipmap( abspath, config.level, config.quality );
  const entry = mipmaps.map( ( { width, height, url } ) => ( { width: width, height: height, url: url } ) );

  const mipmapContents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';
const mipmaps = ${JSON.stringify( entry, null, 2 )};
mipmaps.forEach( mipmap => {
  mipmap.img = new Image();
  const unlock = simLauncher.createLock( mipmap.img );
  mipmap.img.onload = unlock;
  mipmap.img.src = mipmap.url; // trigger the loading of the image for its level
  mipmap.canvas = document.createElement( 'canvas' );
  mipmap.canvas.width = mipmap.width;
  mipmap.canvas.height = mipmap.height;
  const context = mipmap.canvas.getContext( '2d' );
  mipmap.updateCanvas = () => {
    if ( mipmap.img.complete && ( typeof mipmap.img.naturalWidth === 'undefined' || mipmap.img.naturalWidth > 0 ) ) {
      context.drawImage( mipmap.img, 0, 0 );
      delete mipmap.updateCanvas;
    }
  };
} );
export default mipmaps;`;
  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( mipmapContents ) );
};

/**
 * Decode a sound file into a Web Audio AudioBuffer.
 * @param {string} abspath - the absolute path of the sound file
 */
const modulifySound = abspath => {

  // load the sound file
  const dataURI = loadFileAsDataURI( abspath );

  // output the contents of the file that will define the sound in JS format
  const contents = `${HEADER}
import simLauncher from '${expandDots( abspath )}joist/js/simLauncher.js';
import base64SoundToByteArray from '${expandDots( abspath )}tambo/js/base64SoundToByteArray.js';
import WrappedAudioBuffer from '${expandDots( abspath )}tambo/js/WrappedAudioBuffer.js';
import phetAudioContext from '${expandDots( abspath )}tambo/js/phetAudioContext.js';

const soundURI = '${dataURI}';
const soundByteArray = base64SoundToByteArray( phetAudioContext, soundURI );
const unlock = simLauncher.createLock( soundURI );
const wrappedAudioBuffer = new WrappedAudioBuffer();
const onDecodeSuccess = decodedAudio => {
  wrappedAudioBuffer.audioBufferProperty.set( decodedAudio );
  unlock();
};
const onDecodeError = decodeError => {
  console.warn( 'decode of audio data failed, using stubbed sound, error: ' + decodeError );
  wrappedAudioBuffer.audioBufferProperty.set( phetAudioContext.createBuffer( 1, 0, phetAudioContext.sampleRate ) );
  unlock();
};
phetAudioContext.decodeAudioData( soundByteArray.buffer, onDecodeSuccess, onDecodeError );
export default wrappedAudioBuffer;`;

  fs.writeFileSync( convertSuffix( abspath, '.js' ), fixEOL( contents ) );
};

/**
 * Convert .png => _png_mipmap.js, etc.
 *
 * @param {string} abspath - the absolute path
 * @param {string} suffix - the new suffix, such as '.js'
 * @returns {string}
 */
const convertSuffix = ( abspath, suffix ) => {
  const lastDotIndex = abspath.lastIndexOf( '.' );
  return abspath.substring( 0, lastDotIndex ) + '_' + abspath.substring( lastDotIndex + 1 ) + suffix;
};

/**
 * Determines the suffix from a filename, everything after the final '.'
 *
 * @param {string} filename
 * @returns {string}
 */
const getSuffix = filename => {
  const index = filename.lastIndexOf( '.' );
  return filename.substring( index );
};

/**
 * Creates a *.js file corresponding to matching resources such as images or sounds.
 * @param {string} abspath
 * @param {string} rootdir
 * @param {string} subdir
 * @param {string} filename
 */
const modulifyFile = async ( abspath, rootdir, subdir, filename ) => {

  if ( subdir && ( subdir.startsWith( 'images' ) ||

                   // for brand
                   subdir.startsWith( 'phet/images' ) ||
                   subdir.startsWith( 'phet-io/images' ) ||
                   subdir.startsWith( 'adapted-from-phet/images' ) )
       && IMAGE_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifyImage( abspath );
  }

  if ( subdir && ( subdir.startsWith( 'mipmaps' ) ||

                   // for brand
                   subdir.startsWith( 'phet/mipmaps' ) ||
                   subdir.startsWith( 'phet-io/mipmaps' ) ||
                   subdir.startsWith( 'adapted-from-phet/mipmaps' ) )
       && IMAGE_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifyMipmap( abspath );
  }

  if ( subdir && subdir.startsWith( 'sounds' ) && SOUND_SUFFIXES.indexOf( getSuffix( filename ) ) >= 0 ) {
    await modulifySound( abspath );
  }
};

/**
 * Creates the string module at js/${_.camelCase( repo )}Strings.js for repos that need it.
 * @public
 *
 * @param {string} repo
 */
const createStringModule = async repo => {
  const packageObject = grunt.file.readJSON( `../${repo}/package.json` );
  const stringModuleFile = `../${repo}/js/${_.camelCase( repo )}Strings.js`;
  const namespace = _.camelCase( repo );
  fs.writeFileSync( stringModuleFile, fixEOL( `// Copyright ${new Date().getFullYear()}, University of Colorado Boulder

/**
 * Auto-generated from modulify, DO NOT manually modify.
 */

import getStringModule from '../../chipper/js/getStringModule.js';
import ${namespace} from './${namespace}.js';

const ${namespace}Strings = getStringModule( '${packageObject.phet.requirejsNamespace}' );

${namespace}.register( '${namespace}Strings', ${namespace}Strings );

export default ${namespace}Strings;
` ) );
  await updateCopyrightForGeneratedFile( repo, stringModuleFile );
};

/**
 * Creates the namespace module at js/${_.camelCase( repo )}.js for repos that need it.
 * @public
 *
 * @param {string} repo
 */
const createNamespaceModule = async repo => {
  const namespace = _.camelCase( repo );
  const namespaceFile = `../${repo}/js/${namespace}.js`;
  fs.writeFileSync( namespaceFile, fixEOL( `// Copyright ${new Date().getFullYear()}, University of Colorado Boulder

/**
 * Creates the namespace for this simulation.
 */

// modules
import Namespace from '../../phet-core/js/Namespace.js';

export default new Namespace( '${namespace}' );
` ) );
  await updateCopyrightForGeneratedFile( repo, namespaceFile );
};

/**
 * Entry point for modulify, which transforms all of the resources in a repo to *.js files.
 * @param {string} repo - the name of a repo, such as 'joist'
 */
const modulify = async repo => {
  console.log( `modulifying ${repo}` );
  const relativeFiles = [];
  grunt.file.recurse( `../${repo}`, async ( abspath, rootdir, subdir, filename ) => {
    relativeFiles.push( { abspath: abspath, rootdir: rootdir, subdir: subdir, filename: filename } );
  } );

  for ( let i = 0; i < relativeFiles.length; i++ ) {
    const entry = relativeFiles[ i ];
    await modulifyFile( entry.abspath, entry.rootdir, entry.subdir, entry.filename );
  }

  // Create the namespace file, if it did not already exist
  const packageObject = grunt.file.readJSON( `../${repo}/package.json` );
  if ( fs.existsSync( `../${repo}/${repo}-strings_en.json` ) && packageObject.phet && packageObject.phet.requirejsNamespace ) {
    await createStringModule( repo );

    const namespace = _.camelCase( repo );
    const namespaceFile = `../${repo}/js/${namespace}.js`;
    if ( !fs.existsSync( namespaceFile ) ) {
      await createNamespaceModule( repo );
    }
  }
};

module.exports = modulify;<|endoftext|>


<commit_before>"use strict";

// @Compiler-Transpile "true"
// @Compiler-Output "../Dist/Main.js"

let HTTP = require('http');
let LRU = require('lru-cache');

let BrowserCache = new LRU(50);
let PolyfillCache = new LRU(500);
let Polyfills = [];

class Polyfill{
  static listen(Port){
    return HTTP.createServer(function(Request, Response){
      try {
        let Content = Polyfill.serverRequest(Request);
        Response.setHeader("Content-Type", "application/javascript");
        Response.setHeader('Content-Length', Content.length);
        Response.write(Content);
      } catch(e){
        Response.writeHead(404);
        Response.write("Not Found");
      }
      Response.end();
    }).listen(Port, "0.0.0.0");
  }
  static register(Name, Browsers, Function){
    Polyfills.push({Name: Name, Browsers: Browsers, Function: Function.toString()});
  }
  static serverRequest(Request){
    if(Request.url !== '/polyfill.js' && Request.url !== '/polyfill.min.js'){
      throw null
    }

    var Browser = Polyfill.recognizeBrowser(Request.headers['user-agent'] || '');
    let IsMinified = Request.url === '/polyfill.min.js';
    let CacheKey = Browser.Name + ':' + Browser.Version + ':' + IsMinified;

    if(Browser.Version === 0) return ;
    if(PolyfillCache.has(CacheKey)) return PolyfillCache.get(CacheKey);

    let Content = [];
    let PolyFillsAdded = [];
    Polyfills.forEach(function(Info){ // O YEE DUSTRUCTURING WHY U NO SUPPORT?!
      let Name = Info.Name;
      let Browsers = Info.Browsers;
      let Function = Info.Function;
      if(!(Browser.Name in Browsers)) return ;
      if(Browsers[Browser.Name] !== '*' && !Polyfill.matches(Browser, Browsers[Browser.Name])) return ;
      PolyFillsAdded.push(Name);
      Content.push(Name + ' = ' + Function);
    });
    Content = "(function(){\n\n// Polyfills Added:  " + PolyFillsAdded.join(', ') + "\n\n" + Content.join(";\n") + "\n})()";
    PolyfillCache.set(CacheKey, Content);
    return Content;
  }
  static matches(Browser, Condition){
    if(Condition.indexOf('-') !== -1){
      let Chunks = Condition.split('-');
      return Browser.Version >= Number(Chunks[0].trim()) && Browser.Version <= Number(Chunks[1].trim());
    } else {
      return Browser.Version === Number(Condition);
    }
  }
  static recognizeBrowser(UserAgent){

    if(BrowserCache.has(UserAgent)) return BrowserCache.get(UserAgent);

    // Taken from http://stackoverflow.com/a/8754134/2652018
    var Browser = {Name: '', Version: 0};
    try {
      if (/Opera[\/\s](\d+\.\d+)/.test(UserAgent)) {
        Browser.Name = Polyfill.Opera;
      } else if (/MSIE (\d+\.\d+);/.test(UserAgent)) {
        Browser.Name = Polyfill.IE;
      } else if (/Navigator[\/\s](\d+\.\d+)/.test(UserAgent)) {
        Browser.Name = Polyfill.Netscape;
      } else if (/Chrome[\/\s](\d+\.\d+)/.test(UserAgent)) {
        Browser.Name = Polyfill.Chrome;
      } else if (/Safari[\/\s](\d+\.\d+)/.test(UserAgent)) {
        Browser.Name = Polyfill.Safari;
        /Version[\/\s](\d+\.\d+)/.test(UserAgent);
        Browser.Version = Number(RegExp.$1);
      } else if (/Firefox[\/\s](\d+\.\d+)/.test(UserAgent)) {
        Browser.Name = Polyfill.Firefox;
      }
      if(Browser.Version === 0){
        Browser.Version = parseFloat(Number(RegExp.$1));
      }
    } catch(e) {}

    BrowserCache.set(UserAgent, Browser);
    return Browser;
  }
}

Polyfill.Opera = 'Opera';
Polyfill.IE = 'IE';
Polyfill.Netscape = 'Netscape';
Polyfill.Chrome = 'Chrome';
Polyfill.Safari = 'Safari';
Polyfill.Firefox = 'Firefox';

global.SmartPolyfill = Polyfill;

module.exports = Polyfill;<commit_msg>:fire: Remove Netscape from Main.js<commit_after>"use strict";

// @Compiler-Transpile "true"
// @Compiler-Output "../Dist/Main.js"

let HTTP = require('http');
let LRU = require('lru-cache');

let BrowserCache = new LRU(50);
let PolyfillCache = new LRU(500);
let Polyfills = [];

class Polyfill{
  static listen(Port){
    return HTTP.createServer(function(Request, Response){
      try {
        let Content = Polyfill.serverRequest(Request);
        Response.setHeader("Content-Type", "application/javascript");
        Response.setHeader('Content-Length', Content.length);
        Response.write(Content);
      } catch(e){
        Response.writeHead(404);
        Response.write("Not Found");
      }
      Response.end();
    }).listen(Port, "0.0.0.0");
  }
  static register(Name, Browsers, Function){
    Polyfills.push({Name: Name, Browsers: Browsers, Function: Function.toString()});
  }
  static serverRequest(Request){
    if(Request.url !== '/polyfill.js' && Request.url !== '/polyfill.min.js'){
      throw null
    }

    var Browser = Polyfill.recognizeBrowser(Request.headers['user-agent'] || '');
    let IsMinified = Request.url === '/polyfill.min.js';
    let CacheKey = Browser.Name + ':' + Browser.Version + ':' + IsMinified;

    if(Browser.Version === 0) return ;
    if(PolyfillCache.has(CacheKey)) return PolyfillCache.get(CacheKey);

    let Content = [];
    let PolyFillsAdded = [];
    Polyfills.forEach(function(Info){ // O YEE DUSTRUCTURING WHY U NO SUPPORT?!
      let Name = Info.Name;
      let Browsers = Info.Browsers;
      let Function = Info.Function;
      if(!(Browser.Name in Browsers)) return ;
      if(Browsers[Browser.Name] !== '*' && !Polyfill.matches(Browser, Browsers[Browser.Name])) return ;
      PolyFillsAdded.push(Name);
      Content.push(Name + ' = ' + Function);
    });
    Content = "(function(){\n\n// Polyfills Added:  " + PolyFillsAdded.join(', ') + "\n\n" + Content.join(";\n") + "\n})()";
    PolyfillCache.set(CacheKey, Content);
    return Content;
  }
  static matches(Browser, Condition){
    if(Condition.indexOf('-') !== -1){
      let Chunks = Condition.split('-');
      return Browser.Version >= Number(Chunks[0].trim()) && Browser.Version <= Number(Chunks[1].trim());
    } else {
      return Browser.Version === Number(Condition);
    }
  }
  static recognizeBrowser(UserAgent){

    if(BrowserCache.has(UserAgent)) return BrowserCache.get(UserAgent);

    // Taken from http://stackoverflow.com/a/8754134/2652018
    var Browser = {Name: '', Version: 0};
    try {
      if (/Opera[\/\s](\d+\.\d+)/.test(UserAgent)) {
        Browser.Name = Polyfill.Opera;
      } else if (/MSIE (\d+\.\d+);/.test(UserAgent)) {
        Browser.Name = Polyfill.IE;
      } else if (/Chrome[\/\s](\d+\.\d+)/.test(UserAgent)) {
        Browser.Name = Polyfill.Chrome;
      } else if (/Safari[\/\s](\d+\.\d+)/.test(UserAgent)) {
        Browser.Name = Polyfill.Safari;
        /Version[\/\s](\d+\.\d+)/.test(UserAgent);
        Browser.Version = Number(RegExp.$1);
      } else if (/Firefox[\/\s](\d+\.\d+)/.test(UserAgent)) {
        Browser.Name = Polyfill.Firefox;
      }
      if(Browser.Version === 0){
        Browser.Version = parseFloat(Number(RegExp.$1));
      }
    } catch(e) {}

    BrowserCache.set(UserAgent, Browser);
    return Browser;
  }
}

Polyfill.Opera = 'Opera';
Polyfill.IE = 'IE';
Polyfill.Chrome = 'Chrome';
Polyfill.Safari = 'Safari';
Polyfill.Firefox = 'Firefox';

global.SmartPolyfill = Polyfill;

module.exports = Polyfill;<|endoftext|>


<commit_before>/**
 * @namespace
 [lang:ja]
 * enchant.js の環境変数.
 * new Core() を呼ぶ前に変更することで変更することで, 動作設定を変えることができる.
 [/lang]
 [lang:en]
 * enchant.js environment variables.
 * Execution settings can be changed by modifying these before calling new Core().
 [/lang]
 [lang:de]
 * Umgebungsvariable.
 [/lang]
 */
enchant.ENV = {
    /**
     [lang:ja]
     * enchant.jsのバージョン.
     [/lang]
     [lang:en]
     * Version of enchant.js
     [/lang]
     * @type String
     */
    VERSION: '<%= pkg.version %>',
    /**
     [lang:ja]
     * 実行中のブラウザの種類.
     [/lang]
     [lang:en]
     * Identifier of the current browser.
     [/lang]
     * @type String
     */
    BROWSER: (function(ua) {
        if (/Eagle/.test(ua)) {
            return 'eagle';
        } else if (/Opera/.test(ua)) {
            return 'opera';
        } else if (/MSIE|Trident/.test(ua)) {
            return 'ie';
        } else if (/Chrome/.test(ua)) {
            return 'chrome';
        } else if (/(?:Macintosh|Windows).*AppleWebKit/.test(ua)) {
            return 'safari';
        } else if (/(?:iPhone|iPad|iPod).*AppleWebKit/.test(ua)) {
            return 'mobilesafari';
        } else if (/Firefox/.test(ua)) {
            return 'firefox';
        } else if (/Android/.test(ua)) {
            return 'android';
        } else {
            return '';
        }
    }(navigator.userAgent)),
    /**
     [lang:ja]
     * 実行中のブラウザに対応するCSSのベンダープレフィックス.
     [/lang]
     [lang:en]
     * The CSS vendor prefix of the current browser.
     [/lang]
     * @type String
     */
    VENDOR_PREFIX: (function() {
        var ua = navigator.userAgent;
        if (ua.indexOf('Opera') !== -1) {
            return 'O';
        } else if (/MSIE|Trident/.test(ua)) {
            return 'ms';
        } else if (ua.indexOf('WebKit') !== -1) {
            return 'webkit';
        } else if (navigator.product === 'Gecko') {
            return 'Moz';
        } else {
            return '';
        }
    }()),
    /**
     [lang:ja]
     * ブラウザがタッチ入力をサポートしているかどうか.
     [/lang]
     [lang:en]
     * Determines if the current browser supports touch.
     * True, if touch is enabled.
     [/lang]
     * @type Boolean
     */
    TOUCH_ENABLED: (function() {
        var div = document.createElement('div');
        div.setAttribute('ontouchstart', 'return');
        return typeof div.ontouchstart === 'function';
    }()),
    /**
     [lang:ja]
     * 実行中の環境がRetina DisplayのiPhoneかどうか.
     [/lang]
     [lang:en]
     * Determines if the current browser is an iPhone with a retina display.
     * True, if this display is a retina display.
     [/lang]
     * @type Boolean
     */
    RETINA_DISPLAY: (function() {
        if (navigator.userAgent.indexOf('iPhone') !== -1 && window.devicePixelRatio === 2) {
            var viewport = document.querySelector('meta[name="viewport"]');
            if (viewport == null) {
                viewport = document.createElement('meta');
                document.head.appendChild(viewport);
            }
            viewport.setAttribute('content', 'width=640');
            return true;
        } else {
            return false;
        }
    }()),
    /**
     [lang:ja]
     * サウンドの再生にHTMLAudioElement/WebAudioの代わりにflashのプレーヤーを使うかどうか.
     [/lang]
     [lang:en]
     * Determines if for current browser Flash should be used to play
     * sound instead of the native audio class.
     * True, if flash should be used.
     [/lang]
     * @type Boolean
     */
    USE_FLASH_SOUND: (function() {
        var ua = navigator.userAgent;
        var vendor = navigator.vendor || "";
        // non-local access, not on mobile mobile device, not on safari
        return (location.href.indexOf('http') === 0 && ua.indexOf('Mobile') === -1 && vendor.indexOf('Apple') !== -1);
    }()),
    /**
     [lang:ja]
     * クリック/タッチ時の規定の動作を許可するhtmlタグ名.
     * ここに追加したhtmlタグへのイベントはpreventDefaultされない.
     [/lang]
     [lang:en]
     * If click/touch event occure for these tags the setPreventDefault() method will not be called.
     [/lang]
     * @type String[]
     */
    USE_DEFAULT_EVENT_TAGS: ['input', 'textarea', 'select', 'area'],
    /**
     [lang:ja]
     * SurfaceのメソッドとしてアクセスできるようにするCanvasRenderingContext2Dのメソッド.
     [/lang]
     [lang:en]
     * Method names of CanvasRenderingContext2D that will be defined as Surface method.
     [/lang]
     * @type String[]
     */
    CANVAS_DRAWING_METHODS: [
        'putImageData', 'drawImage', 'drawFocusRing', 'fill', 'stroke',
        'clearRect', 'fillRect', 'strokeRect', 'fillText', 'strokeText'
    ],
    /**
     [lang:ja]
     * キーバインドのテーブル.
     * デフォルトで 'left, 'up', 'right', 'down' のイベントが使用可能.
     * @example
     * enchant.ENV.KEY_BIND_TABLE = {
     *     37: 'left',
     *     38: 'up',
     *     39: 'right',
     *     40: 'down',
     *     32: 'a', //-> スペースキーをaボタンとして使う.
     * };
     [/lang]
     [lang:en]
     * Keybind Table.
     * You can use 'left', 'up', 'right', 'down' for preset event.
     * @example
     * enchant.ENV.KEY_BIND_TABLE = {
     *     37: 'left',
     *     38: 'up',
     *     39: 'right',
     *     40: 'down',
     *     32: 'a', //-> use 'space' key as 'a button'
     * };
     [/lang]
     * @type Object
     */
    KEY_BIND_TABLE: {
        37: 'left',
        38: 'up',
        39: 'right',
        40: 'down'
    },
    /**
     [lang:ja]
     * キー押下時の規定の動作を抑止するキーコード.
     * ここに追加したキーによるイベントはpreventDefaultされる.
     [/lang]
     [lang:en]
     * If keydown event occure for these keycodes the setPreventDefault() method will be called.
     [/lang]
     * @type Number[]
     */
    PREVENT_DEFAULT_KEY_CODES: [37, 38, 39, 40, 32],
    /**
     [lang:ja]
     * Mobile Safariでサウンドの再生を有効にするかどうか.
     [/lang]
     [lang:en]
     * Determines if Sound is enabled on Mobile Safari.
     [/lang]
     * @type Boolean
     */
    SOUND_ENABLED_ON_MOBILE_SAFARI: true,
    /**
     [lang:ja]
     * "touch to start" のシーンを使用するかどうか.
     * Mobile SafariでWebAudioのサウンドを再生するためには,
     * 一度タッチイベントハンドラ内で音声を流す必要があるため,
     * Mobile Safariでの実行時にはこのシーンが追加される.
     * falseにすることで, このシーンを表示しないようにできるが,
     * その場合は, 自身の責任でサウンドを有効化する必要がある.
     [/lang]
     [lang:en]
     * Determines if "touch to start" scene is enabled.
     * It is necessary on Mobile Safari because WebAudio Sound is
     * muted by browser until play any sound in touch event handler.
     * If set it to false, you should control this behavior manually.
     [/lang]
     * @type Boolean
     */
    USE_TOUCH_TO_START_SCENE: true,
    /**
     [lang:ja]
     * WebAudioを有効にするどうか.
     * trueならサウンドの再生の際HTMLAudioElementの代わりにWebAudioAPIを使用する.
     [/lang]
     [lang:en]
     * Determines if WebAudioAPI is enabled. (true: use WebAudioAPI instead of Audio element if possible)
     [/lang]
     * @type Boolean
     */
    USE_WEBAUDIO: (function() {
        return location.protocol !== 'file:';
    }()),
    /**
     [lang:ja]
     * アニメーション機能を有効にするかどうか.
     * trueだと, Node#tlにTimelineオブジェクトが作成される.
     [/lang]
     [lang:en]
     * Determines if animation feature is enabled. (true: Timeline instance will be generated in new Node)
     [/lang]
     * @type Boolean
     */
    USE_ANIMATION: true
};
<commit_msg>Space Key (32) from the enchant.ENV.KEY_BIND_TABLE example has been defined in the PREVENT_DEFAULT_KEY_CODES by default.<commit_after>/**
 * @namespace
 [lang:ja]
 * enchant.js の環境変数.
 * new Core() を呼ぶ前に変更することで変更することで, 動作設定を変えることができる.
 [/lang]
 [lang:en]
 * enchant.js environment variables.
 * Execution settings can be changed by modifying these before calling new Core().
 [/lang]
 [lang:de]
 * Umgebungsvariable.
 [/lang]
 */
enchant.ENV = {
    /**
     [lang:ja]
     * enchant.jsのバージョン.
     [/lang]
     [lang:en]
     * Version of enchant.js
     [/lang]
     * @type String
     */
    VERSION: '<%= pkg.version %>',
    /**
     [lang:ja]
     * 実行中のブラウザの種類.
     [/lang]
     [lang:en]
     * Identifier of the current browser.
     [/lang]
     * @type String
     */
    BROWSER: (function(ua) {
        if (/Eagle/.test(ua)) {
            return 'eagle';
        } else if (/Opera/.test(ua)) {
            return 'opera';
        } else if (/MSIE|Trident/.test(ua)) {
            return 'ie';
        } else if (/Chrome/.test(ua)) {
            return 'chrome';
        } else if (/(?:Macintosh|Windows).*AppleWebKit/.test(ua)) {
            return 'safari';
        } else if (/(?:iPhone|iPad|iPod).*AppleWebKit/.test(ua)) {
            return 'mobilesafari';
        } else if (/Firefox/.test(ua)) {
            return 'firefox';
        } else if (/Android/.test(ua)) {
            return 'android';
        } else {
            return '';
        }
    }(navigator.userAgent)),
    /**
     [lang:ja]
     * 実行中のブラウザに対応するCSSのベンダープレフィックス.
     [/lang]
     [lang:en]
     * The CSS vendor prefix of the current browser.
     [/lang]
     * @type String
     */
    VENDOR_PREFIX: (function() {
        var ua = navigator.userAgent;
        if (ua.indexOf('Opera') !== -1) {
            return 'O';
        } else if (/MSIE|Trident/.test(ua)) {
            return 'ms';
        } else if (ua.indexOf('WebKit') !== -1) {
            return 'webkit';
        } else if (navigator.product === 'Gecko') {
            return 'Moz';
        } else {
            return '';
        }
    }()),
    /**
     [lang:ja]
     * ブラウザがタッチ入力をサポートしているかどうか.
     [/lang]
     [lang:en]
     * Determines if the current browser supports touch.
     * True, if touch is enabled.
     [/lang]
     * @type Boolean
     */
    TOUCH_ENABLED: (function() {
        var div = document.createElement('div');
        div.setAttribute('ontouchstart', 'return');
        return typeof div.ontouchstart === 'function';
    }()),
    /**
     [lang:ja]
     * 実行中の環境がRetina DisplayのiPhoneかどうか.
     [/lang]
     [lang:en]
     * Determines if the current browser is an iPhone with a retina display.
     * True, if this display is a retina display.
     [/lang]
     * @type Boolean
     */
    RETINA_DISPLAY: (function() {
        if (navigator.userAgent.indexOf('iPhone') !== -1 && window.devicePixelRatio === 2) {
            var viewport = document.querySelector('meta[name="viewport"]');
            if (viewport == null) {
                viewport = document.createElement('meta');
                document.head.appendChild(viewport);
            }
            viewport.setAttribute('content', 'width=640');
            return true;
        } else {
            return false;
        }
    }()),
    /**
     [lang:ja]
     * サウンドの再生にHTMLAudioElement/WebAudioの代わりにflashのプレーヤーを使うかどうか.
     [/lang]
     [lang:en]
     * Determines if for current browser Flash should be used to play
     * sound instead of the native audio class.
     * True, if flash should be used.
     [/lang]
     * @type Boolean
     */
    USE_FLASH_SOUND: (function() {
        var ua = navigator.userAgent;
        var vendor = navigator.vendor || "";
        // non-local access, not on mobile mobile device, not on safari
        return (location.href.indexOf('http') === 0 && ua.indexOf('Mobile') === -1 && vendor.indexOf('Apple') !== -1);
    }()),
    /**
     [lang:ja]
     * クリック/タッチ時の規定の動作を許可するhtmlタグ名.
     * ここに追加したhtmlタグへのイベントはpreventDefaultされない.
     [/lang]
     [lang:en]
     * If click/touch event occure for these tags the setPreventDefault() method will not be called.
     [/lang]
     * @type String[]
     */
    USE_DEFAULT_EVENT_TAGS: ['input', 'textarea', 'select', 'area'],
    /**
     [lang:ja]
     * SurfaceのメソッドとしてアクセスできるようにするCanvasRenderingContext2Dのメソッド.
     [/lang]
     [lang:en]
     * Method names of CanvasRenderingContext2D that will be defined as Surface method.
     [/lang]
     * @type String[]
     */
    CANVAS_DRAWING_METHODS: [
        'putImageData', 'drawImage', 'drawFocusRing', 'fill', 'stroke',
        'clearRect', 'fillRect', 'strokeRect', 'fillText', 'strokeText'
    ],
    /**
     [lang:ja]
     * キーバインドのテーブル.
     * デフォルトで 'left, 'up', 'right', 'down' のイベントが使用可能.
     * @example
     * enchant.ENV.KEY_BIND_TABLE = {
     *     37: 'left',
     *     38: 'up',
     *     39: 'right',
     *     40: 'down',
     *     32: 'a', //-> スペースキーをaボタンとして使う.
     * };
     [/lang]
     [lang:en]
     * Keybind Table.
     * You can use 'left', 'up', 'right', 'down' for preset event.
     * @example
     * enchant.ENV.KEY_BIND_TABLE = {
     *     37: 'left',
     *     38: 'up',
     *     39: 'right',
     *     40: 'down',
     *     32: 'a', //-> use 'space' key as 'a button'
     * };
     [/lang]
     * @type Object
     */
    KEY_BIND_TABLE: {
        37: 'left',
        38: 'up',
        39: 'right',
        40: 'down'
    },
    /**
     [lang:ja]
     * キー押下時の規定の動作を抑止するキーコード.
     * ここに追加したキーによるイベントはpreventDefaultされる.
     [/lang]
     [lang:en]
     * If keydown event occure for these keycodes the setPreventDefault() method will be called.
     [/lang]
     * @type Number[]
     */
    PREVENT_DEFAULT_KEY_CODES: [37, 38, 39, 40],
    /**
     [lang:ja]
     * Mobile Safariでサウンドの再生を有効にするかどうか.
     [/lang]
     [lang:en]
     * Determines if Sound is enabled on Mobile Safari.
     [/lang]
     * @type Boolean
     */
    SOUND_ENABLED_ON_MOBILE_SAFARI: true,
    /**
     [lang:ja]
     * "touch to start" のシーンを使用するかどうか.
     * Mobile SafariでWebAudioのサウンドを再生するためには,
     * 一度タッチイベントハンドラ内で音声を流す必要があるため,
     * Mobile Safariでの実行時にはこのシーンが追加される.
     * falseにすることで, このシーンを表示しないようにできるが,
     * その場合は, 自身の責任でサウンドを有効化する必要がある.
     [/lang]
     [lang:en]
     * Determines if "touch to start" scene is enabled.
     * It is necessary on Mobile Safari because WebAudio Sound is
     * muted by browser until play any sound in touch event handler.
     * If set it to false, you should control this behavior manually.
     [/lang]
     * @type Boolean
     */
    USE_TOUCH_TO_START_SCENE: true,
    /**
     [lang:ja]
     * WebAudioを有効にするどうか.
     * trueならサウンドの再生の際HTMLAudioElementの代わりにWebAudioAPIを使用する.
     [/lang]
     [lang:en]
     * Determines if WebAudioAPI is enabled. (true: use WebAudioAPI instead of Audio element if possible)
     [/lang]
     * @type Boolean
     */
    USE_WEBAUDIO: (function() {
        return location.protocol !== 'file:';
    }()),
    /**
     [lang:ja]
     * アニメーション機能を有効にするかどうか.
     * trueだと, Node#tlにTimelineオブジェクトが作成される.
     [/lang]
     [lang:en]
     * Determines if animation feature is enabled. (true: Timeline instance will be generated in new Node)
     [/lang]
     * @type Boolean
     */
    USE_ANIMATION: true
};
<|endoftext|>


<commit_before>var io, exec, Emitify, loadRemote;

(function(global) {
    'use strict';

    if (typeof module !== 'undefined' && module.exports)
        module.exports  = new SperoProto();
    else
        global.spero    = new SperoProto();

    function SperoProto() {
        function load(prefix, callback) {
            if (!callback) {
                callback    = prefix;
                prefix      = '/spero';
            }

            loadAll(prefix, function() {
                init();

                if (typeof callback === 'function')
                    callback(Spero(prefix));
            });
        }

        function Spero(prefix) {
            if (!(this instanceof Spero))
                return new Spero(prefix);

            Emitify.call(this);
            this._progress = ProgressProto(prefix, this);
        }

        function init() {
            Spero.prototype = Object.create(Emitify.prototype);

            Spero.prototype.copy  = function(from, to, files) {
                this._progress.copy(from, to, files);
            };

            Spero.prototype.abort = function() {
                this._progress.abort();
            };

            Spero.prototype.pause = function() {
                this._progress.pause();
            };

            Spero.prototype.continue = function() {
                this._progress.continue();
            };
        }

        function loadAll(prefix, callback) {
            var scripts = [];

            if (!exec)
                scripts.push('/modules/execon/lib/exec.js');

            if (!scripts.length)
                loadFiles(prefix, callback);
            else
                loadScript(scripts.map(function(name) {
                    return prefix + name;
                }), function() {
                    loadFiles(prefix, callback);
                });
        }

        function getModulePath(name, lib) {
            var path    = '',
                libdir  = '/',
                dir     = '/modules/';

            if (lib)
                libdir  = '/' + lib + '/';

            path    = dir + name + libdir + name + '.js';

            return path;
        }

        function loadFiles(prefix, callback) {
            exec.series([
                function(callback) {
                    var obj     = {
                            loadRemote  : getModulePath('loadremote', 'lib'),
                            load        : getModulePath('load'),
                            Emitify     : getModulePath('emitify', 'lib'),
                            join        : '/join/join.js'
                        },

                        scripts = Object.keys(obj)
                            .filter(function(name) {
                                return !window[name];
                            })
                            .map(function(name) {
                                return prefix + obj[name];
                            });

                    exec.if(!scripts.length, callback, function() {
                        loadScript(scripts, callback);
                    });
                },

                function(callback) {
                    loadRemote('socket', {
                        name : 'io',
                        prefix: prefix,
                        noPrefix: true
                    }, callback);
                },

                function() {
                    callback();
                }
            ]);
        }

        function loadScript(srcs, callback) {
            var i       = srcs.length,
                func    = function() {
                    --i;

                    if (!i)
                        callback();
                };

            srcs.forEach(function(src) {
                var element = document.createElement('script');

                element.src = src;
                element.addEventListener('load', function load() {
                    func();
                    element.removeEventListener('load', load);
                });

                document.body.appendChild(element);
            });
        }

        function ProgressProto(room, spero) {
            var socket;

            if (!(this instanceof ProgressProto))
                return new ProgressProto(room, spero);

            init(room);

            function init(room) {
                var href            = getHost(),
                    FIVE_SECONDS    = 5000;

                socket = io.connect(href + room, {
                    'max reconnection attempts' : Math.pow(2, 32),
                    'reconnection limit'        : FIVE_SECONDS
                });

                socket.on('err', function(error) {
                    spero.emit('error', error);
                });

                socket.on('file', function(name) {
                    spero.emit('file', name);
                });

                socket.on('progress', function(percent) {
                    spero.emit('progress', percent);
                });

                socket.on('end', function() {
                    spero.emit('end');
                });

                socket.on('connect', function() {
                    spero.emit('connect');
                });

                socket.on('disconnect', function() {
                    spero.emit('disconnect');
                });
            }

            this.pause       = function() {
                socket.emit('pause');
            };

            this.continue   = function() {
                socket.emit('continue');
            };

            this.abort       = function() {
                socket.emit('abort');
            };

            this.copy       = function(from, to, files) {
                socket.emit('copy', from, to, files);
            };

            function getHost() {
                var l       = location,
                    href    = l.origin || l.protocol + '//' + l.host;

                return href;
            }
        }

        return load;
    }

})(this);
<commit_msg>feature(spero) ProgressProto: rm init<commit_after>var io, exec, Emitify, loadRemote;

(function(global) {
    'use strict';

    if (typeof module !== 'undefined' && module.exports)
        module.exports  = new SperoProto();
    else
        global.spero    = new SperoProto();

    function SperoProto() {
        function load(prefix, callback) {
            if (!callback) {
                callback    = prefix;
                prefix      = '/spero';
            }

            loadAll(prefix, function() {
                init();

                if (typeof callback === 'function')
                    callback(Spero(prefix));
            });
        }

        function Spero(prefix) {
            if (!(this instanceof Spero))
                return new Spero(prefix);

            Emitify.call(this);
            this._progress = ProgressProto(prefix, this);
        }

        function init() {
            Spero.prototype = Object.create(Emitify.prototype);

            Spero.prototype.copy  = function(from, to, files) {
                this._progress.copy(from, to, files);
            };

            Spero.prototype.abort = function() {
                this._progress.abort();
            };

            Spero.prototype.pause = function() {
                this._progress.pause();
            };

            Spero.prototype.continue = function() {
                this._progress.continue();
            };
        }

        function loadAll(prefix, callback) {
            var scripts = [];

            if (!exec)
                scripts.push('/modules/execon/lib/exec.js');

            if (!scripts.length)
                loadFiles(prefix, callback);
            else
                loadScript(scripts.map(function(name) {
                    return prefix + name;
                }), function() {
                    loadFiles(prefix, callback);
                });
        }

        function getModulePath(name, lib) {
            var path    = '',
                libdir  = '/',
                dir     = '/modules/';

            if (lib)
                libdir  = '/' + lib + '/';

            path    = dir + name + libdir + name + '.js';

            return path;
        }

        function loadFiles(prefix, callback) {
            exec.series([
                function(callback) {
                    var obj     = {
                            loadRemote  : getModulePath('loadremote', 'lib'),
                            load        : getModulePath('load'),
                            Emitify     : getModulePath('emitify', 'lib'),
                            join        : '/join/join.js'
                        },

                        scripts = Object.keys(obj)
                            .filter(function(name) {
                                return !window[name];
                            })
                            .map(function(name) {
                                return prefix + obj[name];
                            });

                    exec.if(!scripts.length, callback, function() {
                        loadScript(scripts, callback);
                    });
                },

                function(callback) {
                    loadRemote('socket', {
                        name : 'io',
                        prefix: prefix,
                        noPrefix: true
                    }, callback);
                },

                function() {
                    callback();
                }
            ]);
        }

        function loadScript(srcs, callback) {
            var i       = srcs.length,
                func    = function() {
                    --i;

                    if (!i)
                        callback();
                };

            srcs.forEach(function(src) {
                var element = document.createElement('script');

                element.src = src;
                element.addEventListener('load', function load() {
                    func();
                    element.removeEventListener('load', load);
                });

                document.body.appendChild(element);
            });
        }

        function ProgressProto(room, spero) {
            var socket,
                href            = getHost(),
                FIVE_SECONDS    = 5000;

            if (!(this instanceof ProgressProto))
                return new ProgressProto(room, spero);

            socket = io.connect(href + room, {
                'max reconnection attempts' : Math.pow(2, 32),
                'reconnection limit'        : FIVE_SECONDS
            });

            socket.on('err', function(error) {
                spero.emit('error', error);
            });

            socket.on('file', function(name) {
                spero.emit('file', name);
            });

            socket.on('progress', function(percent) {
                spero.emit('progress', percent);
            });

            socket.on('end', function() {
                spero.emit('end');
            });

            socket.on('connect', function() {
                spero.emit('connect');
            });

            socket.on('disconnect', function() {
                spero.emit('disconnect');
            });

            this.pause       = function() {
                socket.emit('pause');
            };

            this.continue   = function() {
                socket.emit('continue');
            };

            this.abort       = function() {
                socket.emit('abort');
            };

            this.copy       = function(from, to, files) {
                socket.emit('copy', from, to, files);
            };

            function getHost() {
                var l       = location,
                    href    = l.origin || l.protocol + '//' + l.host;

                return href;
            }
        }

        return load;
    }

})(this);
<|endoftext|>


<commit_before>module("Transformations", {
    setup : function() {
        stop();
        var that = this;
        this.cb = function(e) {
            ok(true, "Scene loaded");
            that.doc = document.getElementById("xml3dframe").contentDocument;
            start();
        };
        loadDocument("scenes/transformations.xhtml"+window.location.search, this.cb);
    },
    teardown : function() {
        var v = document.getElementById("xml3dframe");
        v.removeEventListener("load", this.cb, true);
    }
});

test("Local Transformations", 4, function() {
    var xTest = this.doc.getElementById("xml3dTest"),
        glTest = getContextForXml3DElement(xTest), hTest = getHandler(xTest);
    var self = this;

    var trans = this.doc.getElementById("group_transform2");
    var css = this.doc.getElementById("css_transform2");
    var xflow = this.doc.getElementById("xflow_tranform2");

    function onFrameDrawn(){
        QUnit.closeMatrix(css.getLocalMatrix(), trans.getLocalMatrix(), EPSILON, "CSS and <transform> equal");
        QUnit.closeMatrix(xflow.getLocalMatrix(), trans.getLocalMatrix(), EPSILON, "CSS and <data> equal");
        start();
    }
    xTest.addEventListener("framedrawn", onFrameDrawn);
    stop();
    hTest.draw();
});

test("Global Transformations", 4, function() {
    var xTest = this.doc.getElementById("xml3dTest"),
        glTest = getContextForXml3DElement(xTest), hTest = getHandler(xTest);
    var self = this;

    var trans = this.doc.getElementById("group_transform1");
    var css = this.doc.getElementById("css_transform1");
    var xflow = this.doc.getElementById("xflow_tranform1");

    function onFrameDrawn(){
        QUnit.closeMatrix(css.getWorldMatrix(), trans.getWorldMatrix(), EPSILON, "CSS and <transform> equal");
        QUnit.closeMatrix(xflow.getWorldMatrix(), trans.getWorldMatrix(), EPSILON, "CSS and <data> equal");
        start();
    }
    xTest.addEventListener("framedrawn", onFrameDrawn);
    stop();
    hTest.draw();
});<commit_msg>Added test for redraw triggering on transform change<commit_after>module("Transformations", {
    setup : function() {
        stop();
        var that = this;
        this.cb = function(e) {
            ok(true, "Scene loaded");
            that.doc = document.getElementById("xml3dframe").contentDocument;
            start();
        };
        loadDocument("scenes/transformations.xhtml"+window.location.search, this.cb);
    },
    teardown : function() {
        var v = document.getElementById("xml3dframe");
        v.removeEventListener("load", this.cb, true);
    }
});

test("Local Transformations", 4, function() {
    var xTest = this.doc.getElementById("xml3dTest"),
        glTest = getContextForXml3DElement(xTest), hTest = getHandler(xTest);
    var self = this;

    var trans = this.doc.getElementById("group_transform2");
    var css = this.doc.getElementById("css_transform2");
    var xflow = this.doc.getElementById("xflow_tranform2");

    function onFrameDrawn(){
        QUnit.closeMatrix(css.getLocalMatrix(), trans.getLocalMatrix(), EPSILON, "CSS and <transform> equal");
        QUnit.closeMatrix(xflow.getLocalMatrix(), trans.getLocalMatrix(), EPSILON, "CSS and <data> equal");
        start();
    }
    xTest.addEventListener("framedrawn", onFrameDrawn);
    stop();
    hTest.draw();
});

test("Global Transformations", 4, function() {
    var xTest = this.doc.getElementById("xml3dTest"),
        glTest = getContextForXml3DElement(xTest), hTest = getHandler(xTest);
    var self = this;

    var trans = this.doc.getElementById("group_transform1");
    var css = this.doc.getElementById("css_transform1");
    var xflow = this.doc.getElementById("xflow_tranform1");

    function onFrameDrawn(){
        QUnit.closeMatrix(css.getWorldMatrix(), trans.getWorldMatrix(), EPSILON, "CSS and <transform> equal");
        QUnit.closeMatrix(xflow.getWorldMatrix(), trans.getWorldMatrix(), EPSILON, "CSS and <data> equal");
        start();
    }
    xTest.addEventListener("framedrawn", onFrameDrawn);
    stop();
    hTest.draw();
});

test("Redraw triggers", 5, function() {
    var xTest = this.doc.getElementById("xml3dTest"),
        glTest = getContextForXml3DElement(xTest), hTest = getHandler(xTest);
    var self = this;
    var renderer = hTest.renderer;

    var camtrans = this.doc.getElementById("t1");
    var grouptrans = this.doc.getElementById("xfm");
    hTest.draw();

    renderer.needsDraw = false;
    renderer.needsPickingDraw = false;
    camtrans.setAttribute("translation", "0 0 -9");
    ok(renderer.needsDraw && renderer.needsPickingDraw, "Camera transformation change needs full redraw");
    hTest.draw();
    ok(!renderer.needsDraw, "Redraw flag was set back after draw");
    renderer.needsPickingDraw = false;

    grouptrans.setAttribute("translation", "-3 0 -9");
    ok(renderer.needsDraw && renderer.needsPickingDraw, "Group transformation change needs full redraw");
});<|endoftext|>


<commit_before>var async = require('async'),
    crypto = require('crypto'),
    request = require('request'),
    RateLimiter = require('limiter').RateLimiter;
    util = require('util');

var logger = require('../logger.js'),
    order = require('../order.js'),
    transaction = require('../transaction.js'),
    counter = require('../util.js').counter,
    pager = require('../util.js').pager,
    mergeObjects = require('../util.js').mergeObjects,
    BaseExchange = require('./base.js').BaseExchange;

var BitfinexClient = module.exports.BitfinexClient = function(key, secret) {
    this.version = 'v1'
    this.url = 'https://api.bitfinex.com/' + this.version;
    this.key = key || '';
    this.secret = secret || '';
    this.getNonce = counter(+new Date());
};

/**
 * Implement Bitfinex's strange header-based payloads.
 *
 * See: https://www.bitfinex.com/pages/api
 */
BitfinexClient.prototype.encode = function(params) {
    var payload = new Buffer(JSON.stringify(params)).toString('base64');
    var signature = crypto.createHmac('sha384', this.secret).update(payload).digest('hex');
    var headers = {
        'X-BFX-APIKEY': this.key,
        'X-BFX-PAYLOAD': payload,
        'X-BFX-SIGNATURE': signature
    };

    // We discard the form params, because they live in headers now. ¯\_(ツ)_/¯
    return headers;
};

BitfinexClient.prototype.call = function(method, resource, params, cb) {
    if (cb === undefined && typeof(params) == 'function') {
        cb = params;
        params = {};
    }

    var req = {
        'url': this.url + resource,
        'json': true
    };

    if (method == 'GET') {
        req['qs'] = params;
    } else {
        params['request'] = '/' + this.version + resource;
        params['nonce'] = String(this.getNonce());
        req['headers'] = this.encode(params);
    }

    return request(req, cb);
};

/**
 * Liquid trading interface to Bitfinex.
 *
 * @param {BitfinexClient} client
 * @param {number} delay Number of milliseconds to delay between ticks (default: 1000)
 * @param {boolean} pretend Don't execute any trades, just watch.
 */
var BitfinexExchange = module.exports.BitfinexExchange = function(client, delay, pretend) {
    BitfinexExchange.super_.call(this, pretend, delay);

    this.client = client;

    // Use this rate limiter whenever using client.
    this.rateLimiter = new RateLimiter(55, 'minute'); // Actually 60 but we'll be careful.

    // This can be switched to async.map if nonce doesn't need to be strictly-incremental.
    // Use async.mapSeries if nonce must be strictly-incrementing, async.map otherwise.
    this.requestMap = async.mapSeries;
};

util.inherits(BitfinexExchange, BaseExchange);

BitfinexExchange.id = 'bitfinex';
BitfinexExchange.configKeys = ['BITFINEX_KEY', 'BITFINEX_SECRET'];

/**
 * Instantiate necessary clients based on configuration and return a
 * {@code BitfinexExchange} instance.
 *
 * @static
 * @param {{BITFINEX_KEY: string, BITFINEX_SECRET: string}}
 * @return {BitfinexExchange}
 */
BitfinexExchange.fromConfig = function(config) {
    var client = new BitfinexClient(config.apiKeys.BITFINEX_KEY, config.apiKeys.BITFINEX_SECRET);

    return new BitfinexExchange(client, config.tickDelay || 1000, config.pretend);
};

/**
 * Execute a unit of polling.
 */
BitfinexExchange.prototype.tick = function() {
    var exchange = this;

    var params = {
        // By default, the API returns ~1600 results.
        'limit_bids': 20,
        'limit_asks': 20
    };
    exchange.client.call('GET', '/book/btcusd', params, function(err, response, data) {
        exchange.requestLock.release();

        // We only care about our orders.
        var err = err || toError(data);
        if (err) {
            logger.error('Failure during BitfinexExchange polling:', err.message);
            return;
        }

        exchange.emit('orderbook', toOrderbook(data));
    });
};

/**
 * Authenticate to the API and start polling loop.
 *
 * @param {function=} callback Function called upon completion.
 */
BitfinexExchange.prototype.ready = function(callback) {
    this.debug('Preparing.');
    this.cleanup();

    var exchange = this;
    async.series([
        function rateLimit(callback) {
            exchange.rateLimiter.removeTokens(2, callback);
        },
        function loadOrders(callback) {
            logger.warn('XXX: Skipping loadOrders pending account activation.)');
            return callback.call(exchange);

            exchange.client.call('POST', '/orders', function(err, response, data) {
                var err = err || toError(data);
                if (err) {
                    logger.error('Failed to load BitfinexExchange placed orders:', err.message);
                    callback.call(exchange, err);
                    return;
                }

                var placedOrders = data.map(toOrder);
                placedOrders.forEach(function(order) {
                    exchange.saveOrder(order);
                });

                if (placedOrders.length) {
                    exchange.debug('Loaded placed orders:', placedOrders.length);
                }
                callback.call(exchange, err);
            });
        },
        function checkBalance(callback) {
            // We don't really care what the minimums are at this point, we just
            // want to update the internal balance values.
            var minQuantity, minValue;
            exchange.checkBalance(minQuantity, minValue, callback);
        },
        function ready(callback) {
            BitfinexExchange.super_.prototype.ready.call(exchange, callback);
        }
    ], callback);
};

/**
 * Check whether there is sufficient balance. Callback with err if not.
 *
 * @param {number=} minQuantity Minimum BTC to expect in balance.
 * @param {number=} minValue Minimum USD to expect in balance.
 * @param {function=} callback Function called upon completion.
 */
BitfinexExchange.prototype.checkBalance = function(minQuantity, minValue, callback) {
    var exchange = this;
    exchange.client.call('POST', '/balances', function(err, response, data) {
        var err = err || toError(data);
        if (err) {
            logger.error('Failed to load BitfinexExchange balance:', err);
            callback && callback.call(exchange, err);
            return;
        }

        var balance = toBalance(data); // XXX: Fill this in.
        exchange.setBalance(balance.quantity, balance.value);

        if (minQuantity && exchange.balance.quantity.lt(minQuantity)) {
            err = new Error('Insufficient BTC balance on Bitfinex: ' + exchange.balance.quantity.toString());
        } else if (minValue && exchange.balance.value.lt(minValue)) {
            err = new Error('Insufficient USD balance on Bitfinex: ' + exchange.balance.quantity.toString());
        }

        callback && callback.call(exchange, err);
    });
};

/**
 * Place an instruction of orders for this exchange.
 *
 * @param {Array.<Order>} orders List of orders to place concurrently.
 * @param {function} callback
 */
BitfinexExchange.prototype.placeOrders = function(orders, callback) {
    var exchange = this;

    if (exchange.pretend) {
        orders.map(function(order) {
            logger.info('[exchange:bitfinex] Pretend placeOrder:', order.toString());
            exchange.saveOrder(order);
        });
        return;
    }

    var side = {
        'ASK': 'sell',
        'BID': 'buy'
    }[order.type];

    // Bitfinex API limits multi operations to 10 orders.
    var pagedOrders = pager(orders, 10);
    exchange.requestMap(pagedOrders, function(page, callback) {
        var params = page.map(function(order) {
            return {
                'symbol': 'btcusd',     // symbol (string): The name of the symbol (see `/symbols`).
                'amount': order.quantity.toFixed(), // amount (decimal): Order size: how much to buy or sell.
                'price': order.rate.toFixed(3),     // price (price): Price to buy or sell at. May omit if a market order.
                'exchange': 'all',      // exchange (string): "bitfinex", "bitstamp", "all" (for no routing).
                'side': side,           // side (string): Either "buy" or "sell".
                'type': 'limit',        // type (string): Either "market" / "limit" / "stop" / "trailing-stop" / "fill-or-kill".
            };
        });

        exchange.rateLimiter.removeTokens(1, function() {
            exchange.requestLock.acquire(true /* exclusive */, function() {
                exchange.client.call('POST', '/order/new/multi', params, function(err, response, data) {
                    var err = err || toError(data);
                    if (err) {
                        callback(err, response, body);
                        return;
                    }

                    if (data.length !== page.length) {
                        // Bitfinex only returns order ids, so we assume they're the same. :/
                        callback(new Error('Bitfinex returned incorrect number of placed orders: ' + data.length + ' of ' + page.length));
                    }

                    page.map(function(order, i) {
                        var newOrder = order.clone({id: data[i]});
                        exchange.debug('Placed %s (from: %s)', newOrder.toString(), order.toString());
                        exchange.saveOrder(newOrder);
                    });

                    callback();
                });
            });
        });
    }, callback);
};

/**
 * Cancel orders.
 *
 * @param {Array.<Order>} orders List of orders to cancel concurrently.
 * @param {function} callback
 */
BitfinexExchange.prototype.cancelOrders = function(orders, callback) {
    var exchange = this;

    if (exchange.pretend) {
        orders.map(function(order) {
            logger.info('[exchange:bitfinex] Pretend cancelOrder:', order.toString());
            exchange.deleteOrder(order);
        });
        return;
    }

    // Bitfinex API limits multi operations to 10 orders.
    var pagedOrders = pager(orders, 10);
    exchange.requestMap(pagedOrders, function(page, callback) {
        var params = {
            'order_ids': page.map(function(order) {
                return order.id
            })
        };

        exchange.rateLimiter.removeTokens(1, function() {
            exchange.requestLock.acquire(true /* exclusive */, function() {
                exchange.client.call('POST', '/order/cancel/multi', params, function(err, response, data) {
                    var err = err || toError(data);
                    if (err) {
                        callback(err, response, body);
                        return;
                    }

                    // XXX: Not clear what this is supposed to return.

                    callback();
                });
            });
        });
    }, callback);

};

/**
 * Given a Bitfinex order (as returned by the API), return a corresponding
 * {@code Order} object.
 *
 * @static
 * @param {object} d
 * @param {boolean=} skipExecuted Don't count executed amount.
 * @return {Order}
 */
var toOrder = BitfinexExchange.toOrder = function(d, skipExecuted) {
    try{
        var amount = d.original_amount;
        if (!skipExecuted) amount = d.remaining_amount;
        var o = new order.Order(d.order_id, d.side.toUpperCase(), d.quantity, d.price, null, 'bitfinex');
    } catch (e) {
        logger.warn('Failed to convert Bitfinex order:', d);
        throw e;
    }
    return o;
};

/**
 * Given a Bitfinex orderbook (as returned by the API), return a corresponding
 * orderbook.
 *
 * @static
 * @param {object} d
 * @return {{asks: Array.<Order>, bids: Array.<Order>}}
 */
var toOrderbook = BitfinexExchange.toOrderbook = function(d) {
    return {
        'asks': d.asks.map(function(o) {
            return new order.Order(null, 'ASK', o.amount, o.price, null, 'bitfinex');
        }),
        'bids': d.bids.map(function(o) {
            return new order.Order(null, 'BID', o.amount, o.price, null, 'bitfinex');
        })
    };
};

/**
 * Given a Bitfinex accounts list (as returned by the API), return normalized
 * parameters for {@code BaseExchange.prototype.setBalance}.
 *
 * @static
 * @param {object} accounts
 * @return {{value: string, quantity: string}}
 */
var toBalance = BitfinexExchange.toBalance = function(balance) {
    var args = {
        'value': '0.00000000000000000000',
        'quantity': '0.00000000000000000000'
    };
    balance.forEach(function(account) {
        var type = account['type'];
        var key = {'deposit': 'value', 'exchange': 'quantity'}[type];
        if (!key) return; // Skip unknown currencies.
        args[key] = account['available'];
    });
    return args;
};

/**
 * Given a Bitfinex response body as parsed JSON, return an {@code Error}
 * instace if it contains an error, or null otherwise.
 *
 * @static
 * @param {object}
 * @return {Error|null}
 */
var toError = BitfinexExchange.toError = function(r) {
    if (!r || !r.message) return null;

    return new Error(r.message);
};
<commit_msg>Enable BitfinexExchange.loadOrders<commit_after>var async = require('async'),
    crypto = require('crypto'),
    request = require('request'),
    RateLimiter = require('limiter').RateLimiter;
    util = require('util');

var logger = require('../logger.js'),
    order = require('../order.js'),
    transaction = require('../transaction.js'),
    counter = require('../util.js').counter,
    pager = require('../util.js').pager,
    mergeObjects = require('../util.js').mergeObjects,
    BaseExchange = require('./base.js').BaseExchange;

var BitfinexClient = module.exports.BitfinexClient = function(key, secret) {
    this.version = 'v1'
    this.url = 'https://api.bitfinex.com/' + this.version;
    this.key = key || '';
    this.secret = secret || '';
    this.getNonce = counter(+new Date());
};

/**
 * Implement Bitfinex's strange header-based payloads.
 *
 * See: https://www.bitfinex.com/pages/api
 */
BitfinexClient.prototype.encode = function(params) {
    var payload = new Buffer(JSON.stringify(params)).toString('base64');
    var signature = crypto.createHmac('sha384', this.secret).update(payload).digest('hex');
    var headers = {
        'X-BFX-APIKEY': this.key,
        'X-BFX-PAYLOAD': payload,
        'X-BFX-SIGNATURE': signature
    };

    // We discard the form params, because they live in headers now. ¯\_(ツ)_/¯
    return headers;
};

BitfinexClient.prototype.call = function(method, resource, params, cb) {
    if (cb === undefined && typeof(params) == 'function') {
        cb = params;
        params = {};
    }

    var req = {
        'url': this.url + resource,
        'json': true
    };

    if (method == 'GET') {
        req['qs'] = params;
    } else {
        params['request'] = '/' + this.version + resource;
        params['nonce'] = String(this.getNonce());
        req['headers'] = this.encode(params);
    }

    return request(req, cb);
};

/**
 * Liquid trading interface to Bitfinex.
 *
 * @param {BitfinexClient} client
 * @param {number} delay Number of milliseconds to delay between ticks (default: 1000)
 * @param {boolean} pretend Don't execute any trades, just watch.
 */
var BitfinexExchange = module.exports.BitfinexExchange = function(client, delay, pretend) {
    BitfinexExchange.super_.call(this, pretend, delay);

    this.client = client;

    // Use this rate limiter whenever using client.
    this.rateLimiter = new RateLimiter(55, 'minute'); // Actually 60 but we'll be careful.

    // This can be switched to async.map if nonce doesn't need to be strictly-incremental.
    // Use async.mapSeries if nonce must be strictly-incrementing, async.map otherwise.
    this.requestMap = async.mapSeries;
};

util.inherits(BitfinexExchange, BaseExchange);

BitfinexExchange.id = 'bitfinex';
BitfinexExchange.configKeys = ['BITFINEX_KEY', 'BITFINEX_SECRET'];

/**
 * Instantiate necessary clients based on configuration and return a
 * {@code BitfinexExchange} instance.
 *
 * @static
 * @param {{BITFINEX_KEY: string, BITFINEX_SECRET: string}}
 * @return {BitfinexExchange}
 */
BitfinexExchange.fromConfig = function(config) {
    var client = new BitfinexClient(config.apiKeys.BITFINEX_KEY, config.apiKeys.BITFINEX_SECRET);

    return new BitfinexExchange(client, config.tickDelay || 1000, config.pretend);
};

/**
 * Execute a unit of polling.
 */
BitfinexExchange.prototype.tick = function() {
    var exchange = this;

    var params = {
        // By default, the API returns ~1600 results.
        'limit_bids': 20,
        'limit_asks': 20
    };
    exchange.client.call('GET', '/book/btcusd', params, function(err, response, data) {
        exchange.requestLock.release();

        // We only care about our orders.
        var err = err || toError(data);
        if (err) {
            logger.error('Failure during BitfinexExchange polling:', err.message);
            return;
        }

        exchange.emit('orderbook', toOrderbook(data));
    });
};

/**
 * Authenticate to the API and start polling loop.
 *
 * @param {function=} callback Function called upon completion.
 */
BitfinexExchange.prototype.ready = function(callback) {
    this.debug('Preparing.');
    this.cleanup();

    var exchange = this;
    async.series([
        function rateLimit(callback) {
            exchange.rateLimiter.removeTokens(2, callback);
        },
        function loadOrders(callback) {
            exchange.client.call('POST', '/orders', function(err, response, data) {
                var err = err || toError(data);
                if (err) {
                    logger.error('Failed to load BitfinexExchange placed orders:', err.message);
                    callback.call(exchange, err);
                    return;
                }

                var placedOrders = data.map(toOrder);
                placedOrders.forEach(function(order) {
                    exchange.saveOrder(order);
                });

                if (placedOrders.length) {
                    exchange.debug('Loaded placed orders:', placedOrders.length);
                }
                callback.call(exchange, err);
            });
        },
        function checkBalance(callback) {
            // We don't really care what the minimums are at this point, we just
            // want to update the internal balance values.
            var minQuantity, minValue;
            exchange.checkBalance(minQuantity, minValue, callback);
        },
        function ready(callback) {
            BitfinexExchange.super_.prototype.ready.call(exchange, callback);
        }
    ], callback);
};

/**
 * Check whether there is sufficient balance. Callback with err if not.
 *
 * @param {number=} minQuantity Minimum BTC to expect in balance.
 * @param {number=} minValue Minimum USD to expect in balance.
 * @param {function=} callback Function called upon completion.
 */
BitfinexExchange.prototype.checkBalance = function(minQuantity, minValue, callback) {
    var exchange = this;
    exchange.client.call('POST', '/balances', function(err, response, data) {
        var err = err || toError(data);
        if (err) {
            logger.error('Failed to load BitfinexExchange balance:', err);
            callback && callback.call(exchange, err);
            return;
        }

        var balance = toBalance(data);
        exchange.setBalance(balance.quantity, balance.value);

        if (minQuantity && exchange.balance.quantity.lt(minQuantity)) {
            err = new Error('Insufficient BTC balance on Bitfinex: ' + exchange.balance.quantity.toString());
        } else if (minValue && exchange.balance.value.lt(minValue)) {
            err = new Error('Insufficient USD balance on Bitfinex: ' + exchange.balance.quantity.toString());
        }

        callback && callback.call(exchange, err);
    });
};

/**
 * Place an instruction of orders for this exchange.
 *
 * @param {Array.<Order>} orders List of orders to place concurrently.
 * @param {function} callback
 */
BitfinexExchange.prototype.placeOrders = function(orders, callback) {
    var exchange = this;

    if (exchange.pretend) {
        orders.map(function(order) {
            logger.info('[exchange:bitfinex] Pretend placeOrder:', order.toString());
            exchange.saveOrder(order);
        });
        return;
    }

    var side = {
        'ASK': 'sell',
        'BID': 'buy'
    }[order.type];

    // Bitfinex API limits multi operations to 10 orders.
    var pagedOrders = pager(orders, 10);
    exchange.requestMap(pagedOrders, function(page, callback) {
        var params = page.map(function(order) {
            return {
                'symbol': 'btcusd',     // symbol (string): The name of the symbol (see `/symbols`).
                'amount': order.quantity.toFixed(), // amount (decimal): Order size: how much to buy or sell.
                'price': order.rate.toFixed(3),     // price (price): Price to buy or sell at. May omit if a market order.
                'exchange': 'all',      // exchange (string): "bitfinex", "bitstamp", "all" (for no routing).
                'side': side,           // side (string): Either "buy" or "sell".
                'type': 'limit',        // type (string): Either "market" / "limit" / "stop" / "trailing-stop" / "fill-or-kill".
            };
        });

        exchange.rateLimiter.removeTokens(1, function() {
            exchange.requestLock.acquire(true /* exclusive */, function() {
                exchange.client.call('POST', '/order/new/multi', params, function(err, response, data) {
                    var err = err || toError(data);
                    if (err) {
                        callback(err, response, body);
                        return;
                    }

                    if (data.length !== page.length) {
                        // Bitfinex only returns order ids, so we assume they're the same. :/
                        callback(new Error('Bitfinex returned incorrect number of placed orders: ' + data.length + ' of ' + page.length));
                    }

                    page.map(function(order, i) {
                        var newOrder = order.clone({id: data[i]});
                        exchange.debug('Placed %s (from: %s)', newOrder.toString(), order.toString());
                        exchange.saveOrder(newOrder);
                    });

                    callback();
                });
            });
        });
    }, callback);
};

/**
 * Cancel orders.
 *
 * @param {Array.<Order>} orders List of orders to cancel concurrently.
 * @param {function} callback
 */
BitfinexExchange.prototype.cancelOrders = function(orders, callback) {
    var exchange = this;

    if (exchange.pretend) {
        orders.map(function(order) {
            logger.info('[exchange:bitfinex] Pretend cancelOrder:', order.toString());
            exchange.deleteOrder(order);
        });
        return;
    }

    // Bitfinex API limits multi operations to 10 orders.
    var pagedOrders = pager(orders, 10);
    exchange.requestMap(pagedOrders, function(page, callback) {
        var params = {
            'order_ids': page.map(function(order) {
                return order.id
            })
        };

        exchange.rateLimiter.removeTokens(1, function() {
            exchange.requestLock.acquire(true /* exclusive */, function() {
                exchange.client.call('POST', '/order/cancel/multi', params, function(err, response, data) {
                    var err = err || toError(data);
                    if (err) {
                        callback(err, response, body);
                        return;
                    }

                    // XXX: Not clear what this is supposed to return.

                    callback();
                });
            });
        });
    }, callback);

};

/**
 * Given a Bitfinex order (as returned by the API), return a corresponding
 * {@code Order} object.
 *
 * @static
 * @param {object} d
 * @param {boolean=} skipExecuted Don't count executed amount.
 * @return {Order}
 */
var toOrder = BitfinexExchange.toOrder = function(d, skipExecuted) {
    try{
        var amount = d.original_amount;
        if (!skipExecuted) amount = d.remaining_amount;
        var o = new order.Order(d.order_id, d.side.toUpperCase(), d.quantity, d.price, null, 'bitfinex');
    } catch (e) {
        logger.warn('Failed to convert Bitfinex order:', d);
        throw e;
    }
    return o;
};

/**
 * Given a Bitfinex orderbook (as returned by the API), return a corresponding
 * orderbook.
 *
 * @static
 * @param {object} d
 * @return {{asks: Array.<Order>, bids: Array.<Order>}}
 */
var toOrderbook = BitfinexExchange.toOrderbook = function(d) {
    return {
        'asks': d.asks.map(function(o) {
            return new order.Order(null, 'ASK', o.amount, o.price, null, 'bitfinex');
        }),
        'bids': d.bids.map(function(o) {
            return new order.Order(null, 'BID', o.amount, o.price, null, 'bitfinex');
        })
    };
};

/**
 * Given a Bitfinex accounts list (as returned by the API), return normalized
 * parameters for {@code BaseExchange.prototype.setBalance}.
 *
 * @static
 * @param {object} accounts
 * @return {{value: string, quantity: string}}
 */
var toBalance = BitfinexExchange.toBalance = function(balance) {
    var args = {
        'value': '0.00000000000000000000',
        'quantity': '0.00000000000000000000'
    };
    balance.forEach(function(account) {
        var type = account['type'];
        var key = {'deposit': 'value', 'exchange': 'quantity'}[type];
        if (!key) return; // Skip unknown currencies.
        args[key] = account['available'];
    });
    return args;
};

/**
 * Given a Bitfinex response body as parsed JSON, return an {@code Error}
 * instace if it contains an error, or null otherwise.
 *
 * @static
 * @param {object}
 * @return {Error|null}
 */
var toError = BitfinexExchange.toError = function(r) {
    if (!r || !r.message) return null;

    return new Error(r.message);
};
<|endoftext|>