<commit_before>var makeAckable = require('./ackable');

// versioned events, acks, retries, resync
lichess.StrongSocket = function(url, version, settings) {

  var now = Date.now;

  var settings = $.extend(true, {}, lichess.StrongSocket.defaults, settings);
  var url = url;
  var version = version;
  var versioned = version !== false;
  var options = settings.options;
  var ws;
  var pingSchedule;
  var connectSchedule;
  var ackable = makeAckable(function(t, d) { send(t, d) });
  var lastPingTime = now();
  var pongCount = 0;
  var averageLag = 0;
  var tryOtherUrl = false;
  var autoReconnect = true;
  var nbConnects = 0;
  var storage = lichess.storage.make('surl6');

  var connect = function() {
    destroy();
    autoReconnect = true;
    var fullUrl = options.protocol + "//" + baseUrl() + url + "?" + $.param(settings.params);
    debug("connection attempt to " + fullUrl);
    try {
      ws = new WebSocket(fullUrl);
      ws.onerror = function(e) {
        onError(e);
      };
      ws.onclose = function(e) {
        if (autoReconnect) {
          debug('Will autoreconnect in ' + options.autoReconnectDelay);
          scheduleConnect(options.autoReconnectDelay);
        }
      };
      ws.onopen = function() {
        debug("connected to " + fullUrl);
        onSuccess();
        $('body').removeClass('offline').addClass('online').addClass(nbConnects > 1 ? 'reconnected' : '');
        pingNow();
        lichess.pubsub.emit('socket.open')();
        ackable.resend();
      };
      ws.onmessage = function(e) {
        var m = JSON.parse(e.data);
        // if (Math.random() > 0.5) {
        //   console.log(m, 'skip');
        //   return;
        // }
        if (m.t === 'n') pong();
        // else debug(e.data);
        if (m.t === 'b') m.d.forEach(handle);
        else handle(m);
      };
    } catch (e) {
      onError(e);
    }
    scheduleConnect(options.pingMaxLag);
  };

  var send = function(t, d, o, noRetry) {
    o = o || {};
    var msg = { t: t };
    if (d !== undefined) {
      if (o.withLag) d.l = Math.round(averageLag);
      if (o.millis >= 0) d.s = Math.round(o.millis * 0.1).toString(36);
      msg.d = d;
    }
    if (o.ackable) {
      msg.d = msg.d || {}; // can't ack message without data
      ackable.register(t, msg.d); // adds d.a, the ack ID we expect to get back
    }
    var message = JSON.stringify(msg);
    debug("send " + message);
    try {
      ws.send(message);
    } catch (e) {
      // maybe sent before socket opens,
      // try again a second later.
      if (!noRetry) setTimeout(function() {
        send(t, msg.d, o, true);
      }, 1000);
    }
  };
  lichess.pubsub.on('socket.send', send);

  var scheduleConnect = function(delay) {
    if (options.idle) delay = 10 * 1000 + Math.random() * 10 * 1000;
    // debug('schedule connect ' + delay);
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    connectSchedule = setTimeout(function() {
      $('body').addClass('offline').removeClass('online');
      tryOtherUrl = true;
      connect();
    }, delay);
  };

  var schedulePing = function(delay) {
    clearTimeout(pingSchedule);
    pingSchedule = setTimeout(pingNow, delay);
  };

  var pingNow = function() {
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    try {
      ws.send(pingData());
      lastPingTime = now();
    } catch (e) {
      debug(e, true);
    }
    scheduleConnect(options.pingMaxLag);
  };

  var computePingDelay = function() {
    return options.pingDelay + (options.idle ? 1000 : 0);
  };

  var pong = function() {
    clearTimeout(connectSchedule);
    schedulePing(computePingDelay());
    var currentLag = Math.min(now() - lastPingTime, 10000);
    pongCount++;

    // Average first 4 pings, then switch to decaying average.
    var mix = pongCount > 4 ? 0.1 : 1 / pongCount;
    averageLag += mix * (currentLag - averageLag);

    lichess.pubsub.emit('socket.lag')(averageLag);
  };

  var pingData = function() {
    if (!versioned) return '{"t":"p"}';
    var data = {
      t: "p",
      v: version
    };
    if (pongCount % 8 === 2) data.l = Math.round(0.1 * averageLag);
    return JSON.stringify(data);
  };

  var handle = function(m) {
    if (m.v) {
      if (m.v <= version) {
        debug("already has event " + m.v);
        return;
      }
      if (m.v > version + 1) {
        debug("event gap detected from " + version + " to " + m.v);
        return;
      }
      version = m.v;
    }
    switch (m.t || false) {
      case false:
        break;
      case 'resync':
        lichess.reload();
        break;
      case 'ack':
        ackable.gotAck(m.d);
        break;
      default:
        lichess.pubsub.emit('socket.in.' + m.t)(m.d);
        var processed = settings.receive && settings.receive(m.t, m.d);
        if (!processed && settings.events[m.t]) settings.events[m.t](m.d || null, m);
    }
  };

  var debug = function(msg, always) {
    if (always || options.debug) {
      console.debug("[" + options.name + " " + settings.params.sri + "]", msg);
    }
  };

  var destroy = function() {
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    disconnect();
    ws = null;
  };

  var disconnect = function(onNextConnect) {
    if (ws) {
      debug("Disconnect");
      autoReconnect = false;
      ws.onerror = ws.onclose = ws.onopen = ws.onmessage = $.noop;
      ws.close();
    }
    if (onNextConnect) options.onNextConnect = onNextConnect;
  };

  var onError = function(e) {
    options.debug = true;
    debug('error: ' + JSON.stringify(e));
    tryOtherUrl = true;
    setTimeout(function() {
      if (!$('#network_error').length) {
        $('#top').append('Network error');
      }
    }, 1000);
    clearTimeout(pingSchedule);
  };

  var onSuccess = function() {
    $('#network_error').remove();
    nbConnects++;
    if (nbConnects === 1) {
      options.onFirstConnect();
      var disconnectTimeout;
      lichess.idleTimer(10 * 60 * 1000, function() {
        options.idle = true;
        disconnectTimeout = setTimeout(lichess.socket.destroy, 2 * 60 * 60 * 1000);
      }, function() {
        options.idle = false;
        if (ws) clearTimeout(disconnectTimeout);
        else location.reload();
      });
    }
    if (options.onNextConnect) {
      options.onNextConnect();
      delete options.onNextConnect;
    }
  };

  var baseUrl = function() {
    var urls = options.baseUrls, url = storage.get();
    if (!url) {
      url = urls[0];
      storage.set(url);
    } else if (tryOtherUrl) {
      tryOtherUrl = false;
      url = urls[(urls.indexOf(url) + 1) % urls.length];
      storage.set(url);
    }
    return url;
  };

  connect();
  window.addEventListener('unload', destroy);

  return {
    connect: connect,
    disconnect: disconnect,
    send: send,
    destroy: destroy,
    options: options,
    pingInterval: function() {
      return computePingDelay() + averageLag;
    },
    averageLag: function() {
      return averageLag;
    },
    getVersion: function() {
      return version;
    }
  };
};
lichess.StrongSocket.sri = Math.random().toString(36).slice(2, 12);
lichess.StrongSocket.available = window.WebSocket || window.MozWebSocket;
lichess.StrongSocket.defaults = {
  events: {
    fen: function(e) {
      $('.live_' + e.id).each(function() {
        lichess.parseFen($(this).data("fen", e.fen).data("lastmove", e.lm));
      });
    },
    challenges: function(d) {
      lichess.challengeApp.update(d);
    },
    notifications: function(d) {
      lichess.notifyApp.update(d, true);
    }
  },
  params: {
    sri: lichess.StrongSocket.sri
  },
  options: {
    name: "unnamed",
    idle: false,
    pingMaxLag: 8000, // time to wait for pong before reseting the connection
    pingDelay: 2000, // time between pong and ping
    autoReconnectDelay: 2000,
    protocol: location.protocol === 'https:' ? 'wss:' : 'ws:',
    baseUrls: (function(d) {
      return [d].concat((d === 'socket.lichess.org' ? [5, 6, 7, 8, 9] : []).map(function(port) {
        return d + ':' + (9020 + port);
      }));
    })(document.body.getAttribute('data-socket-domain')),
    onFirstConnect: $.noop
  }
};
<commit_msg>reconnect WS less aggressively<commit_after>var makeAckable = require('./ackable');

// versioned events, acks, retries, resync
lichess.StrongSocket = function(url, version, settings) {

  var now = Date.now;

  var settings = $.extend(true, {}, lichess.StrongSocket.defaults, settings);
  var url = url;
  var version = version;
  var versioned = version !== false;
  var options = settings.options;
  var ws;
  var pingSchedule;
  var connectSchedule;
  var ackable = makeAckable(function(t, d) { send(t, d) });
  var lastPingTime = now();
  var pongCount = 0;
  var averageLag = 0;
  var tryOtherUrl = false;
  var autoReconnect = true;
  var nbConnects = 0;
  var storage = lichess.storage.make('surl6');

  var connect = function() {
    destroy();
    autoReconnect = true;
    var fullUrl = options.protocol + "//" + baseUrl() + url + "?" + $.param(settings.params);
    debug("connection attempt to " + fullUrl);
    try {
      ws = new WebSocket(fullUrl);
      ws.onerror = function(e) {
        onError(e);
      };
      ws.onclose = function(e) {
        if (autoReconnect) {
          debug('Will autoreconnect in ' + options.autoReconnectDelay);
          scheduleConnect(options.autoReconnectDelay);
        }
      };
      ws.onopen = function() {
        debug("connected to " + fullUrl);
        onSuccess();
        $('body').removeClass('offline').addClass('online').addClass(nbConnects > 1 ? 'reconnected' : '');
        pingNow();
        lichess.pubsub.emit('socket.open')();
        ackable.resend();
      };
      ws.onmessage = function(e) {
        var m = JSON.parse(e.data);
        // if (Math.random() > 0.5) {
        //   console.log(m, 'skip');
        //   return;
        // }
        if (m.t === 'n') pong();
        // else debug(e.data);
        if (m.t === 'b') m.d.forEach(handle);
        else handle(m);
      };
    } catch (e) {
      onError(e);
    }
    scheduleConnect(options.pingMaxLag);
  };

  var send = function(t, d, o, noRetry) {
    o = o || {};
    var msg = { t: t };
    if (d !== undefined) {
      if (o.withLag) d.l = Math.round(averageLag);
      if (o.millis >= 0) d.s = Math.round(o.millis * 0.1).toString(36);
      msg.d = d;
    }
    if (o.ackable) {
      msg.d = msg.d || {}; // can't ack message without data
      ackable.register(t, msg.d); // adds d.a, the ack ID we expect to get back
    }
    var message = JSON.stringify(msg);
    debug("send " + message);
    try {
      ws.send(message);
    } catch (e) {
      // maybe sent before socket opens,
      // try again a second later.
      if (!noRetry) setTimeout(function() {
        send(t, msg.d, o, true);
      }, 1000);
    }
  };
  lichess.pubsub.on('socket.send', send);

  var scheduleConnect = function(delay) {
    if (options.idle) delay = 10 * 1000 + Math.random() * 10 * 1000;
    // debug('schedule connect ' + delay);
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    connectSchedule = setTimeout(function() {
      $('body').addClass('offline').removeClass('online');
      tryOtherUrl = true;
      connect();
    }, delay);
  };

  var schedulePing = function(delay) {
    clearTimeout(pingSchedule);
    pingSchedule = setTimeout(pingNow, delay);
  };

  var pingNow = function() {
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    try {
      ws.send(pingData());
      lastPingTime = now();
    } catch (e) {
      debug(e, true);
    }
    scheduleConnect(options.pingMaxLag);
  };

  var computePingDelay = function() {
    return options.pingDelay + (options.idle ? 1000 : 0);
  };

  var pong = function() {
    clearTimeout(connectSchedule);
    schedulePing(computePingDelay());
    var currentLag = Math.min(now() - lastPingTime, 10000);
    pongCount++;

    // Average first 4 pings, then switch to decaying average.
    var mix = pongCount > 4 ? 0.1 : 1 / pongCount;
    averageLag += mix * (currentLag - averageLag);

    lichess.pubsub.emit('socket.lag')(averageLag);
  };

  var pingData = function() {
    if (!versioned) return '{"t":"p"}';
    var data = {
      t: "p",
      v: version
    };
    if (pongCount % 8 === 2) data.l = Math.round(0.1 * averageLag);
    return JSON.stringify(data);
  };

  var handle = function(m) {
    if (m.v) {
      if (m.v <= version) {
        debug("already has event " + m.v);
        return;
      }
      if (m.v > version + 1) {
        debug("event gap detected from " + version + " to " + m.v);
        return;
      }
      version = m.v;
    }
    switch (m.t || false) {
      case false:
        break;
      case 'resync':
        lichess.reload();
        break;
      case 'ack':
        ackable.gotAck(m.d);
        break;
      default:
        lichess.pubsub.emit('socket.in.' + m.t)(m.d);
        var processed = settings.receive && settings.receive(m.t, m.d);
        if (!processed && settings.events[m.t]) settings.events[m.t](m.d || null, m);
    }
  };

  var debug = function(msg, always) {
    if (always || options.debug) {
      console.debug("[" + options.name + " " + settings.params.sri + "]", msg);
    }
  };

  var destroy = function() {
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    disconnect();
    ws = null;
  };

  var disconnect = function(onNextConnect) {
    if (ws) {
      debug("Disconnect");
      autoReconnect = false;
      ws.onerror = ws.onclose = ws.onopen = ws.onmessage = $.noop;
      ws.close();
    }
    if (onNextConnect) options.onNextConnect = onNextConnect;
  };

  var onError = function(e) {
    options.debug = true;
    debug('error: ' + JSON.stringify(e));
    tryOtherUrl = true;
    setTimeout(function() {
      if (!$('#network_error').length) {
        $('#top').append('Network error');
      }
    }, 1000);
    clearTimeout(pingSchedule);
  };

  var onSuccess = function() {
    $('#network_error').remove();
    nbConnects++;
    if (nbConnects === 1) {
      options.onFirstConnect();
      var disconnectTimeout;
      lichess.idleTimer(10 * 60 * 1000, function() {
        options.idle = true;
        disconnectTimeout = setTimeout(lichess.socket.destroy, 2 * 60 * 60 * 1000);
      }, function() {
        options.idle = false;
        if (ws) clearTimeout(disconnectTimeout);
        else location.reload();
      });
    }
    if (options.onNextConnect) {
      options.onNextConnect();
      delete options.onNextConnect;
    }
  };

  var baseUrl = function() {
    var urls = options.baseUrls, url = storage.get();
    if (!url) {
      url = urls[0];
      storage.set(url);
    } else if (tryOtherUrl) {
      tryOtherUrl = false;
      url = urls[(urls.indexOf(url) + 1) % urls.length];
      storage.set(url);
    }
    return url;
  };

  connect();
  window.addEventListener('unload', destroy);

  return {
    connect: connect,
    disconnect: disconnect,
    send: send,
    destroy: destroy,
    options: options,
    pingInterval: function() {
      return computePingDelay() + averageLag;
    },
    averageLag: function() {
      return averageLag;
    },
    getVersion: function() {
      return version;
    }
  };
};
lichess.StrongSocket.sri = Math.random().toString(36).slice(2, 12);
lichess.StrongSocket.available = window.WebSocket || window.MozWebSocket;
lichess.StrongSocket.defaults = {
  events: {
    fen: function(e) {
      $('.live_' + e.id).each(function() {
        lichess.parseFen($(this).data("fen", e.fen).data("lastmove", e.lm));
      });
    },
    challenges: function(d) {
      lichess.challengeApp.update(d);
    },
    notifications: function(d) {
      lichess.notifyApp.update(d, true);
    }
  },
  params: {
    sri: lichess.StrongSocket.sri
  },
  options: {
    name: "unnamed",
    idle: false,
    pingMaxLag: 8000, // time to wait for pong before reseting the connection
    pingDelay: 2000, // time between pong and ping
    autoReconnectDelay: 3000,
    protocol: location.protocol === 'https:' ? 'wss:' : 'ws:',
    baseUrls: (function(d) {
      return [d].concat((d === 'socket.lichess.org' ? [5, 6, 7, 8, 9] : []).map(function(port) {
        return d + ':' + (9020 + port);
      }));
    })(document.body.getAttribute('data-socket-domain')),
    onFirstConnect: $.noop
  }
};
<|endoftext|>


<commit_before>// REACT COMPONENTS
import React from 'react';
import {
  Form,
  Radio,
  Checkbox,
  Header,
} from 'semantic-ui-react';

// PROJECT COMPONENTS
import {
  FormPartsContainer,
  FormHeading,
  IntervalColumnHeadings,
  CashFlowRow,
  ControlledRadioYesNo,
} from './formHelpers';
import {
  ContractRentField,
  RentShareField,
  PlainRentRow,
} from './rentFields';
import CashFlowRowAfterConfirm from './CashFlowRowAfterConfirm';

// COMPONENT HELPER FUNCTIONS
import { getTimeSetter } from '../utils/getTimeSetter';

// LOGIC
import {
  getEveryMember,
  isHeadOrSpouse,
  getDependentMembers,
  isDisabled,
  isUnder13,
} from '../utils/getMembers';

// ========================================
// COMPONENTS
// ========================================
const EarnedFrom = function ({ heading, cashflowProps, children }) {
  return (

      <FormHeading>{ heading }</FormHeading>
      <CashFlowRowAfterConfirm { ...cashflowProps }>
        { children }
      </CashFlowRowAfterConfirm>

  );
};  // End EarnedFrom

const Utilities = function ({ current, type, time, setClientProperty }) {

  let climate     = current.climateControl,
      electricity = current.nonHeatElectricity,
      phone       = current.phone,
      fuelAssist  = current.fuelAssistance;

  let setChecked = function (evnt, inputProps) {
    var obj = { ...inputProps, value: inputProps.checked };
    setClientProperty(evnt, obj);
  };  // End setChecked()

  return (

      Which of these utilities do you pay for?

      <Checkbox
        name={ 'climateControl' }
        label={ 'Heating or cooling (e.g. A/C during summer)' }
        checked={ climate }
        onChange={ setChecked } />

      <Checkbox
        name={ 'nonHeatElectricity' }
        label={ 'Electricity for non-heating purposes' }
        checked={ electricity }
        onChange={ setChecked } />

      <Checkbox
        name={ 'phone' }
        label={ 'Telephone service' }
        checked={ phone }
        onChange={ setChecked } />

      <ControlledRadioYesNo
        labelText = { 'Do you get Fuel Assistance?' }
        checked   = { fuelAssist }
        name      = { 'fuelAssistance' }
        onChange  = { setClientProperty } />

  );
};  // End Utilities(<>)

const HousingDetails = function ({ current, type, time, setClientProperty }) {

  let housing = current.housing,
      sharedProps = {
        timeState:         current,
        current:           current,
        type:              type,
        time:              time,
        setClientProperty: setClientProperty,
      };

  if (current.housing === 'voucher') {
    return (

        <ContractRentField { ...sharedProps } />
        <RentShareField { ...sharedProps } />
        <Utilities { ...sharedProps } />

    );

  } else if (housing === 'homeless') {
    return null;

  } else if (housing === 'renter') {
    return (

        <PlainRentRow { ...sharedProps } />
        <Utilities { ...sharedProps } />

    );

  } else if (housing === 'homeowner') {
    return (

        <IntervalColumnHeadings type={ type } />
        <CashFlowRow
          { ...sharedProps }
          generic={ 'mortgage' }> Mortgage
        </CashFlowRow>
        <CashFlowRow
          { ...sharedProps }
          generic={ 'housingInsurance' }> Insurance Costs
        </CashFlowRow>
        <CashFlowRow
          { ...sharedProps }
          generic={ 'propertyTax' }> Property Tax
        </CashFlowRow>
        <Utilities { ...sharedProps } />

    );

  }  // end which expenses
};  // End HousingDetails(<>)

const HousingRadio = function ({ currentValue, label, time, setClientProperty }) {

  var value = label.toLowerCase();

  return (
    <Form.Field>
      <Radio
        name={ 'housing' }
        label={ label }
        value={ value }
        checked={ currentValue === value }
        onChange={ setClientProperty } />
    </Form.Field>
  );

};  // End HousingRadio(<>)

/**
 * @function
 * @param {object} props
 * @param {object} props.current - Client data of current user circumstances
 * @param {string} props.type - 'expense' or 'income', etc., for classes
 * @param {string} props.time - 'current' or 'future'
 * @param {function} props.setClientProperty - Sets state values
 *
 * @returns React element
 */
const Housing = function ({ current, type, time, setClientProperty }) {

  // We're using a bunch of radio buttons. Since `checked` is defined
  // in Radio components, `setClientProperty()` would store it, but we
  // want the value, so get rid of checked.
  /** Makes sure values are propagated to 'current' properties if needed. */
  let ensureRouteAndValue = function (evnt, inputProps) {
    var obj = { ...inputProps, name: inputProps.name, value: inputProps.value, checked: null };
    setClientProperty(evnt, obj);
  };

  let sharedProps = {
    current:           current,
    type:              type,
    time:              time,
    setClientProperty: ensureRouteAndValue,
  };

  return (

      <FormHeading>Housing</FormHeading>

      { current.housing === 'voucher'
        ? null
        :

          What is your housing situation?
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Homeless' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Renter' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Homeowner' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />

}

      <HousingDetails { ...sharedProps } />

  );

};  // End Housing()

/**
 * @function
 * @param {object} props
 * @param {object} props.current - Client data of current user circumstances
 * @param {object} props.time - 'current' or 'future'
 * @param {object} props.setClientProperty - Sets state values
 *
 * @returns React element
 */
const ExpensesFormContent = function ({ current, time, setClientProperty }) {

  let type        = 'expense',
      household   = current.household,
      sharedProps = { timeState: current, type: type, time: time, setClientProperty: setClientProperty };

  /** @todo Make an age-checking function to
   *     keep household data structure under
   *     control in one place. */
  var isOver12 = function (member) {
    return !isUnder13(member);
  };

  // Won't include head or spouse
  var allDependents = getDependentMembers(household),
      under13       = getEveryMember(allDependents, isUnder13),
      over12        = getEveryMember(allDependents, isOver12);

  // 'Elderly' here is using the lowest common denominator - SNAP standards.
  var isElderlyOrDisabled = function (member) {
    return isDisabled(member) || member.m_age >= 60;
  };
  var elderlyOrDisabled = getEveryMember(household, isElderlyOrDisabled),
      elderlyOrDisabledHeadOrSpouse = getEveryMember(elderlyOrDisabled, isHeadOrSpouse);

  return (

      { under13.length > 0
        ?

          <FormHeading subheading = { 'A "child" is a person 12 or younger. Don\'t include amounts that are paid for by other benefit programs.\n' }>
            Reasonable Unreimbursed Non-Medical Child Care
          </FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childDirectCare' }> Direct care costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childBeforeAndAfterSchoolCare' }> Before- and after-school care
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childTransportation' }> Transportation costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childOtherCare' }> Other care
          </CashFlowRow>

          <EarnedFrom
            heading={ 'Pay You Got Because Of Childcare' }
            cashflowProps={{
              ...sharedProps,
              generic:      'earnedBecauseOfChildCare',
              confirmLabel: 'If you didn\'t have that childcare, would you make less money?',
            }}>
            How much less?
          </EarnedFrom>

        : null
      }

      { current.hasSnap
        ?

          <FormHeading>Child Support</FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childSupportPaidOut' }> Legally obligated child support
          </CashFlowRow>

        : null
      }

      {/* Head or spouse can't be a dependent, so they don't count. */}
      { over12.length > 0
        ?

          <FormHeading subheading = { 'For the care of people who are older than 12, but are still dependents (those under 18 or disabled). Don\'t include amounts that are paid for by other benefit programs.\n' }>
            Dependent Care of Persons Over 12 Years of Age
          </FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultDirectCare' }> Direct care costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultTransportation' }> Transportation costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultOtherCare' }> Other care
          </CashFlowRow>

        : null
      }

      { elderlyOrDisabled.length > 0
        ?

          <FormHeading>Unreimbursed Disabled/Handicapped/Elderly Assistance</FormHeading>

Unreimbursed expenses to cover care attendants and auxiliary apparatus for any family member who is elderly or is a person with disabilities. Auxiliary apparatus are items such as wheelchairs, ramps, adaptations to vehicles, or special equipment to enable a blind person to read or type, but only if these items are directly related to permitting the disabled person or other family member to work.

Examples of eligible disability assistance expenses:

The payments made on a motorized wheelchair for the 42 year old son of the head of household enable the son to leave the house and go to work each day on his own. Prior to the purchase of the motorized wheelchair, the son was unable to make the commute to work. These payments are an eligible disability assistance expense.

Payments to a care attendant to stay with a disabled 16-year-old child allow the child’s mother to go to work every day. These payments are an eligible disability assistance allowance.

          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'disabledAssistance' }> Disabled/Handicapped assistance
          </CashFlowRow>

          <EarnedFrom
            heading={ 'Pay You Got Because Of Extra Care' }
            cashflowProps={{
              ...sharedProps,
              generic:      'earnedBecauseOfAdultCare',
              confirmLabel: 'If you didn\'t have that assistance, would you make less money?',
            }}>
            How much less?
          </EarnedFrom>

        : null
      }

      {/** These medical expenses don't count for Section 8 unless
        *     the disabled person is the head or spouse. From
        *     {@link http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf}
        *     Appendix B, item (D) */}
      { elderlyOrDisabledHeadOrSpouse.length > 0 || (current.hasSnap && elderlyOrDisabled.length > 0)
        ?

          <FormHeading>Unreimbursed Medical Expenses</FormHeading>

Do not repeat anything you already listed in the section above. Examples of allowable medical expenses:

The orthodontist expenses for a child’s braces.

Services of doctors and health care professionals.

Services of health care facilities.

Medical insurance premiums.

Prescription/non-prescription medicines (prescribed by a physician).

Transportation to treatment (cab fare, bus fare, mileage).

Dental expenses, eyeglasses, hearing aids, batteries.

Live-in or periodic medical assistance.

Monthly payment on accumulated medical bills (regular monthly payments on a bill that was previously incurred).

          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic='disabledMedical'> Disabled/Elderly medical expenses
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic='otherMedical'> Medical expenses of other members
          </CashFlowRow>

        : null
      }

      <Housing
        current={ current }
        time={ time }
        type={ type }
        setClientProperty={ setClientProperty } />

  );

};  // End ExpensesFormContent()

/**
 * @todo SNAP: Does a medical assistant's payments count as a medical expense?
 *     (Answer: Yes. @see {@link https://www.mass.gov/service-details/snap-verifications})
 * @todo SNAP: Medical expense only matters if household has elder/disabled, but
 *     are they any medical expenses or only those of the disabled person? "Medical
 *     Expenses for Disabled or Elderly". Also, do they sometimes need to
 *     enter medical expenses even if they don't have an elderly or disabled
 *     household member?
 */

/**
  * @function
  * @param {object} props
  * @param {function} props.changeClient - Setting client state
  * @param {function} props.previousStep - Go to previous form step
  * @param {function} props.nextStep - Go to next form step
  * @param {object} props.client - Object will all the data for calculating benefits
  *
  * @returns React element
  */
// `props` is a cloned version of the original props. References broken.
const CurrentExpensesStep = function ({ changeClient, previousStep, nextStep, client }) {

  const setTimeProp = getTimeSetter('current', changeClient);

  return (
    <Form className = 'expense-form flex-item flex-column'>
      <FormPartsContainer
        title     = { 'Current Household Expenses' }
        clarifier = { null }
        left      = {{ name: 'Previous', func: previousStep }}
        right     = {{ name: 'Next', func: nextStep }}>
        <ExpensesFormContent
          setClientProperty={ setTimeProp }
          current={ client.current }
          time={ 'current' } />
      </FormPartsContainer>
    </Form>
  );

};  // End CurrentExpensesStep()

export { CurrentExpensesStep };
<commit_msg>Wording changes as per discussion.<commit_after>// REACT COMPONENTS
import React from 'react';
import {
  Form,
  Radio,
  Checkbox,
  Header,
} from 'semantic-ui-react';

// PROJECT COMPONENTS
import {
  FormPartsContainer,
  FormHeading,
  IntervalColumnHeadings,
  CashFlowRow,
  ControlledRadioYesNo,
} from './formHelpers';
import {
  ContractRentField,
  RentShareField,
  PlainRentRow,
} from './rentFields';
import CashFlowRowAfterConfirm from './CashFlowRowAfterConfirm';

// COMPONENT HELPER FUNCTIONS
import { getTimeSetter } from '../utils/getTimeSetter';

// LOGIC
import {
  getEveryMember,
  isHeadOrSpouse,
  getDependentMembers,
  isDisabled,
  isUnder13,
} from '../utils/getMembers';

// ========================================
// COMPONENTS
// ========================================
const EarnedFrom = function ({ heading, cashflowProps, children }) {
  return (

      <FormHeading>{ heading }</FormHeading>
      <CashFlowRowAfterConfirm { ...cashflowProps }>
        { children }
      </CashFlowRowAfterConfirm>

  );
};  // End EarnedFrom

const Utilities = function ({ current, type, time, setClientProperty }) {

  let climate     = current.climateControl,
      electricity = current.nonHeatElectricity,
      phone       = current.phone,
      fuelAssist  = current.fuelAssistance;

  let setChecked = function (evnt, inputProps) {
    var obj = { ...inputProps, value: inputProps.checked };
    setClientProperty(evnt, obj);
  };  // End setChecked()

  return (

      Which of these utilities do you pay for?

      <Checkbox
        name={ 'climateControl' }
        label={ 'Heating or cooling (e.g. A/C during summer)' }
        checked={ climate }
        onChange={ setChecked } />

      <Checkbox
        name={ 'nonHeatElectricity' }
        label={ 'Electricity for non-heating purposes' }
        checked={ electricity }
        onChange={ setChecked } />

      <Checkbox
        name={ 'phone' }
        label={ 'Telephone service' }
        checked={ phone }
        onChange={ setChecked } />

      <ControlledRadioYesNo
        labelText = { 'Do you get Fuel Assistance?' }
        checked   = { fuelAssist }
        name      = { 'fuelAssistance' }
        onChange  = { setClientProperty } />

  );
};  // End Utilities(<>)

const HousingDetails = function ({ current, type, time, setClientProperty }) {

  let housing = current.housing,
      sharedProps = {
        timeState:         current,
        current:           current,
        type:              type,
        time:              time,
        setClientProperty: setClientProperty,
      };

  if (current.housing === 'voucher') {
    return (

        <ContractRentField { ...sharedProps } />
        <RentShareField { ...sharedProps } />
        <Utilities { ...sharedProps } />

    );

  } else if (housing === 'homeless') {
    return null;

  } else if (housing === 'renter') {
    return (

        <PlainRentRow { ...sharedProps } />
        <Utilities { ...sharedProps } />

    );

  } else if (housing === 'homeowner') {
    return (

        <IntervalColumnHeadings type={ type } />
        <CashFlowRow
          { ...sharedProps }
          generic={ 'mortgage' }> Mortgage
        </CashFlowRow>
        <CashFlowRow
          { ...sharedProps }
          generic={ 'housingInsurance' }> Insurance Costs
        </CashFlowRow>
        <CashFlowRow
          { ...sharedProps }
          generic={ 'propertyTax' }> Property Tax
        </CashFlowRow>
        <Utilities { ...sharedProps } />

    );

  }  // end which expenses
};  // End HousingDetails(<>)

const HousingRadio = function ({ currentValue, label, time, setClientProperty }) {

  var value = label.toLowerCase();

  return (
    <Form.Field>
      <Radio
        name={ 'housing' }
        label={ label }
        value={ value }
        checked={ currentValue === value }
        onChange={ setClientProperty } />
    </Form.Field>
  );

};  // End HousingRadio(<>)

/**
 * @function
 * @param {object} props
 * @param {object} props.current - Client data of current user circumstances
 * @param {string} props.type - 'expense' or 'income', etc., for classes
 * @param {string} props.time - 'current' or 'future'
 * @param {function} props.setClientProperty - Sets state values
 *
 * @returns React element
 */
const Housing = function ({ current, type, time, setClientProperty }) {

  // We're using a bunch of radio buttons. Since `checked` is defined
  // in Radio components, `setClientProperty()` would store it, but we
  // want the value, so get rid of checked.
  /** Makes sure values are propagated to 'current' properties if needed. */
  let ensureRouteAndValue = function (evnt, inputProps) {
    var obj = { ...inputProps, name: inputProps.name, value: inputProps.value, checked: null };
    setClientProperty(evnt, obj);
  };

  let sharedProps = {
    current:           current,
    type:              type,
    time:              time,
    setClientProperty: ensureRouteAndValue,
  };

  return (

      <FormHeading>Housing</FormHeading>

      { current.housing === 'voucher'
        ? null
        :

          What is your housing situation?
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Homeless' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Renter' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />
          <HousingRadio
            currentValue={ current.housing }
            label={ 'Homeowner' }
            time={ time }
            setClientProperty={ ensureRouteAndValue } />

}

      <HousingDetails { ...sharedProps } />

  );

};  // End Housing()

/**
 * @function
 * @param {object} props
 * @param {object} props.current - Client data of current user circumstances
 * @param {object} props.time - 'current' or 'future'
 * @param {object} props.setClientProperty - Sets state values
 *
 * @returns React element
 */
const ExpensesFormContent = function ({ current, time, setClientProperty }) {

  let type        = 'expense',
      household   = current.household,
      sharedProps = { timeState: current, type: type, time: time, setClientProperty: setClientProperty };

  /** @todo Make an age-checking function to
   *     keep household data structure under
   *     control in one place. */
  var isOver12 = function (member) {
    return !isUnder13(member);
  };

  // Won't include head or spouse
  var allDependents = getDependentMembers(household),
      under13       = getEveryMember(allDependents, isUnder13),
      over12        = getEveryMember(allDependents, isOver12);

  // 'Elderly' here is using the lowest common denominator - SNAP standards.
  var isElderlyOrDisabled = function (member) {
    return isDisabled(member) || member.m_age >= 60;
  };
  var elderlyOrDisabled = getEveryMember(household, isElderlyOrDisabled),
      elderlyOrDisabledHeadOrSpouse = getEveryMember(elderlyOrDisabled, isHeadOrSpouse);

  return (

      { under13.length > 0
        ?

          <FormHeading subheading = { 'A "child" is a person 12 or younger. Don\'t include amounts that are paid for by other benefit programs.\n' }>
            Reasonable Unreimbursed Non-Medical Child Care
          </FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childDirectCare' }> Direct care costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childBeforeAndAfterSchoolCare' }> Before- and after-school care
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childTransportation' }> Transportation costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childOtherCare' }> Other care
          </CashFlowRow>

          <EarnedFrom
            heading={ 'Effect of Childcare' }
            cashflowProps={{
              ...sharedProps,
              generic:      'earnedBecauseOfChildCare',
              confirmLabel: 'If you didn\'t have that childcare, would it affect your current job?',
            }}>
            How much less would you make?
          </EarnedFrom>

        : null
      }

      { current.hasSnap
        ?

          <FormHeading>Child Support</FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'childSupportPaidOut' }> Legally obligated child support
          </CashFlowRow>

        : null
      }

      {/* Head or spouse can't be a dependent, so they don't count. */}
      { over12.length > 0
        ?

          <FormHeading subheading = { 'For the care of people who are older than 12, but are still dependents (those under 18 or disabled). Don\'t include amounts that are paid for by other benefit programs.\n' }>
            Dependent Care of Persons Over 12 Years of Age
          </FormHeading>
          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultDirectCare' }> Direct care costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultTransportation' }> Transportation costs
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic={ 'adultOtherCare' }> Other care
          </CashFlowRow>

        : null
      }

      { elderlyOrDisabled.length > 0
        ?

          <FormHeading>Unreimbursed Disabled/Handicapped/Elderly Assistance</FormHeading>

Unreimbursed expenses to cover care attendants and auxiliary apparatus for any family member who is elderly or is a person with disabilities. Auxiliary apparatus are items such as wheelchairs, ramps, adaptations to vehicles, or special equipment to enable a blind person to read or type, but only if these items are directly related to permitting the disabled person or other family member to work.

Examples of eligible disability assistance expenses:

The payments made on a motorized wheelchair for the 42 year old son of the head of household enable the son to leave the house and go to work each day on his own. Prior to the purchase of the motorized wheelchair, the son was unable to make the commute to work. These payments are an eligible disability assistance expense.

Payments to a care attendant to stay with a disabled 16-year-old child allow the child’s mother to go to work every day. These payments are an eligible disability assistance allowance.

          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic={ 'disabledAssistance' }> Disabled/Handicapped assistance
          </CashFlowRow>

          <EarnedFrom
            heading={ 'Effect of Assistance' }
            cashflowProps={{
              ...sharedProps,
              generic:      'earnedBecauseOfAdultCare',
              confirmLabel: 'If you didn\'t have that assistance, would it affect your current job?',
            }}>
            How much less?
          </EarnedFrom>

        : null
      }

      {/** These medical expenses don't count for Section 8 unless
        *     the disabled person is the head or spouse. From
        *     {@link http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf}
        *     Appendix B, item (D) */}
      { elderlyOrDisabledHeadOrSpouse.length > 0 || (current.hasSnap && elderlyOrDisabled.length > 0)
        ?

          <FormHeading>Unreimbursed Medical Expenses</FormHeading>

Do not repeat anything you already listed in the section above. Examples of allowable medical expenses:

The orthodontist expenses for a child’s braces.

Services of doctors and health care professionals.

Services of health care facilities.

Medical insurance premiums.

Prescription/non-prescription medicines (prescribed by a physician).

Transportation to treatment (cab fare, bus fare, mileage).

Dental expenses, eyeglasses, hearing aids, batteries.

Live-in or periodic medical assistance.

Monthly payment on accumulated medical bills (regular monthly payments on a bill that was previously incurred).

          <IntervalColumnHeadings type={ type } />
          <CashFlowRow
            { ...sharedProps }
            generic='disabledMedical'> Disabled/Elderly medical expenses
          </CashFlowRow>
          <CashFlowRow
            { ...sharedProps }
            generic='otherMedical'> Medical expenses of other members
          </CashFlowRow>

        : null
      }

      <Housing
        current={ current }
        time={ time }
        type={ type }
        setClientProperty={ setClientProperty } />

  );

};  // End ExpensesFormContent()

/**
 * @todo SNAP: Does a medical assistant's payments count as a medical expense?
 *     (Answer: Yes. @see {@link https://www.mass.gov/service-details/snap-verifications})
 * @todo SNAP: Medical expense only matters if household has elder/disabled, but
 *     are they any medical expenses or only those of the disabled person? "Medical
 *     Expenses for Disabled or Elderly". Also, do they sometimes need to
 *     enter medical expenses even if they don't have an elderly or disabled
 *     household member?
 */

/**
  * @function
  * @param {object} props
  * @param {function} props.changeClient - Setting client state
  * @param {function} props.previousStep - Go to previous form step
  * @param {function} props.nextStep - Go to next form step
  * @param {object} props.client - Object will all the data for calculating benefits
  *
  * @returns React element
  */
// `props` is a cloned version of the original props. References broken.
const CurrentExpensesStep = function ({ changeClient, previousStep, nextStep, client }) {

  const setTimeProp = getTimeSetter('current', changeClient);

  return (
    <Form className = 'expense-form flex-item flex-column'>
      <FormPartsContainer
        title     = { 'Current Household Expenses' }
        clarifier = { null }
        left      = {{ name: 'Previous', func: previousStep }}
        right     = {{ name: 'Next', func: nextStep }}>
        <ExpensesFormContent
          setClientProperty={ setTimeProp }
          current={ client.current }
          time={ 'current' } />
      </FormPartsContainer>
    </Form>
  );

};  // End CurrentExpensesStep()

export { CurrentExpensesStep };
<|endoftext|>


<commit_before>goog.provide('glift.enums');

goog.require('glift');

/**
 * Various constants used throughout glift.
 */
glift.enums = {
  /**
   * Camel cases an enum. Can be useful for things that have functions or
   * packages named from enum names.
   *
   * @param {string} input The enum to input
   * @return {string} transformed enum name.
   */
  toCamelCase: function(input) {
    return input.toLowerCase().replace(/_(.)?/g, function(match, group1) {
      return group1 ? group1.toUpperCase() : '';
    });
  },
};

/**
 * Also sometimes referred to as colors.
 * @enum{string}
 */
glift.enums.states = {
  BLACK: 'BLACK',
  WHITE: 'WHITE',
  EMPTY: 'EMPTY'
};

/**
 * @enum{string}
 */
glift.enums.boardAlignments = {
  TOP: "TOP",
  RIGHT: "RIGHT",
  CENTER: "CENTER"
};

/**
 * List of directions. Used for a variety of tasks.
 * @enum{string}
 */
glift.enums.directions = {
  LEFT: 'LEFT',
  RIGHT: 'RIGHT',
  TOP: 'TOP',
  BOTTOM: 'BOTTOM'
};

/**
 * List of board regions. Usually used for cropping.
 * @enum{string}
 */
glift.enums.boardRegions = {
  LEFT: 'LEFT',
  RIGHT: 'RIGHT',
  TOP: 'TOP',
  BOTTOM: 'BOTTOM',
  TOP_LEFT: 'TOP_LEFT',
  TOP_RIGHT: 'TOP_RIGHT',
  BOTTOM_LEFT: 'BOTTOM_LEFT',
  BOTTOM_RIGHT: 'BOTTOM_RIGHT',
  ALL: 'ALL',
  // Automatically determine the board region.
  AUTO: 'AUTO',
  // Minimal cropbox, modulo some heuristics. To do this, you usually need a
  // movetree, and usually, you need next-path information.
  MINIMAL: 'MINIMAL'
};

/**
 * @enum {string}
 */
glift.enums.marks = {
  CIRCLE: 'CIRCLE',
  SQUARE: 'SQUARE',
  TRIANGLE: 'TRIANGLE',
  XMARK: 'XMARK',
  // STONE_MARKER marks the last played stone
  STONE_MARKER: 'STONE_MARKER',
  LABEL: 'LABEL',

  // These last few 'marks' are variations on the LABEL mark type.
  // TODO(kashomon): Consolidate these somehow.
  //
  // Neither LABEL_ALPHA nor LABEL_NUMERIC are used for rendering, but they
  // are extremly convenient to have this distinction when passing information
  // from the display to the controller
  LABEL_ALPHA: 'LABEL_ALPHA',
  LABEL_NUMERIC: 'LABEL_NUMERIC',

  // There last two are variations on the LABEL mark. VARIATION_MARKER is used
  // so we can color labels differently for variations.
  VARIATION_MARKER: 'VARIATION_MARKER',

  // We color 'correct' variations differently in problems,
  CORRECT_VARIATION: 'CORRECT_VARIATION',

  // We color 'correct' variations differently in problems,
  KO_LOCATION: 'KO_LOCATION'
};

/**
 * Enum to indicate how a move for a problem was resolved.
 * @enum {string}
 */
glift.enums.problemResults = {
  CORRECT: 'CORRECT',
  INCORRECT: 'INCORRECT',
  INDETERMINATE: 'INDETERMINATE',
  FAILURE: 'FAILURE' // i.e., none of these (couldn't place stone).
};

/**
 * Whether or not to show variations in the UI.
 * @enum {string}
 */
glift.enums.showVariations = {
  ALWAYS: 'ALWAYS',
  NEVER: 'NEVER',
  MORE_THAN_ONE: 'MORE_THAN_ONE'
};

/**
 * The types of widgets users can create. Used to link Controllers and Options.
 * @enum {string}
 */
glift.enums.widgetTypes = {
  CORRECT_VARIATIONS_PROBLEM: 'CORRECT_VARIATIONS_PROBLEM',
  EXAMPLE: 'EXAMPLE',
  GAME_VIEWER: 'GAME_VIEWER',
  REDUCED_GAME_VIEWER: 'REDUCED_GAME_VIEWER',
  STANDARD_PROBLEM: 'STANDARD_PROBLEM',
  BOARD_EDITOR: 'BOARD_EDITOR'
};

/**
 * The types of components that exist in the Glift UI.
 * @enum {string}
 */
glift.enums.boardComponents = {
  BOARD: 'BOARD',
  COMMENT_BOX: 'COMMENT_BOX',
  EXTRA_ICONBAR: 'EXTRA_ICONBAR',
  ICONBAR: 'ICONBAR',
  STATUS_BAR: 'STATUS_BAR'
};

/**
 * Rotations we can apply to Go Boards. Doesn't rotate the fundamental data (the
 * SGF points), but rotates at the time the board is drawn.
 * @enum {string}
 */
glift.enums.rotations = {
  NO_ROTATION: 'NO_ROTATION',
  CLOCKWISE_90: 'CLOCKWISE_90',
  CLOCKWISE_180: 'CLOCKWISE_180',
  CLOCKWISE_270: 'CLOCKWISE_270'
};
<commit_msg>Convert double quotes to single quotes.<commit_after>goog.provide('glift.enums');

goog.require('glift');

/**
 * Various constants used throughout glift.
 */
glift.enums = {
  /**
   * Camel cases an enum. Can be useful for things that have functions or
   * packages named from enum names.
   *
   * @param {string} input The enum to input
   * @return {string} transformed enum name.
   */
  toCamelCase: function(input) {
    return input.toLowerCase().replace(/_(.)?/g, function(match, group1) {
      return group1 ? group1.toUpperCase() : '';
    });
  },
};

/**
 * Also sometimes referred to as colors.
 * @enum{string}
 */
glift.enums.states = {
  BLACK: 'BLACK',
  WHITE: 'WHITE',
  EMPTY: 'EMPTY'
};

/**
 * @enum{string}
 */
glift.enums.boardAlignments = {
  TOP: 'TOP',
  RIGHT: 'RIGHT',
  CENTER: 'CENTER'
};

/**
 * List of directions. Used for a variety of tasks.
 * @enum{string}
 */
glift.enums.directions = {
  LEFT: 'LEFT',
  RIGHT: 'RIGHT',
  TOP: 'TOP',
  BOTTOM: 'BOTTOM'
};

/**
 * List of board regions. Usually used for cropping.
 * @enum{string}
 */
glift.enums.boardRegions = {
  LEFT: 'LEFT',
  RIGHT: 'RIGHT',
  TOP: 'TOP',
  BOTTOM: 'BOTTOM',
  TOP_LEFT: 'TOP_LEFT',
  TOP_RIGHT: 'TOP_RIGHT',
  BOTTOM_LEFT: 'BOTTOM_LEFT',
  BOTTOM_RIGHT: 'BOTTOM_RIGHT',
  ALL: 'ALL',
  // Automatically determine the board region.
  AUTO: 'AUTO',
  // Minimal cropbox, modulo some heuristics. To do this, you usually need a
  // movetree, and usually, you need next-path information.
  MINIMAL: 'MINIMAL'
};

/**
 * @enum {string}
 */
glift.enums.marks = {
  CIRCLE: 'CIRCLE',
  SQUARE: 'SQUARE',
  TRIANGLE: 'TRIANGLE',
  XMARK: 'XMARK',
  // STONE_MARKER marks the last played stone
  STONE_MARKER: 'STONE_MARKER',
  LABEL: 'LABEL',

  // These last few 'marks' are variations on the LABEL mark type.
  // TODO(kashomon): Consolidate these somehow.
  //
  // Neither LABEL_ALPHA nor LABEL_NUMERIC are used for rendering, but they
  // are extremly convenient to have this distinction when passing information
  // from the display to the controller
  LABEL_ALPHA: 'LABEL_ALPHA',
  LABEL_NUMERIC: 'LABEL_NUMERIC',

  // There last two are variations on the LABEL mark. VARIATION_MARKER is used
  // so we can color labels differently for variations.
  VARIATION_MARKER: 'VARIATION_MARKER',

  // We color 'correct' variations differently in problems,
  CORRECT_VARIATION: 'CORRECT_VARIATION',

  // We color 'correct' variations differently in problems,
  KO_LOCATION: 'KO_LOCATION'
};

/**
 * Enum to indicate how a move for a problem was resolved.
 * @enum {string}
 */
glift.enums.problemResults = {
  CORRECT: 'CORRECT',
  INCORRECT: 'INCORRECT',
  INDETERMINATE: 'INDETERMINATE',
  FAILURE: 'FAILURE' // i.e., none of these (couldn't place stone).
};

/**
 * Whether or not to show variations in the UI.
 * @enum {string}
 */
glift.enums.showVariations = {
  ALWAYS: 'ALWAYS',
  NEVER: 'NEVER',
  MORE_THAN_ONE: 'MORE_THAN_ONE'
};

/**
 * The types of widgets users can create. Used to link Controllers and Options.
 * @enum {string}
 */
glift.enums.widgetTypes = {
  CORRECT_VARIATIONS_PROBLEM: 'CORRECT_VARIATIONS_PROBLEM',
  EXAMPLE: 'EXAMPLE',
  GAME_VIEWER: 'GAME_VIEWER',
  REDUCED_GAME_VIEWER: 'REDUCED_GAME_VIEWER',
  STANDARD_PROBLEM: 'STANDARD_PROBLEM',
  BOARD_EDITOR: 'BOARD_EDITOR'
};

/**
 * The types of components that exist in the Glift UI.
 * @enum {string}
 */
glift.enums.boardComponents = {
  BOARD: 'BOARD',
  COMMENT_BOX: 'COMMENT_BOX',
  EXTRA_ICONBAR: 'EXTRA_ICONBAR',
  ICONBAR: 'ICONBAR',
  STATUS_BAR: 'STATUS_BAR'
};

/**
 * Rotations we can apply to Go Boards. Doesn't rotate the fundamental data (the
 * SGF points), but rotates at the time the board is drawn.
 * @enum {string}
 */
glift.enums.rotations = {
  NO_ROTATION: 'NO_ROTATION',
  CLOCKWISE_90: 'CLOCKWISE_90',
  CLOCKWISE_180: 'CLOCKWISE_180',
  CLOCKWISE_270: 'CLOCKWISE_270'
};
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();

router.get("/api/test", function(req, res){
	res.send("Hello, test");
});

/*
router.get("/api/new", function(req, res){
  var saves = req.db.collection('saves');
  var save = {};
  if(!req.query.level){
    save.level = 0;
  }else{
    save.level = req.query.level;
  }
  save.username = req.query.username;
  saves.insert(save, function(err, doc) {
    if(err){
      res.json({error: true, message: "ERROR Error adding data."+err});
    }else{
      res.json({error: false, id: doc._id , message: "SUCCESS "+doc._id});
    }
  });
});*/

module.exports = router;
<commit_msg>Submit leaderboard scores<commit_after>var express = require('express');
var router = express.Router();

router.get("/api/test", function(req, res){
	res.send("Hello, test");scores
});

router.get("/api/user-scores/new", function(req, res){
  var scores = req.db.collection('user-scores');
  var score = {};
  if(!req.query.score){
    score.score = 0;
  }else{
    score.score = parseInt(req.query.score);
  }
  score.name = req.query.name;
  scores.insert(score, function(err, doc) {
    if(err){
      res.json({error: true, message: "ERROR Error adding data."+err});
    }else{
      res.json({error: false, id: doc._id , message: "SUCCESS "+doc._id});
    }
  });
});

router.get("/api/team-scores/new", function(req, res){
  var scores = req.db.collection('team-scores');
  var score = {};
  if(!req.query.score){
    score.score = 0;
  }else{
    score.score = parseInt(req.query.score);
  }
  score.name = req.query.name;
  scores.insert(score, function(err, doc) {
    if(err){
      res.json({error: true, message: "ERROR Error adding data."+err});
    }else{
      res.json({error: false, id: doc._id , message: "SUCCESS "+doc._id});
    }
  });
});

module.exports = router;
<|endoftext|>


<commit_before>/*
---

script: router.js

description: A basic URL and Method router.

license: MIT-style license

authors:
- Mark Obcena

provides: [Router]

...
*/

(function(){

var trim = function(path){
	var slash = path.lastIndexOf('/');
	if (path.length != 1 && slash == path.length - 1) return path.substring(0, slash);
	else return path;
};

var Router = new Class({

	$routes: {
		GET: [], POST: [], PUT: [], DELETE: [], HEAD: []
	},

	$cache: {},

	cacheRequest: true,

	$unrouted: function(request, response){
		response.setStatus(404);
		request.next();
	},

	setUnrouted: function(fn){
		this.$unrouted = fn;
	},

	addRoute: function(matcher, func, options){
		var type = typeOf(matcher), form = [], method, len, routes, current;
		if (!matcher) return this;
		options = options || {};
		if (typeof matcher == 'string'){
			var _ = this.prepareMatcher(matcher);
			matcher = _.matcher; form = _.form;
		}
		method = Array.from(options.method || ['GET']);
		len = method.length;
		while (len--){
			routes = this.$routes[method[len].toUpperCase()];
			if (!routes) continue;
			current = {
				key: matcher,
				action: func,
				type: type,
				form: form,
				conditions: options.conditions || {}
			};
			routes.push(current);
			if (method[len] == 'HEAD') this.$routes.HEAD.push(current);
		}
		return this;
	},

	'protected prepareMatcher': function(matcher){
		var params = matcher.match(/:([A-Za-z0-9_$]*)|\*/g), form = [];
		if (params){
			var i = params.length;
			while (i--){
				form.unshift(params[i] == '*' ? 'splat' : params[i].substring(1));
				form = form.filter(function(i){ return i.length; });
			}
			matcher = matcher.multiReplace(
				[(/:([A-Za-z0-9_$]*)|\*/g), '([^\\\/]+)'],
				[(/\{/g), '(?:'],
				[(/\}/g), ')?']
			);
		}
		return {matcher: new RegExp('^' + matcher + '$'), form: form};
	},

	addRoutes: function(items){
		var len, current;
		items = Array.from(items);
		len = items.reverse().length;
		while (len--){
			current = items[len];
			this.addRoute(current.path, current.action, current.options);
		}
		return this;
	},

	matchRoute: function(request){
		var path = trim(request.pathInfo),
			method = request.method,
			routes = this.$routes[method.toUpperCase()];
		if (this.isCached(path, request)) return this.getCached(path, request);
		if (!routes || routes.length == 0) return this.$unrouted;
		return this.getMatch(request, path, routes);
	},

	'protected getMatch': function(request, path, routes){
		var len, route, matches, captures;
		len = routes.length;
		while (len--){
			route = routes[len];
			matches = path.match(route.key);
			if (matches){
				if (!this.checkConditions(request, route.conditions)) continue;
				captures = this.getCaptures(request, route, matches);
				Object.append(request, captures);
				this.setCached(path, Object.append(captures, {
					conditions: route.conditions,
					action: route.action
				}));
				return route.action;
			}
		}
		return this.$unrouted;
	},

	'protected getCaptures': function(request, route, matches){
		var result = {captures: [], params: {}, splat: []};
		matches.shift();
		matches = matches.filter(function(i){
			return i && i.indexOf('/') !== 0;
		});
		if (route.type == 'regexp'){
			result.captures = matches;
		} else {
			var i = route.form.length;
			while (i--){
				if (route.form[i] == 'splat') result.splat.push(matches[i]);
				else result.params[route.form[i]] = matches[i];
			}
		}
		return result;
	},

	'protected checkConditions': function(request, conditions){
		for (var key in conditions){
			var type = typeOf(conditions[key]);
			if ((type == 'string' && conditions[key] !== request[key])
				|| (type == 'regexp' && !request[key].test(conditions[key]))
				|| (type == 'function' && !Function.stab(function(){
						return conditions[key](request[key]);
				}))) return false;
		}
		return true;
	},

	'protected setCached': function(path, obj){
		if (this.cacheRequest) this.$cache[path] = obj;
		return this;
	},

	'protected getCached': function(path, request){
		request.captures = this.$cache[path].captures;
		request.params = this.$cache[path].params;
		request.splat = this.$cache[path].splat;
		return this.$cache[path].action;
	},

	isCached: function(path, request){
		return (this.cacheRequest && this.$cache[path])
				&& this.checkConditions(request, this.$cache[path].conditions);
	}

});

exports.Router = Router;

})();<commit_msg>Removed unneeded filter.<commit_after>/*
---

script: router.js

description: A basic URL and Method router.

license: MIT-style license

authors:
- Mark Obcena

provides: [Router]

...
*/

(function(){

var trim = function(path){
	var slash = path.lastIndexOf('/');
	if (path.length != 1 && slash == path.length - 1) return path.substring(0, slash);
	else return path;
};

var Router = new Class({

	$routes: {
		GET: [], POST: [], PUT: [], DELETE: [], HEAD: []
	},

	$cache: {},

	cacheRequest: true,

	$unrouted: function(request, response){
		response.setStatus(404);
		request.next();
	},

	setUnrouted: function(fn){
		this.$unrouted = fn;
	},

	addRoute: function(matcher, func, options){
		var type = typeOf(matcher), form = [], method, len, routes, current;
		if (!matcher) return this;
		options = options || {};
		if (typeof matcher == 'string'){
			var _ = this.prepareMatcher(matcher);
			matcher = _.matcher; form = _.form;
		}
		method = Array.from(options.method || ['GET']);
		len = method.length;
		while (len--){
			routes = this.$routes[method[len].toUpperCase()];
			if (!routes) continue;
			current = {
				key: matcher,
				action: func,
				type: type,
				form: form,
				conditions: options.conditions || {}
			};
			routes.push(current);
			if (method[len] == 'HEAD') this.$routes.HEAD.push(current);
		}
		return this;
	},

	'protected prepareMatcher': function(matcher){
		var params = matcher.match(/:([A-Za-z0-9_$]*)|\*/g), form = [];
		if (params){
			var i = params.length;
			while (i--){
				form.unshift(params[i] == '*' ? 'splat' : params[i].substring(1));
				form = form.filter(function(i){ return i.length; });
			}
			matcher = matcher.multiReplace(
				[(/:([A-Za-z0-9_$]*)|\*/g), '([^\\\/]+)'],
				[(/\{/g), '(?:'],
				[(/\}/g), ')?']
			);
		}
		return {matcher: new RegExp('^' + matcher + '$'), form: form};
	},

	addRoutes: function(items){
		var len, current;
		items = Array.from(items);
		len = items.reverse().length;
		while (len--){
			current = items[len];
			this.addRoute(current.path, current.action, current.options);
		}
		return this;
	},

	matchRoute: function(request){
		var path = trim(request.pathInfo),
			method = request.method,
			routes = this.$routes[method.toUpperCase()];
		if (this.isCached(path, request)) return this.getCached(path, request);
		if (!routes || routes.length == 0) return this.$unrouted;
		return this.getMatch(request, path, routes);
	},

	'protected getMatch': function(request, path, routes){
		var len, route, matches, captures;
		len = routes.length;
		while (len--){
			route = routes[len];
			matches = path.match(route.key);
			if (matches){
				if (!this.checkConditions(request, route.conditions)) continue;
				captures = this.getCaptures(request, route, matches);
				Object.append(request, captures);
				this.setCached(path, Object.append(captures, {
					conditions: route.conditions,
					action: route.action
				}));
				return route.action;
			}
		}
		return this.$unrouted;
	},

	'protected getCaptures': function(request, route, matches){
		var result = {captures: [], params: {}, splat: []};
		matches.shift();
		if (route.type == 'regexp'){
			result.captures = matches;
		} else {
			var i = route.form.length;
			while (i--){
				if (route.form[i] == 'splat') result.splat.push(matches[i]);
				else result.params[route.form[i]] = matches[i];
			}
		}
		return result;
	},

	'protected checkConditions': function(request, conditions){
		for (var key in conditions){
			var type = typeOf(conditions[key]);
			if ((type == 'string' && conditions[key] !== request[key])
				|| (type == 'regexp' && !request[key].test(conditions[key]))
				|| (type == 'function' && !Function.stab(function(){
						return conditions[key](request[key]);
				}))) return false;
		}
		return true;
	},

	'protected setCached': function(path, obj){
		if (this.cacheRequest) this.$cache[path] = obj;
		return this;
	},

	'protected getCached': function(path, request){
		request.captures = this.$cache[path].captures;
		request.params = this.$cache[path].params;
		request.splat = this.$cache[path].splat;
		return this.$cache[path].action;
	},

	isCached: function(path, request){
		return (this.cacheRequest && this.$cache[path])
				&& this.checkConditions(request, this.$cache[path].conditions);
	}

});

exports.Router = Router;

})();<|endoftext|>


<commit_before>import tryResult from './tryResult';

export default function fetchResource(url, options) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    xhr.open(options.method, url);
    xhr.setRequestHeader('Content-Type', 'application/json');

    xhr.onload = () => {
      if (xhr.statusText !== 'OK') {
        reject(tryResult(
          () => JSON.parse(xhr.responseText),
          () => xhr.responseText
        ));
      } else {
        resolve(JSON.parse(xhr.responseText));
      }
    };

    xhr.send(JSON.stringify(options.data));
  });
}
<commit_msg>Use Promise from pinkie-promise<commit_after>import Promise from 'pinkie-promise';
import tryResult from './tryResult';

export default function fetchResource(url, options) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    xhr.open(options.method, url);
    xhr.setRequestHeader('Content-Type', 'application/json');

    xhr.onload = () => {
      if (xhr.statusText !== 'OK') {
        reject(tryResult(
          () => JSON.parse(xhr.responseText),
          () => xhr.responseText
        ));
      } else {
        resolve(JSON.parse(xhr.responseText));
      }
    };

    xhr.send(JSON.stringify(options.data));
  });
}
<|endoftext|>


<commit_before>import {resolve} from "path";

export default function (filename, regexps, root) {
	for(let redirectPair of regexps) {
		if(redirectPair[0].test(filename)) {
			console.log("FOUND MATCH", redirectPair[0]);
			return resolve(root, redirectPair[1]);
		}
	}

	return null;
}<commit_msg>feat(redirect): Allow to use groups ($1) in redirect target<commit_after>import {resolve} from "path";

export default function (filename, regexps, root) {
	for(let redirectPair of regexps) {
		const regexp = redirectPair[0];

		if(regexp.test(filename)) {
			console.log("FOUND MATCH", regexp);
			let redirect = redirectPair[1];

			// if redirect is of "different/path/$1.js" form
			if(/\$\d/.test(redirect)) {
				// "abs/path/to/path/lib.js".match(/path/(\w+).js$/)[0] ->
				// "path/lib.js".replace(/path/(\w+).js$/, "different/path/$1.js") ->
				// "different/path/lib.js"
				redirect = filename.match(regexp)[0].replace(regexp, redirect);
			}

			return resolve(root, redirect);
		}
	}

	return null;
}<|endoftext|>


<commit_before>var logger = require('../lib/logger')
var config = require('../lib/config')
var transactionStore = require('./transactionStore')

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m
var FN_ARG_SPLIT = /,/
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg

module.exports = {
  wrapMethod: function (_opbeatOriginalFunction, _opbeatBefore, _opbeatAfter, _opbeatContext) {
    var namedArguments = extractNamedFunctionArgs(_opbeatOriginalFunction).join(',')
    var context = {
      _opbeatOriginalFunction: _opbeatOriginalFunction,
      _opbeatBefore: _opbeatBefore,
      _opbeatAfter: _opbeatAfter,
      _opbeatContext: _opbeatContext
    }

    return buildWrapperFunction(context, namedArguments)
  },

  instrumentMethodWithCallback: function (fn, fnName, transaction, type, options) {
    options = options || {}
    var nameParts = []

    if (!config.get('isInstalled')) {
      logger.log('opbeat.instrumentation.instrumentMethodWithCallback.not.installed')
      return fn
    }

    if (options.prefix) {
      nameParts.push(options.prefix)
    }

    if (fnName) {
      nameParts.push(fnName)
    }

    if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
      transaction = options.traceBuffer
    }

    var name = nameParts.join('.')
    var ref = fn
    var context = {
      traceName: name,
      traceType: type,
      options: options,
      fn: fn,
      transaction: transaction
    }

    var wrappedMethod = this.wrapMethod(ref, function instrumentMethodWithCallbackBefore (context) {
      var args = Array.prototype.slice.call(arguments).slice(1)
      var callback = args[options.callbackIndex]

      // Wrap callback
      var wrappedCallback = this.wrapMethod(callback, function instrumentMethodWithCallbackBeforeCallback () {
        instrumentMethodAfter.apply(this, [context])
        return {}
      }, null)

      // Override callback with wrapped one
      args[context.options.callbackIndex] = wrappedCallback

      // Call base
      return instrumentMethodBefore.apply(this, [context].concat(args))
    }.bind(this), null, context)

    wrappedMethod.original = ref

    return wrappedMethod
  },

  instrumentMethod: function (module, fn, transaction, type, options) {
    options = options || {}
    var ref
    var nameParts = []

    if (options.prefix) {
      nameParts.push(options.prefix)
    }

    if (fn) {
      nameParts.push(fn)
    }

    var name = nameParts.join('.')

    if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
      transaction = options.traceBuffer
    }

    if (options.instrumentModule) {
      ref = module
    } else {
      ref = module[fn]
    }

    if (!config.get('isInstalled')) {
      logger.log('opbeat.instrumentation.instrumentMethod.not.installed')
      return ref
    }

    if(typeof type === 'function') {
      traceType = type.call(options)
    } else {
      traceType = type
    }

    var context = {
      traceName: name,
      traceType: traceType,
      options: options,
      fn: fn,
      transaction: transaction
    }

    var wrappedMethod = this.wrapMethod(ref, instrumentMethodBefore, instrumentMethodAfter, context)
    wrappedMethod.original = ref

    if (options.override) {
      module[fn] = wrappedMethod
    }

    return wrappedMethod
  },

  instrumentStaticModule: function ($delegate, $injector, options) {

    var self = this;

    var opbeatInstrumentInstanceWrapperFunction  =  function () {
      var args = Array.prototype.slice.call(arguments)
      var wrapper = $delegate.apply(this, args)

      options.wrapper = {
        args: args
      }

      self.instrumentObject(wrapper, $injector, options)
      return wrapper
    }

    // Copy all static properties over
    for (var key in $delegate) {
      if ($delegate.hasOwnProperty(key)) {
        opbeatInstrumentInstanceWrapperFunction[key] = $delegate[key]
      }
    }

    return opbeatInstrumentInstanceWrapperFunction

  },

  instrumentModule: function (module, $injector, options) {
    options = options || {}
    var that = this

    if (!config.get('isInstalled')) {
      logger.log('opbeat.instrumentation.instrumentModule.not.installed')
      return module
    }

    var wrapper = function () {
      var fn = module
      var args = Array.prototype.slice.call(arguments)
      var transaction = transactionStore.getRecentByUrl($injector.get('$location').absUrl())

      if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
        transaction = options.traceBuffer
      }

      if (transaction) {
        fn = that.instrumentMethod(module, '', transaction, options.type, {
          prefix: options.prefix,
          override: false,
          instrumentModule: true,
          signatureFormatter: options.signatureFormatter,
          config: options.config
        })
      } else {
        logger.log('%c instrumentModule.error.transaction.missing', 'background-color: #ffff00', module)
      }

      return fn.apply(module, args)
    }

    // Copy all properties over
    for (var key in module) {
      if (module.hasOwnProperty(key)) {
        wrapper[key] = module[key]
      }
    }

    // Instrument functions
    this.getObjectFunctions(module).forEach(function (funcScope) {
      wrapper[funcScope.property] = function () {
        var fn = funcScope.ref
        var args = Array.prototype.slice.call(arguments)
        var transaction = transactionStore.getRecentByUrl($injector.get('$location').absUrl())

        if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
          transaction = options.traceBuffer
        }

        if (transaction) {
          fn = that.instrumentMethod(module, funcScope.property, transaction, options.type, {
            prefix: options.prefix,
            override: true,
            signatureFormatter: options.signatureFormatter
          })
        } else {
          logger.log('%c instrumentModule.error.transaction.missing', 'background-color: #ffff00', module)
        }

        return fn.apply(module, args)
      }
    })

    return wrapper
  },

  instrumentObject: function (object, $injector, options) {
    options = options || {}

    if (!config.get('isInstalled')) {
      logger.log('opbeat.instrumentation.instrumentObject.not.installed')
      return object
    }

    var transaction
    if (options.transaction) {
      transaction = options.transaction
    } else {
      var url = $injector.get('$location').absUrl()
      transaction = transactionStore.getRecentByUrl(url)
    }

    if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
      transaction = options.traceBuffer
    }

    if (transaction) {
      // Instrument static functions
      this.getObjectFunctions(object).forEach(function (funcScope) {
        options.override = true;
        this.instrumentMethod(object, funcScope.property, transaction, options.type, options)
      }.bind(this))
    } else {
      logger.log('%c instrumentObject.error.transaction.missing', 'background-color: #ffff00', object)
    }

    return object
  },

  uninstrumentMethod: function (module, fn) {
    var ref = module[fn]
    if (ref.original) {
      module[fn] = ref.original
    }
  },

  getObjectFunctions: function (scope) {
    return Object.keys(scope).filter(function (key) {
      return typeof scope[key] === 'function'
    }).map(function (property) {
      var ref = scope[property]
      return {
        scope: scope,
        property: property,
        ref: ref
      }
    })
  },

  getControllerInfoFromArgs: function (args) {
    var scope, name

    if (typeof args[0] === 'string') {
      name = args[0]
    } else if (typeof args[0] === 'function') {
      name = args[0].name

      // Function has been wrapped by us, use original function name
      if (name === 'opbeatFunctionWrapper' && args[0].original) {
        name = args[0].original.name
      }
    }

    if (typeof args[1] === 'object') {
      scope = args[1].$scope
    }

    return {
      scope: scope,
      name: name
    }
  },

  resolveAngularDependenciesByType: function ($rootElement, type) {
    var appName = $rootElement.attr('ng-app') || config.get('angularAppName')

    if (!appName) {
      return []
    }

    return window.angular.module(appName)._invokeQueue.filter(function (m) {
      return m[1] === type
    }).map(function (m) {
      return m[2][0]
    })
  }
}

function instrumentMethodBefore (context) {
  // Optimized copy of arguments (V8 https://github.com/GoogleChrome/devtools-docs/issues/53#issuecomment-51941358)
  var args = new Array(arguments.length)
  for (var i = 0, l = arguments.length; i < l; i++) {
    args[i] = arguments[i]
  }

  args = args.slice(1)

  var name = context.traceName
  var transaction = context.transaction

  if (context.options.signatureFormatter) {
    name = context.options.signatureFormatter.apply(this, [context.fn, args, context.options])
  }

  if (transaction) {
    var trace = transaction.startTrace(name, context.traceType, context.options)
    context.trace = trace
  } else {
    logger.log('%c instrumentMethodBefore.error.transaction.missing', 'background-color: #ffff00', context)
  }

  return {
    args: args
  }
}

function instrumentMethodAfter (context) {
  if (context.trace) {
    context.trace.end()
  }
}

function extractNamedFunctionArgs (fn) {
  var fnText = fn.toString().replace(STRIP_COMMENTS, '')
  var argDecl = fnText.match(FN_ARGS)

  if (argDecl.length === 2) {
    return argDecl[1].split(FN_ARG_SPLIT)
  }

  return []
}

function buildWrapperFunction (ctx, funcArguments) {
  var funcBody = 'var args = new Array(arguments.length)\n' +
    'for (var i = 0, l = arguments.length; i < l; i++) {\n' +
    '  args[i] = arguments[i]\n' +
    '}\n' +
    '// Before callback\n' +
    'if (typeof _opbeatBefore === "function") {\n' +
    'var beforeData = _opbeatBefore.apply(this, [_opbeatContext].concat(args))\n' +
    'if (beforeData.args) {\n' +
    ' args = beforeData.args\n' +
    '}\n' +
    '}\n' +
    '// Execute original function\n' +
    'var result = _opbeatOriginalFunction.apply(this, args)\n' +
    '// After callback\n' +
    'if (typeof _opbeatAfter === "function") {\n' +
    '  // After + Promise handling\n' +
    '  if (result && typeof result.then === "function") {\n' +
    '    result.finally(function () {\n' +
    '      _opbeatAfter.apply(this, [_opbeatContext].concat(args))\n' +
    '    }.bind(this))\n' +
    '  } else {\n' +
    '    _opbeatAfter.apply(this, [_opbeatContext].concat(args))\n' +
    '  }\n' +
    '}\n' +
    'return result\n'

  var newBody = []
  for (var k in ctx) {
    var i = 'var ' + k + ' = ctx["' + k + '"];'
    newBody.push(i)
  }

  var res = 'return function opbeatFunctionWrapper(' + funcArguments + '){ ' + funcBody + ' }'
  newBody.push(res)
  var F = new Function('ctx', newBody.join('\n')) // eslint-disable-line no-new-func
  return F(ctx)
}
<commit_msg>Make it possible to pass `prefix` option as function<commit_after>var logger = require('../lib/logger')
var config = require('../lib/config')
var transactionStore = require('./transactionStore')

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m
var FN_ARG_SPLIT = /,/
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg

module.exports = {
  wrapMethod: function (_opbeatOriginalFunction, _opbeatBefore, _opbeatAfter, _opbeatContext) {
    var namedArguments = extractNamedFunctionArgs(_opbeatOriginalFunction).join(',')
    var context = {
      _opbeatOriginalFunction: _opbeatOriginalFunction,
      _opbeatBefore: _opbeatBefore,
      _opbeatAfter: _opbeatAfter,
      _opbeatContext: _opbeatContext
    }

    return buildWrapperFunction(context, namedArguments)
  },

  instrumentMethodWithCallback: function (fn, fnName, transaction, type, options) {
    options = options || {}
    var nameParts = []

    if (!config.get('isInstalled')) {
      logger.log('opbeat.instrumentation.instrumentMethodWithCallback.not.installed')
      return fn
    }

    if (options.prefix) {
      if (typeof options.prefix === 'function') {
        args = options.wrapper ? options.wrapper.args : []
        options.prefix = options.prefix.call(this, args)
      }
      nameParts.push(options.prefix)
    }

    if (fnName) {
      nameParts.push(fnName)
    }

    if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
      transaction = options.traceBuffer
    }

    var name = nameParts.join('.')
    var ref = fn
    var context = {
      traceName: name,
      traceType: type,
      options: options,
      fn: fn,
      transaction: transaction
    }

    var wrappedMethod = this.wrapMethod(ref, function instrumentMethodWithCallbackBefore (context) {
      var args = Array.prototype.slice.call(arguments).slice(1)
      var callback = args[options.callbackIndex]

      // Wrap callback
      var wrappedCallback = this.wrapMethod(callback, function instrumentMethodWithCallbackBeforeCallback () {
        instrumentMethodAfter.apply(this, [context])
        return {}
      }, null)

      // Override callback with wrapped one
      args[context.options.callbackIndex] = wrappedCallback

      // Call base
      return instrumentMethodBefore.apply(this, [context].concat(args))
    }.bind(this), null, context)

    wrappedMethod.original = ref

    return wrappedMethod
  },

  instrumentMethod: function (module, fn, transaction, type, options) {
    options = options || {}
    var ref
    var nameParts = []

    if (options.prefix) {
      if (typeof options.prefix === 'function') {
        args = options.wrapper ? options.wrapper.args : []
        options.prefix = options.prefix.call(this, args)
      }
      nameParts.push(options.prefix)
    }

    if (fn) {
      nameParts.push(fn)
    }

    var name = nameParts.join('.')

    if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
      transaction = options.traceBuffer
    }

    if (options.instrumentModule) {
      ref = module
    } else {
      ref = module[fn]
    }

    if (!config.get('isInstalled')) {
      logger.log('opbeat.instrumentation.instrumentMethod.not.installed')
      return ref
    }

    if(typeof type === 'function') {
      traceType = type.call(options)
    } else {
      traceType = type
    }

    var context = {
      traceName: name,
      traceType: traceType,
      options: options,
      fn: fn,
      transaction: transaction
    }

    var wrappedMethod = this.wrapMethod(ref, instrumentMethodBefore, instrumentMethodAfter, context)
    wrappedMethod.original = ref

    if (options.override) {
      module[fn] = wrappedMethod
    }

    return wrappedMethod
  },

  instrumentStaticModule: function ($delegate, $injector, options) {

    var self = this;

    var opbeatInstrumentInstanceWrapperFunction  =  function () {
      var args = Array.prototype.slice.call(arguments)
      var wrapper = $delegate.apply(this, args)

      options.wrapper = {
        args: args
      }

      self.instrumentObject(wrapper, $injector, options)
      return wrapper
    }

    // Copy all static properties over
    for (var key in $delegate) {
      if ($delegate.hasOwnProperty(key)) {
        opbeatInstrumentInstanceWrapperFunction[key] = $delegate[key]
      }
    }

    return opbeatInstrumentInstanceWrapperFunction

  },

  instrumentModule: function (module, $injector, options) {
    options = options || {}
    var that = this

    if (!config.get('isInstalled')) {
      logger.log('opbeat.instrumentation.instrumentModule.not.installed')
      return module
    }

    var wrapper = function () {
      var fn = module
      var args = Array.prototype.slice.call(arguments)
      var transaction = transactionStore.getRecentByUrl($injector.get('$location').absUrl())

      if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
        transaction = options.traceBuffer
      }

      if (transaction) {
        fn = that.instrumentMethod(module, '', transaction, options.type, {
          prefix: options.prefix,
          override: false,
          instrumentModule: true,
          signatureFormatter: options.signatureFormatter,
          config: options.config
        })
      } else {
        logger.log('%c instrumentModule.error.transaction.missing', 'background-color: #ffff00', module)
      }

      return fn.apply(module, args)
    }

    // Copy all properties over
    for (var key in module) {
      if (module.hasOwnProperty(key)) {
        wrapper[key] = module[key]
      }
    }

    // Instrument functions
    this.getObjectFunctions(module).forEach(function (funcScope) {
      wrapper[funcScope.property] = function () {
        var fn = funcScope.ref
        var args = Array.prototype.slice.call(arguments)
        var transaction = transactionStore.getRecentByUrl($injector.get('$location').absUrl())

        if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
          transaction = options.traceBuffer
        }

        if (transaction) {
          fn = that.instrumentMethod(module, funcScope.property, transaction, options.type, {
            prefix: options.prefix,
            override: true,
            signatureFormatter: options.signatureFormatter
          })
        } else {
          logger.log('%c instrumentModule.error.transaction.missing', 'background-color: #ffff00', module)
        }

        return fn.apply(module, args)
      }
    })

    return wrapper
  },

  instrumentObject: function (object, $injector, options) {
    options = options || {}

    if (!config.get('isInstalled')) {
      logger.log('opbeat.instrumentation.instrumentObject.not.installed')
      return object
    }

    var transaction
    if (options.transaction) {
      transaction = options.transaction
    } else {
      var url = $injector.get('$location').absUrl()
      transaction = transactionStore.getRecentByUrl(url)
    }

    if (!transaction && options.traceBuffer && !options.traceBuffer.isLocked()) {
      transaction = options.traceBuffer
    }

    if (transaction) {
      // Instrument static functions
      this.getObjectFunctions(object).forEach(function (funcScope) {
        options.override = true;
        this.instrumentMethod(object, funcScope.property, transaction, options.type, options)
      }.bind(this))
    } else {
      logger.log('%c instrumentObject.error.transaction.missing', 'background-color: #ffff00', object)
    }

    return object
  },

  uninstrumentMethod: function (module, fn) {
    var ref = module[fn]
    if (ref.original) {
      module[fn] = ref.original
    }
  },

  getObjectFunctions: function (scope) {
    return Object.keys(scope).filter(function (key) {
      return typeof scope[key] === 'function'
    }).map(function (property) {
      var ref = scope[property]
      return {
        scope: scope,
        property: property,
        ref: ref
      }
    })
  },

  getControllerInfoFromArgs: function (args) {
    var scope, name

    if (typeof args[0] === 'string') {
      name = args[0]
    } else if (typeof args[0] === 'function') {
      name = args[0].name

      // Function has been wrapped by us, use original function name
      if (name === 'opbeatFunctionWrapper' && args[0].original) {
        name = args[0].original.name
      }
    }

    if (typeof args[1] === 'object') {
      scope = args[1].$scope
    }

    return {
      scope: scope,
      name: name
    }
  },

  resolveAngularDependenciesByType: function ($rootElement, type) {
    var appName = $rootElement.attr('ng-app') || config.get('angularAppName')

    if (!appName) {
      return []
    }

    return window.angular.module(appName)._invokeQueue.filter(function (m) {
      return m[1] === type
    }).map(function (m) {
      return m[2][0]
    })
  }
}

function instrumentMethodBefore (context) {
  // Optimized copy of arguments (V8 https://github.com/GoogleChrome/devtools-docs/issues/53#issuecomment-51941358)
  var args = new Array(arguments.length)
  for (var i = 0, l = arguments.length; i < l; i++) {
    args[i] = arguments[i]
  }

  args = args.slice(1)

  var name = context.traceName
  var transaction = context.transaction

  if (context.options.signatureFormatter) {
    name = context.options.signatureFormatter.apply(this, [context.fn, args, context.options])
  }

  if (transaction) {
    var trace = transaction.startTrace(name, context.traceType, context.options)
    context.trace = trace
  } else {
    logger.log('%c instrumentMethodBefore.error.transaction.missing', 'background-color: #ffff00', context)
  }

  return {
    args: args
  }
}

function instrumentMethodAfter (context) {
  if (context.trace) {
    context.trace.end()
  }
}

function extractNamedFunctionArgs (fn) {
  var fnText = fn.toString().replace(STRIP_COMMENTS, '')
  var argDecl = fnText.match(FN_ARGS)

  if (argDecl.length === 2) {
    return argDecl[1].split(FN_ARG_SPLIT)
  }

  return []
}

function buildWrapperFunction (ctx, funcArguments) {
  var funcBody = 'var args = new Array(arguments.length)\n' +
    'for (var i = 0, l = arguments.length; i < l; i++) {\n' +
    '  args[i] = arguments[i]\n' +
    '}\n' +
    '// Before callback\n' +
    'if (typeof _opbeatBefore === "function") {\n' +
    'var beforeData = _opbeatBefore.apply(this, [_opbeatContext].concat(args))\n' +
    'if (beforeData.args) {\n' +
    ' args = beforeData.args\n' +
    '}\n' +
    '}\n' +
    '// Execute original function\n' +
    'var result = _opbeatOriginalFunction.apply(this, args)\n' +
    '// After callback\n' +
    'if (typeof _opbeatAfter === "function") {\n' +
    '  // After + Promise handling\n' +
    '  if (result && typeof result.then === "function") {\n' +
    '    result.finally(function () {\n' +
    '      _opbeatAfter.apply(this, [_opbeatContext].concat(args))\n' +
    '    }.bind(this))\n' +
    '  } else {\n' +
    '    _opbeatAfter.apply(this, [_opbeatContext].concat(args))\n' +
    '  }\n' +
    '}\n' +
    'return result\n'

  var newBody = []
  for (var k in ctx) {
    var i = 'var ' + k + ' = ctx["' + k + '"];'
    newBody.push(i)
  }

  var res = 'return function opbeatFunctionWrapper(' + funcArguments + '){ ' + funcBody + ' }'
  newBody.push(res)
  var F = new Function('ctx', newBody.join('\n')) // eslint-disable-line no-new-func
  return F(ctx)
}
<|endoftext|>