<commit_before>(function() {
  'use strict';
  /**
   * The request history analyser.
   * It takes all available history data for given URL and HTTP method and computes
   * stats for this request.
   */
  Polymer({
    is: 'request-stats-analyser',

    properties: {
      url: String,
      method: String,
      request: Object,
      response: Object,
      analysing: {
        type: Boolean,
        readOnly: true,
        notify: true
      },
      /**
       * A map of calculated medians.
       * - totalTime - Median for request total time
       * - sentTime - Median for sending time
       * - ttfb
       * - receivingTime
       * - connectTime
       * - requestBody - Median for request body size
       * - requestHeaders - Median for request headers size
       * - responseBody - Median for response body size
       * - responseHeaders - Median for response headers size
       */
      medians: {
        type: Object,
        notify: true,
        value: function() {
          return {};
        }
      },
      /**
       * Map of time related data.
       * Data are sorted according to object's created property.
       * - totals - List of total load times
       * - ttfb - List of time to first byte times
       * - sents - List of sent message times
       * - receiveds - List of time to receive message times
       * - connects - List of connection times
       * - labels - chart labels for data. Labets are dates and time strings.
       */
      times: {
        type: Object,
        notify: true,
        value: function() {
          return {};
        }
      },
      /**
       * Map of calculated data sizes transmited from / to client.
       * It contains two properties: request and response.
       * Both properties contains:
       * - body - List of sizes of payload
       * - headers - List of sizes of headers
       * - bLabels - Payload lables for charts
       * - hLabels - Headers lables for charts
       */
      sizes: {
        type: Object,
        notify: true,
        value: function() {
          return {
            request: {},
            response: {}
          };
        }
      },
      // Status codes in `labels` array and number of occurence in `values` array
      statuses: {
        type: Object,
        notify: true,
        value: function() {
          return {
            labels: [],
            values: []
          };
        }
      },
      /**
       * Flags to tell if data has values.
       * Object structure is combined `times` and `sizes` into one object (with this
       * property names as keys) but value is a Boolean determinig if given position has data.
       */
      presence: {
        type: Object,
        notify: true,
        value: function() {
          return {
            times: {
              totals: false
            },
            sizes: {
              request: {
                body: false,
                headers: false
              },
              response: {
                body: false,
                headers: false
              }
            }
          };
        }
      },
      // True if after computation any data are set.
      hasAnyData: {
        type: Boolean,
        notify: true,
        value: false,
        computed: '_hasAnyData(presence.times.totals, presence.sizes.request.body, ' +
          'presence.sizes.request.headers, presence.sizes.response.body, ' +
          'presence.sizes.response.headers)'
      }
    },

    observers: [
      '_autoAnalyseData(url, method)',
      '_responseChanged(response.*)'
    ],

    _getDb: function() {
      return new PouchDB('history-data');
    },

    _hasArrayData: function(arr) {
      return !!(arr.base && arr.base.length);
    },

    _hasAnyData: function() {
      var t = Array.from(arguments);
      return t.some((i) => i === true);
    },

    _autoAnalyseData: function(a, b) {
      this.debounce('history-analyser', () => {
        this.analyseData(a, b);
      }, 1000);
    },

    clear: function() {
      this.set('medians', {});
      this.set('times', {});
      this.set('sizes', {
        request: {},
        response: {}
      });
      this.set('statuses', {
        labels: [],
        values: []
      });
      this.set('presence', {
        times: {
          totals: false
        },
        sizes: {
          request: {
            body: false,
            headers: false
          },
          response: {
            body: false,
            headers: false
          }
        }
      });
    },

    analyseData: function(url, method) {
      url = url || this.url;
      method = method || this.method;
      if (!url || !method) {
        this.clear();
        return;
      }
      this._setAnalysing(true);
      let uri;
      try {
        uri = new URL(url);
        uri.search = '';
        uri.hash = '';
        uri = uri.toString();
      } catch (e) {
        let i = url.indexOf('?');
        if (i !== -1) {
          uri = url.substr(0, i);
        } else {
          uri = url;
        }
      }
      var seek = encodeURIComponent(uri) + '/' + method;
      var db = this._getDb();
      db.allDocs()
      .then((r) => r.rows.filter((i) => i.id.indexOf(seek) !== -1))
      .then((items) => db.allDocs({
        keys: items.map((i) => i.id),
        // jscs:disable
        include_docs: true
        // jscs:enable
      }))
      .then((r) => r.rows.map((i) => i.doc))
      .then((r) => this._processData(r))
      .catch((e) => {
        console.error(e);
        this._setAnalysing(false);
      });
    },

    _processWorkerData: function(e) {
      var data = e.data;

      this.set('medians', data.medians);
      this.set('times', data.times);
      this.set('sizes', data.sizes);
      this.set('statuses', data.statuses);
      this.set('presence', data.presence);
      this._setAnalysing(false);
      this.fire('request-stats-analysed');
    },

    _processData: function(docs) {
      if (this._worker) {
        return this._worker.postMessage(docs);
      }
      var blob = new Blob([this.$.worker.textContent]);
      this._workerUrl = window.URL.createObjectURL(blob);
      this._worker = new Worker(this._workerUrl);
      this._worker.onmessage = this._processWorkerData.bind(this);
      this._worker.postMessage(docs);
    },
    /**
     * Calculate a median from the array of numbers.
     */
    calculateMedian: function(arr) {
      if (!arr || arr.length === 0) {
        return 0;
      }
      if (arr.length === 1) {
        return Math.round(arr[0]);
      }
      var copy = arr.slice(0).sort(function(a, b) {
        return a - b;
      });
      var half = Math.floor(copy.length / 2);
      if (copy.length % 2 === 0) {
        // If there is an even number of results,
        // the median will be the mean of the two central numbers.
        return Math.round((copy[half - 1] + copy[half]) / 2.0);
      }
      // If there is an odd number of results, the median is the middle number.
      return Math.round(copy[half]);
    },

    _responseChanged: function() {
      if (!this.response || !this.response.stats) {
        // not ready.
        return;
      }
      this._setAnalysing(true);
      var cp = Object.assign({}, this.response);
      delete cp._headers;
      delete cp._body;
      delete cp.ok;
      delete cp.redirects;
      delete cp.statusText;
      var post = {
        request: {
          headers: this.request.headers,
          payload: this.request.payload
        },
        response: cp
      };
      if (this._responseWorker) {
        return this._responseWorker.postMessage(post);
      }
      var blob = new Blob([this.$.responseProcess.textContent]);
      this._responseWorkerUrl = window.URL.createObjectURL(blob);
      this._responseWorker = new Worker(this._responseWorkerUrl);
      this._responseWorker.onmessage = this._processResponseWorkerData.bind(this);
      this._responseWorker.postMessage(post);
    },

    _processResponseWorkerData: function(e) {
      var data = e.data;
      this.push('times.labels', data.times.label);
      this.push('times.totals', data.times.total);
      this.push('times.ttfb', data.times.ttfb);
      this.push('times.connects', data.times.connect);
      this.push('times.receiveds', data.times.received);
      this.push('times.sents', data.times.sent);
      if (data.times.ssl && (data.times.ssl > -1 || (this.time.ssl && this.time.ssl.length))) {
        if (data.times.ssl === -1) {
          data.times.ssl = 0;
        }
        this.push('times.ssl', data.times.ssl);
      }

      if (data.sizes.request.headers) {
        this.push('sizes.request.hLabels', data.sizes.request.hLabel);
        this.push('sizes.request.headers', data.sizes.request.headers);
      }
      if (data.sizes.request.body) {
        this.push('sizes.request.bLabels', data.sizes.request.bLabel);
        this.push('sizes.request.body', data.sizes.request.body);
      }
      if (data.sizes.response.headers) {
        this.push('sizes.response.hLabels', data.sizes.response.hLabel);
        this.push('sizes.response.headers', data.sizes.response.headers);
      }
      if (data.sizes.response.body) {
        this.push('sizes.response.bLabels', data.sizes.response.bLabel);
        this.push('sizes.response.body', data.sizes.response.body);
      }

      this.set('medians.total', this.calculateMedian(this.times.totals));
      this.set('medians.ttfb', this.calculateMedian(this.times.ttfb));
      this.set('medians.sent', this.calculateMedian(this.times.sents));
      this.set('medians.receiving', this.calculateMedian(this.times.receiveds));
      this.set('medians.connect', this.calculateMedian(this.times.connects));
      this.set('medians.ssl', this.calculateMedian(this.times.ssl));
      this.set('medians.requestBody', this.calculateMedian(this.sizes.request.body));
      this.set('medians.requestHeaders', this.calculateMedian(this.sizes.request.headers));
      this.set('medians.responseBody', this.calculateMedian(this.sizes.response.body));
      this.set('medians.responseHeaders', this.calculateMedian(this.sizes.response.headers));

      if (!this.statuses.labels || !this.statuses.labels.length) {
        this.statuses = {
          labels: [],
          values: []
        };
      }
      var currentCode = String(this.response.status);
      var statusIndex = this.statuses.labels.findIndex((i) => i === currentCode);
      if (statusIndex === -1) {
        this.push('statuses.labels', currentCode);
        this.push('statuses.values', 1);
      } else {
        let cnt = this.statuses.values[statusIndex];
        this.set('statuses.values.' + statusIndex, ++cnt);
      }

      this.set('presence', {
        times: {
          totals: !!(this.times.totals && this.times.totals.length),
          ttfb: !!(this.times.ttfb && this.times.ttfb.length),
          sents: !!(this.times.sents && this.times.sents.length),
          receiveds: !!(this.times.receiveds && this.times.receiveds.length),
          connects: !!(this.times.connects && this.times.connects.length),
          labels: !!(this.times.labels && this.times.labels.length),
          ssl: !!(this.times.ssl && this.times.ssl.length),
        },
        sizes: {
          request: {
            body: !!(this.sizes.request.body && this.sizes.request.body.length),
            headers: !!(this.sizes.request.headers && this.sizes.request.headers.length)
          },
          response: {
            body: !!(this.sizes.response.body && this.sizes.response.body.length),
            headers: !!(this.sizes.response.headers && this.sizes.response.headers.length)
          }
        },
        statuses: !!(this.statuses.labels && this.statuses.labels.length)
      });

      this._setAnalysing(false);
      this.fire('request-stats-analysed');
    }
  });
})();
<commit_msg>Added performance check to the analyser<commit_after>(function() {
  'use strict';
  /**
   * The request history analyser.
   * It takes all available history data for given URL and HTTP method and computes
   * stats for this request.
   */
  Polymer({
    is: 'request-stats-analyser',

    properties: {
      url: String,
      method: String,
      request: Object,
      response: Object,
      analysing: {
        type: Boolean,
        readOnly: true,
        notify: true
      },
      /**
       * A map of calculated medians.
       * - totalTime - Median for request total time
       * - sentTime - Median for sending time
       * - ttfb
       * - receivingTime
       * - connectTime
       * - requestBody - Median for request body size
       * - requestHeaders - Median for request headers size
       * - responseBody - Median for response body size
       * - responseHeaders - Median for response headers size
       */
      medians: {
        type: Object,
        notify: true,
        value: function() {
          return {};
        }
      },
      /**
       * Map of time related data.
       * Data are sorted according to object's created property.
       * - totals - List of total load times
       * - ttfb - List of time to first byte times
       * - sents - List of sent message times
       * - receiveds - List of time to receive message times
       * - connects - List of connection times
       * - labels - chart labels for data. Labets are dates and time strings.
       */
      times: {
        type: Object,
        notify: true,
        value: function() {
          return {};
        }
      },
      /**
       * Map of calculated data sizes transmited from / to client.
       * It contains two properties: request and response.
       * Both properties contains:
       * - body - List of sizes of payload
       * - headers - List of sizes of headers
       * - bLabels - Payload lables for charts
       * - hLabels - Headers lables for charts
       */
      sizes: {
        type: Object,
        notify: true,
        value: function() {
          return {
            request: {},
            response: {}
          };
        }
      },
      // Status codes in `labels` array and number of occurence in `values` array
      statuses: {
        type: Object,
        notify: true,
        value: function() {
          return {
            labels: [],
            values: []
          };
        }
      },
      /**
       * Flags to tell if data has values.
       * Object structure is combined `times` and `sizes` into one object (with this
       * property names as keys) but value is a Boolean determinig if given position has data.
       */
      presence: {
        type: Object,
        notify: true,
        value: function() {
          return {
            times: {
              totals: false
            },
            sizes: {
              request: {
                body: false,
                headers: false
              },
              response: {
                body: false,
                headers: false
              }
            }
          };
        }
      },
      // True if after computation any data are set.
      hasAnyData: {
        type: Boolean,
        notify: true,
        value: false,
        computed: '_hasAnyData(presence.times.totals, presence.sizes.request.body, ' +
          'presence.sizes.request.headers, presence.sizes.response.body, ' +
          'presence.sizes.response.headers)'
      }
    },

    observers: [
      '_autoAnalyseData(url, method)',
      '_responseChanged(response.*)'
    ],

    _getDb: function() {
      return new PouchDB('history-data');
    },

    _hasArrayData: function(arr) {
      return !!(arr.base && arr.base.length);
    },

    _hasAnyData: function() {
      var t = Array.from(arguments);
      return t.some((i) => i === true);
    },

    _autoAnalyseData: function(a, b) {
      this.debounce('history-analyser', () => {
        this.analyseData(a, b);
      }, 1000);
    },

    clear: function() {
      this.set('medians', {});
      this.set('times', {});
      this.set('sizes', {
        request: {},
        response: {}
      });
      this.set('statuses', {
        labels: [],
        values: []
      });
      this.set('presence', {
        times: {
          totals: false
        },
        sizes: {
          request: {
            body: false,
            headers: false
          },
          response: {
            body: false,
            headers: false
          }
        }
      });
    },

    analyseData: function(url, method) {
      url = url || this.url;
      method = method || this.method;
      if (!url || !method) {
        this.clear();
        return;
      }
      this._setAnalysing(true);
      let uri;
      try {
        uri = new URL(url);
        uri.search = '';
        uri.hash = '';
        uri = uri.toString();
      } catch (e) {
        let i = url.indexOf('?');
        if (i !== -1) {
          uri = url.substr(0, i);
        } else {
          uri = url;
        }
      }
      // console.time('History data analyser - search');
      // console.time('History data analyser - all');
      this.startTime = performance.now();
      // performance.mark('history-data-analyser-start');
      var seek = encodeURIComponent(uri) + '/' + method;
      var db = this._getDb();
      db.allDocs()
      .then((r) => {
        // performance.mark('history-data-analyser-keys-query');
        return r.rows.filter((i) => i.id.indexOf(seek) !== -1);
      })
      .then((items) => {
        // performance.mark('history-data-analyser-keys-filter');
        return db.allDocs({
          keys: items.map((i) => i.id),
          // jscs:disable
          include_docs: true
          // jscs:enable
        });
      })
      .then((r) => {
        // performance.mark('history-data-analyser-getting-data');
        return r.rows.map((i) => i.doc);
      })
      .then((r) => {
        // performance.mark('history-data-analyser-mapping-data');
        // console.timeEnd('History data analyser - search');
        return this._processData(r);
      })
      .catch((e) => {
        console.error(e);
        this._setAnalysing(false);
      });
    },

    _processWorkerData: function(e) {
      var data = e.data;
      this.set('medians', data.medians);
      this.set('times', data.times);
      this.set('sizes', data.sizes);
      this.set('statuses', data.statuses);
      this.set('presence', data.presence);
      this._setAnalysing(false);
      this.fire('request-stats-analysed');
      // console.timeEnd('History data analyser - process');
      // console.timeEnd('History data analyser - all');
      // performance.mark('history-data-analyser-processing');
      var execTime = performance.now() - this.startTime;
      console.log('execTime', execTime);
      this.fire('send-analytics', {
        type: 'timing',
        category: 'History assistant',
        variable: 'Analysing history',
        value: execTime
      });
    },

    _processData: function(docs) {
      // console.time('History data analyser - process');
      // performance.mark('history-data-analyser-process-data');
      if (this._worker) {
        return this._worker.postMessage(docs);
      }
      var blob = new Blob([this.$.worker.textContent]);
      this._workerUrl = window.URL.createObjectURL(blob);
      this._worker = new Worker(this._workerUrl);
      this._worker.onmessage = this._processWorkerData.bind(this);
      this._worker.postMessage(docs);
    },
    /**
     * Calculate a median from the array of numbers.
     */
    calculateMedian: function(arr) {
      if (!arr || arr.length === 0) {
        return 0;
      }
      if (arr.length === 1) {
        return Math.round(arr[0]);
      }
      var copy = arr.slice(0).sort(function(a, b) {
        return a - b;
      });
      var half = Math.floor(copy.length / 2);
      if (copy.length % 2 === 0) {
        // If there is an even number of results,
        // the median will be the mean of the two central numbers.
        return Math.round((copy[half - 1] + copy[half]) / 2.0);
      }
      // If there is an odd number of results, the median is the middle number.
      return Math.round(copy[half]);
    },

    _responseChanged: function() {
      if (!this.response || !this.response.stats) {
        // not ready.
        return;
      }
      this._setAnalysing(true);
      var cp = Object.assign({}, this.response);
      delete cp._headers;
      delete cp._body;
      delete cp.ok;
      delete cp.redirects;
      delete cp.statusText;
      var post = {
        request: {
          headers: this.request.headers,
          payload: this.request.payload
        },
        response: cp
      };
      if (this._responseWorker) {
        return this._responseWorker.postMessage(post);
      }
      var blob = new Blob([this.$.responseProcess.textContent]);
      this._responseWorkerUrl = window.URL.createObjectURL(blob);
      this._responseWorker = new Worker(this._responseWorkerUrl);
      this._responseWorker.onmessage = this._processResponseWorkerData.bind(this);
      this._responseWorker.postMessage(post);
    },

    _processResponseWorkerData: function(e) {
      var data = e.data;
      this.push('times.labels', data.times.label);
      this.push('times.totals', data.times.total);
      this.push('times.ttfb', data.times.ttfb);
      this.push('times.connects', data.times.connect);
      this.push('times.receiveds', data.times.received);
      this.push('times.sents', data.times.sent);
      if (data.times.ssl && (data.times.ssl > -1 || (this.time.ssl && this.time.ssl.length))) {
        if (data.times.ssl === -1) {
          data.times.ssl = 0;
        }
        this.push('times.ssl', data.times.ssl);
      }

      if (data.sizes.request.headers) {
        this.push('sizes.request.hLabels', data.sizes.request.hLabel);
        this.push('sizes.request.headers', data.sizes.request.headers);
      }
      if (data.sizes.request.body) {
        this.push('sizes.request.bLabels', data.sizes.request.bLabel);
        this.push('sizes.request.body', data.sizes.request.body);
      }
      if (data.sizes.response.headers) {
        this.push('sizes.response.hLabels', data.sizes.response.hLabel);
        this.push('sizes.response.headers', data.sizes.response.headers);
      }
      if (data.sizes.response.body) {
        this.push('sizes.response.bLabels', data.sizes.response.bLabel);
        this.push('sizes.response.body', data.sizes.response.body);
      }

      this.set('medians.total', this.calculateMedian(this.times.totals));
      this.set('medians.ttfb', this.calculateMedian(this.times.ttfb));
      this.set('medians.sent', this.calculateMedian(this.times.sents));
      this.set('medians.receiving', this.calculateMedian(this.times.receiveds));
      this.set('medians.connect', this.calculateMedian(this.times.connects));
      this.set('medians.ssl', this.calculateMedian(this.times.ssl));
      this.set('medians.requestBody', this.calculateMedian(this.sizes.request.body));
      this.set('medians.requestHeaders', this.calculateMedian(this.sizes.request.headers));
      this.set('medians.responseBody', this.calculateMedian(this.sizes.response.body));
      this.set('medians.responseHeaders', this.calculateMedian(this.sizes.response.headers));

      if (!this.statuses.labels || !this.statuses.labels.length) {
        this.statuses = {
          labels: [],
          values: []
        };
      }
      var currentCode = String(this.response.status);
      var statusIndex = this.statuses.labels.findIndex((i) => i === currentCode);
      if (statusIndex === -1) {
        this.push('statuses.labels', currentCode);
        this.push('statuses.values', 1);
      } else {
        let cnt = this.statuses.values[statusIndex];
        this.set('statuses.values.' + statusIndex, ++cnt);
      }

      this.set('presence', {
        times: {
          totals: !!(this.times.totals && this.times.totals.length),
          ttfb: !!(this.times.ttfb && this.times.ttfb.length),
          sents: !!(this.times.sents && this.times.sents.length),
          receiveds: !!(this.times.receiveds && this.times.receiveds.length),
          connects: !!(this.times.connects && this.times.connects.length),
          labels: !!(this.times.labels && this.times.labels.length),
          ssl: !!(this.times.ssl && this.times.ssl.length),
        },
        sizes: {
          request: {
            body: !!(this.sizes.request.body && this.sizes.request.body.length),
            headers: !!(this.sizes.request.headers && this.sizes.request.headers.length)
          },
          response: {
            body: !!(this.sizes.response.body && this.sizes.response.body.length),
            headers: !!(this.sizes.response.headers && this.sizes.response.headers.length)
          }
        },
        statuses: !!(this.statuses.labels && this.statuses.labels.length)
      });

      this._setAnalysing(false);
      this.fire('request-stats-analysed');
    }
  });
})();
<|endoftext|>


<commit_before>const express = require('express');
const path = require('path');
const viewLib = require('./lib/view');
const cacheLib = require('./lib/cache');
const routerLib = require('./lib/router');
const helperLib = require('./lib/helper');
const modelLib = require('./lib/model');
const Signer = require('./lib/signer');

/*
|* dp for Node
|*
|* @param {Object} [options] {
|         app: Object, Express App.
|         trustProxy: Booelan, trust proxy, Default is null.
|         apppath: String, Application Absolute Path [Required]
|         controllerPath: String, View Path, Default is 'controller'
|         viewPath: String, View Path, Default is 'view'
|         port: Int, Binding port, If specified, will bind automatically.
|         debug: Boolean, Debug Mode, Default is false.
|         preMiddlewares: Object, Pre middlewares, Default is null.
|         compression: Boolean, Compression, Default is true.
|         enhanceSecurity: Boolean, Security(Helmet), Default is true.
|         minifyRemoveLineBreakWhitespace: Boolean,
|           Whether if remove line break whitespace or not, Default is true.
|         requestSizeLimit: Numeric, Limit size of the request.
|         error: Function, Error Handler.
|         errorLogging: Boolean, Error Logging enabled, Default is true.
|         logging: Boolean, Logging enabled, Default is false.
|         static: [String], Static file paths.
|         redirectNakedToWWW: Boolean or Object,
|           Whether redirect naked domain to www or not, Default is false.
|         cacheDsn: ([)Object(]), Pre-defined cache dsns, Default is empty.
|         databaseDsn: [Object], Pre-defined database dsn key as `key`, Default is empty.
|         mode: String, Supported values are `web` or `job`, Default is `web`.
|         session: {
|             driver: String, ENUM('redis'), Session Driver, Default is undefined, disabled.
|             algorithm: String, Hash algorithm for signing Session IDs, Default is 'sha256'.
|             secret: String, Session secret key for signing, Default is dp provided value.
|             signPrefix: String, Prefix for all signed Session IDs, Default is cookie.signPrefix.
|             ttl: Int?, Time-to-live for cookies or null for session, Default is 3600*24*30.
|             volatility: Boolean, Whether if use session cookie or not, Default is false.
|             connection: Object, Options for Driver, Default is empty object({}).
|         }
|         cookie: {
|             algorithm: String, Hash algorithm for signing cookies, Default is 'sha256'.
|             secret: String, Cookie secret key for signing, Default is dp provided value.
|             signPrefix: String, Prefix for all signed cookies, Default is 's:'.
|             ttl: Int?, Time-to-live for cookies or null for session, Default is 3600*24*30.
|         }
|     }
|*
*/
module.exports = (options) => {
  let { app } = options;
  const config = {};

  const defaultVal = (val, defVal) => (typeof val === 'undefined' ? defVal : val);
  const arrayToObj = (arr, key) => (Array.isArray(arr) ? arr.reduce((o, value) => (
    Object.defineProperty(o, value[key], {
      value, writable: true, enumerable: true, configurable: true,
    })
  ), {}) : arr);

  config.mode = (options.mode || 'web').toLowerCase();
  config.debug = defaultVal(options.debug, false);

  config.cfg = {};
  config.cfg.apppath = options.apppath;
  config.cfg.controller = options.apppath + (options.controllerPath || '/controller');
  config.cfg.view = options.apppath + (options.viewPath || '/view');
  config.cfg.minifyRemoveLineBreakWhitespace = defaultVal(
    options.minifyRemoveLineBreakWhitespace,
    true
  );
  config.cfg.requestSizeLimit = options.requestSizeLimit || '0.5mb';
  config.cfg.errorLogging = defaultVal(options.errorLogging, true);

  config.handler = { error: undefined };

  if (typeof options.error === 'function') {
    const errorHandler = options.error;
    config.handler.error = (controller, error, statusCode) => (
      new Promise((resolve) => resolve(errorHandler(controller, error, statusCode))).catch((e) => {
        console.error('Error while handling error:', e);

        if (controller && controller.finisher) {
          controller.finisher.error('An error has occurred.');
        }

        return false;
      })
    );
  }

  config.cfg.viewHelpers = options.viewHelpers || {};

  if (!app) app = express();
  config.app = app;

  if (options.logging) {
    app.use(require('morgan')('short', {}));
  }

  let redirectOpts = options.redirectNakedToWWW;
  if (redirectOpts) {
    if (typeof redirectOpts !== 'object' && typeof redirectOpts !== 'function') redirectOpts = {};
    app.use(require('express-naked-redirect')(redirectOpts));
  }

  if (defaultVal(options.compression, true)) {
    app.use(require('compression')());
  }

  if (defaultVal(options.enhanceSecurity, true)) {
    app.use(require('helmet')());
  }

  if (defaultVal(options.cookieEnabled, true)) {
    app.use(require('cookie-parser')());
  }

  if (options.preMiddlewares) {
    let { preMiddlewares } = options;
    if (!Array.isArray(preMiddlewares)) preMiddlewares = [preMiddlewares];

    for (let i = 0; i < preMiddlewares.length; i += 1) {
      app.use(preMiddlewares[i]);
    }
  }

  if (defaultVal(options.session, {})) {
    const ns = options.session || {};
    let isSessionCookie = ns.volatility;
    if (typeof isSessionCookie !== 'undefined') {
      console.warn('dp-node: options.session.volatility is deprecated. Use options.session.ttl = null instead.');
    } else {
      isSessionCookie = ns.ttl === null;
    }
    config.cfg.session = {
      driver: ns.driver || 'stub',
      connection: ns.connection,
      ttlMs: isSessionCookie ? null : (ns.ttl || 3600 * 24 * 30) * 1e3,
      cookieName: ns.cookieName || 'DSESSIONID',
      keyLength: ns.keyLength || 32,
      signer: new Signer(
        ns.algorithm || 'sha256',
        ns.secret || 'dR@9oNp0W@r~NoD2',
        ns.signPrefix || (options.cookie && options.cookie.signPrefix) || 's:'
      ),
    };
  }

  if (defaultVal(options.cookie, {})) {
    const ns = options.cookie || {};
    let isSessionCookie = ns.volatility;
    if (typeof isSessionCookie !== 'undefined') {
      console.warn('dp-node: options.cookie.volatility is deprecated. Use options.cookie.ttl = null instead.');
    } else {
      isSessionCookie = ns.ttl === null;
    }
    config.cfg.cookie = {
      ttlMs: isSessionCookie ? null : (ns.ttl || 3600 * 24 * 30) * 1e3,
      signer: new Signer(
        ns.algorithm || 'sha256',
        ns.secret || 'dR@9oNp0W@r~Co0K2',
        ns.signPrefix || 's:'
      ),
    };
  }

  if (defaultVal(options.cacheDsn, {})) {
    config.cfg.cacheDsn = arrayToObj(options.cacheDsn, 'key') || {};
  }

  if (defaultVal(options.databaseDsn, [])) {
    config.cfg.databaseDsn = options.databaseDsn || [];
  }

  if (!options.suppressDefaultStaticFiles) {
    app.use('/dp', express.static(path.resolve(__dirname, '/lib/static')));
  }

  if (options.static) {
    const paths = options.static;
    (Array.isArray(paths) ? paths : [paths])
      .forEach((e) => app.use(express.static(`${options.apppath}/${e}`)));
  }

  if (options.trustProxy) {
    app.enable('trust proxy', typeof options.trustProxy === 'boolean' ? undefined : options.trustProxy);
  }

  if (!global.isTest && options.port != null && config.mode !== 'job') {
    let listenOpts = options.port;
    if (!Array.isArray(listenOpts)) listenOpts = [listenOpts];

    app.server = app.listen(...listenOpts);
    if (options.logging) {
      app.server.on('listening', () => {
        const boundAddr = app.server.address();
        let addrRepr;
        switch (boundAddr && boundAddr.family) {
          case 'IPv4': addrRepr = `http://${boundAddr.address}:${boundAddr.port}/`; break;
          case 'IPv6': addrRepr = `http://[${boundAddr.address}]:${boundAddr.port}/`; break;
          default: addrRepr = boundAddr; break;
        }
        console.log('Listening on', addrRepr);
      });
    }
  }

  config.view = viewLib(config);
  config.cache = cacheLib(config);
  config.router = routerLib(config);
  config.helper = helperLib(config);
  config.model = modelLib(config);

  const dp = {
    app,
    listen: app.listen.bind(app),
  };

  // Assign config when test mode enabled.
  if (global.isTest) {
    dp.config = config;
  }

  // for Job mode.
  if (config.mode === 'job') {
    return require('./lib/job')(config);
  }

  app.engine = {
    helper: config.helper,
    model: config.model,
    view: config.view,
    cache: config.cache,
  };

  app.dp = dp;

  return app;
};

module.exports.Tester = require('./lib/tester');
<commit_msg>Add express delegate option.<commit_after>const express = require('express');
const path = require('path');
const viewLib = require('./lib/view');
const cacheLib = require('./lib/cache');
const routerLib = require('./lib/router');
const helperLib = require('./lib/helper');
const modelLib = require('./lib/model');
const Signer = require('./lib/signer');

/*
|* dp for Node
|*
|* @param {Object} [options] {
|         app: Object, Express App.
|         trustProxy: Booelan, trust proxy, Default is null.
|         apppath: String, Application Absolute Path [Required]
|         controllerPath: String, View Path, Default is 'controller'
|         viewPath: String, View Path, Default is 'view'
|         port: Int, Binding port, If specified, will bind automatically.
|         debug: Boolean, Debug Mode, Default is false.
|         preMiddlewares: Object, Pre middlewares, Default is null.
|         compression: Boolean, Compression, Default is true.
|         enhanceSecurity: Boolean, Security(Helmet), Default is true.
|         minifyRemoveLineBreakWhitespace: Boolean,
|           Whether if remove line break whitespace or not, Default is true.
|         requestSizeLimit: Numeric, Limit size of the request.
|         error: Function, Error Handler.
|         errorLogging: Boolean, Error Logging enabled, Default is true.
|         logging: Boolean, Logging enabled, Default is false.
|         static: [String], Static file paths.
|         redirectNakedToWWW: Boolean or Object,
|           Whether redirect naked domain to www or not, Default is false.
|         cacheDsn: ([)Object(]), Pre-defined cache dsns, Default is empty.
|         databaseDsn: [Object], Pre-defined database dsn key as `key`, Default is empty.
|         mode: String, Supported values are `web` or `job`, Default is `web`.
|         session: {
|             driver: String, ENUM('redis'), Session Driver, Default is undefined, disabled.
|             algorithm: String, Hash algorithm for signing Session IDs, Default is 'sha256'.
|             secret: String, Session secret key for signing, Default is dp provided value.
|             signPrefix: String, Prefix for all signed Session IDs, Default is cookie.signPrefix.
|             ttl: Int?, Time-to-live for cookies or null for session, Default is 3600*24*30.
|             volatility: Boolean, Whether if use session cookie or not, Default is false.
|             connection: Object, Options for Driver, Default is empty object({}).
|         }
|         cookie: {
|             algorithm: String, Hash algorithm for signing cookies, Default is 'sha256'.
|             secret: String, Cookie secret key for signing, Default is dp provided value.
|             signPrefix: String, Prefix for all signed cookies, Default is 's:'.
|             ttl: Int?, Time-to-live for cookies or null for session, Default is 3600*24*30.
|         }
|     }
|*
*/
module.exports = (options) => {
  let { app } = options;
  const config = {};

  const defaultVal = (val, defVal) => (typeof val === 'undefined' ? defVal : val);
  const arrayToObj = (arr, key) => (Array.isArray(arr) ? arr.reduce((o, value) => (
    Object.defineProperty(o, value[key], {
      value, writable: true, enumerable: true, configurable: true,
    })
  ), {}) : arr);

  config.mode = (options.mode || 'web').toLowerCase();
  config.debug = defaultVal(options.debug, false);

  config.cfg = {};
  config.cfg.apppath = options.apppath;
  config.cfg.controller = options.apppath + (options.controllerPath || '/controller');
  config.cfg.view = options.apppath + (options.viewPath || '/view');
  config.cfg.minifyRemoveLineBreakWhitespace = defaultVal(
    options.minifyRemoveLineBreakWhitespace,
    true
  );
  config.cfg.requestSizeLimit = options.requestSizeLimit || '0.5mb';
  config.cfg.errorLogging = defaultVal(options.errorLogging, true);

  config.handler = { error: undefined };

  if (typeof options.error === 'function') {
    const errorHandler = options.error;
    config.handler.error = (controller, error, statusCode) => (
      new Promise((resolve) => resolve(errorHandler(controller, error, statusCode))).catch((e) => {
        console.error('Error while handling error:', e);

        if (controller && controller.finisher) {
          controller.finisher.error('An error has occurred.');
        }

        return false;
      })
    );
  }

  config.cfg.viewHelpers = options.viewHelpers || {};

  if (!app) app = express();
  config.app = app;

  if (options.logging) {
    app.use(require('morgan')('short', {}));
  }

  let redirectOpts = options.redirectNakedToWWW;
  if (redirectOpts) {
    if (typeof redirectOpts !== 'object' && typeof redirectOpts !== 'function') redirectOpts = {};
    app.use(require('express-naked-redirect')(redirectOpts));
  }

  if (defaultVal(options.compression, true)) {
    app.use(require('compression')());
  }

  if (defaultVal(options.enhanceSecurity, true)) {
    app.use(require('helmet')());
  }

  if (defaultVal(options.cookieEnabled, true)) {
    app.use(require('cookie-parser')());
  }

  if (options.preMiddlewares) {
    let { preMiddlewares } = options;
    if (!Array.isArray(preMiddlewares)) preMiddlewares = [preMiddlewares];

    for (let i = 0; i < preMiddlewares.length; i += 1) {
      app.use(preMiddlewares[i]);
    }
  }

  if (defaultVal(options.session, {})) {
    const ns = options.session || {};
    let isSessionCookie = ns.volatility;
    if (typeof isSessionCookie !== 'undefined') {
      console.warn('dp-node: options.session.volatility is deprecated. Use options.session.ttl = null instead.');
    } else {
      isSessionCookie = ns.ttl === null;
    }
    config.cfg.session = {
      driver: ns.driver || 'stub',
      connection: ns.connection,
      ttlMs: isSessionCookie ? null : (ns.ttl || 3600 * 24 * 30) * 1e3,
      cookieName: ns.cookieName || 'DSESSIONID',
      keyLength: ns.keyLength || 32,
      signer: new Signer(
        ns.algorithm || 'sha256',
        ns.secret || 'dR@9oNp0W@r~NoD2',
        ns.signPrefix || (options.cookie && options.cookie.signPrefix) || 's:'
      ),
    };
  }

  if (defaultVal(options.cookie, {})) {
    const ns = options.cookie || {};
    let isSessionCookie = ns.volatility;
    if (typeof isSessionCookie !== 'undefined') {
      console.warn('dp-node: options.cookie.volatility is deprecated. Use options.cookie.ttl = null instead.');
    } else {
      isSessionCookie = ns.ttl === null;
    }
    config.cfg.cookie = {
      ttlMs: isSessionCookie ? null : (ns.ttl || 3600 * 24 * 30) * 1e3,
      signer: new Signer(
        ns.algorithm || 'sha256',
        ns.secret || 'dR@9oNp0W@r~Co0K2',
        ns.signPrefix || 's:'
      ),
    };
  }

  if (defaultVal(options.cacheDsn, {})) {
    config.cfg.cacheDsn = arrayToObj(options.cacheDsn, 'key') || {};
  }

  if (defaultVal(options.databaseDsn, [])) {
    config.cfg.databaseDsn = options.databaseDsn || [];
  }

  if (!options.suppressDefaultStaticFiles) {
    app.use('/dp', express.static(path.resolve(__dirname, '/lib/static')));
  }

  if (options.express) {
    options.express(app);
  }

  if (options.static) {
    const paths = options.static;
    (Array.isArray(paths) ? paths : [paths])
      .forEach((e) => app.use(express.static(`${options.apppath}/${e}`)));
  }

  if (options.trustProxy) {
    app.enable('trust proxy', typeof options.trustProxy === 'boolean' ? undefined : options.trustProxy);
  }

  if (!global.isTest && options.port != null && config.mode !== 'job') {
    let listenOpts = options.port;
    if (!Array.isArray(listenOpts)) listenOpts = [listenOpts];

    app.server = app.listen(...listenOpts);
    if (options.logging) {
      app.server.on('listening', () => {
        const boundAddr = app.server.address();
        let addrRepr;
        switch (boundAddr && boundAddr.family) {
          case 'IPv4': addrRepr = `http://${boundAddr.address}:${boundAddr.port}/`; break;
          case 'IPv6': addrRepr = `http://[${boundAddr.address}]:${boundAddr.port}/`; break;
          default: addrRepr = boundAddr; break;
        }
        console.log('Listening on', addrRepr);
      });
    }
  }

  config.view = viewLib(config);
  config.cache = cacheLib(config);
  config.router = routerLib(config);
  config.helper = helperLib(config);
  config.model = modelLib(config);

  const dp = {
    app,
    listen: app.listen.bind(app),
  };

  // Assign config when test mode enabled.
  if (global.isTest) {
    dp.config = config;
  }

  // for Job mode.
  if (config.mode === 'job') {
    return require('./lib/job')(config);
  }

  app.engine = {
    helper: config.helper,
    model: config.model,
    view: config.view,
    cache: config.cache,
  };

  app.dp = dp;

  return app;
};

module.exports.Tester = require('./lib/tester');
<|endoftext|>


<commit_before>import { connect } from 'react-redux';
import { changeSetting } from 'flavours/glitch/actions/settings';
import { createSelector } from 'reselect';
import { Map as ImmutableMap } from 'immutable';
import { useEmoji } from 'flavours/glitch/actions/emojis';
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { EmojiPicker as EmojiPickerAsync } from 'flavours/glitch/util/async-components';
import Overlay from 'react-overlays/lib/Overlay';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import detectPassiveEvents from 'detect-passive-events';
import { buildCustomEmojis, categoriesFromEmojis } from 'flavours/glitch/util/emoji';
import { useSystemEmojiFont } from 'flavours/glitch/util/initial_state';

const messages = defineMessages({
  emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
  emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
  emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°）╯︵ ┻━┻' },
  custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' },
  recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' },
  search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' },
  people: { id: 'emoji_button.people', defaultMessage: 'People' },
  nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' },
  food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' },
  activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' },
  travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' },
  objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
  symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
  flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' },
});

const perLine = 8;
const lines   = 2;

const DEFAULTS = [
  '+1',
  'grinning',
  'kissing_heart',
  'heart_eyes',
  'laughing',
  'stuck_out_tongue_winking_eye',
  'sweat_smile',
  'joy',
  'yum',
  'disappointed',
  'thinking_face',
  'weary',
  'sob',
  'sunglasses',
  'heart',
  'ok_hand',
];

const getFrequentlyUsedEmojis = createSelector([
  state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()),
], emojiCounters => {
  let emojis = emojiCounters
    .keySeq()
    .sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b))
    .reverse()
    .slice(0, perLine * lines)
    .toArray();

  if (emojis.length < DEFAULTS.length) {
    emojis = emojis.concat(DEFAULTS.slice(0, DEFAULTS.length - emojis.length));
  }

  return emojis;
});

const getCustomEmojis = createSelector([
  state => state.get('custom_emojis'),
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
  const aShort = a.get('shortcode').toLowerCase();
  const bShort = b.get('shortcode').toLowerCase();

  if (aShort < bShort) {
    return -1;
  } else if (aShort > bShort ) {
    return 1;
  } else {
    return 0;
  }
}));

const mapStateToProps = state => ({
  custom_emojis: getCustomEmojis(state),
  skinTone: state.getIn(['settings', 'skinTone']),
  frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
});

const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
  onSkinTone: skinTone => {
    dispatch(changeSetting(['skinTone'], skinTone));
  },

  onPickEmoji: emoji => {
    dispatch(useEmoji(emoji));

    if (onPickEmoji) {
      onPickEmoji(emoji);
    }
  },
});

const assetHost = process.env.CDN_HOST || '';
let EmojiPicker, Emoji; // load asynchronously

const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`;
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;

class ModifierPickerMenu extends React.PureComponent {

  static propTypes = {
    active: PropTypes.bool,
    onSelect: PropTypes.func.isRequired,
    onClose: PropTypes.func.isRequired,
  };

  handleClick = e => {
    this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.active) {
      this.attachListeners();
    } else {
      this.removeListeners();
    }
  }

  componentWillUnmount () {
    this.removeListeners();
  }

  handleDocumentClick = e => {
    if (this.node && !this.node.contains(e.target)) {
      this.props.onClose();
    }
  }

  attachListeners () {
    document.addEventListener('click', this.handleDocumentClick, false);
    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
  }

  removeListeners () {
    document.removeEventListener('click', this.handleDocumentClick, false);
    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
  }

  setRef = c => {
    this.node = c;
  }

  render () {
    const { active } = this.props;

    return (

        <button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>

    );
  }

}

class ModifierPicker extends React.PureComponent {

  static propTypes = {
    active: PropTypes.bool,
    modifier: PropTypes.number,
    onChange: PropTypes.func,
    onClose: PropTypes.func,
    onOpen: PropTypes.func,
  };

  handleClick = () => {
    if (this.props.active) {
      this.props.onClose();
    } else {
      this.props.onOpen();
    }
  }

  handleSelect = modifier => {
    this.props.onChange(modifier);
    this.props.onClose();
  }

  render () {
    const { active, modifier } = this.props;

    return (

        <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} />
        <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />

    );
  }

}

@injectIntl
class EmojiPickerMenu extends React.PureComponent {

  static propTypes = {
    custom_emojis: ImmutablePropTypes.list,
    frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
    loading: PropTypes.bool,
    onClose: PropTypes.func.isRequired,
    onPick: PropTypes.func.isRequired,
    style: PropTypes.object,
    placement: PropTypes.string,
    arrowOffsetLeft: PropTypes.string,
    arrowOffsetTop: PropTypes.string,
    intl: PropTypes.object.isRequired,
    skinTone: PropTypes.number.isRequired,
    onSkinTone: PropTypes.func.isRequired,
  };

  static defaultProps = {
    style: {},
    loading: true,
    frequentlyUsedEmojis: [],
  };

  state = {
    modifierOpen: false,
    placement: null,
  };

  handleDocumentClick = e => {
    if (this.node && !this.node.contains(e.target)) {
      this.props.onClose();
    }
  }

  componentDidMount () {
    document.addEventListener('click', this.handleDocumentClick, false);
    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
  }

  componentWillUnmount () {
    document.removeEventListener('click', this.handleDocumentClick, false);
    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
  }

  setRef = c => {
    this.node = c;
  }

  getI18n = () => {
    const { intl } = this.props;

    return {
      search: intl.formatMessage(messages.emoji_search),
      notfound: intl.formatMessage(messages.emoji_not_found),
      categories: {
        search: intl.formatMessage(messages.search_results),
        recent: intl.formatMessage(messages.recent),
        people: intl.formatMessage(messages.people),
        nature: intl.formatMessage(messages.nature),
        foods: intl.formatMessage(messages.food),
        activity: intl.formatMessage(messages.activity),
        places: intl.formatMessage(messages.travel),
        objects: intl.formatMessage(messages.objects),
        symbols: intl.formatMessage(messages.symbols),
        flags: intl.formatMessage(messages.flags),
        custom: intl.formatMessage(messages.custom),
      },
    };
  }

  handleClick = (emoji, event) => {
    if (!emoji.native) {
      emoji.native = emoji.colons;
    }
    if (!event.ctrlKey) {
      this.props.onClose();
    }
    this.props.onPick(emoji);
  }

  handleModifierOpen = () => {
    this.setState({ modifierOpen: true });
  }

  handleModifierClose = () => {
    this.setState({ modifierOpen: false });
  }

  handleModifierChange = modifier => {
    this.props.onSkinTone(modifier);
  }

  render () {
    const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;

    if (loading) {
      return
;
    }

    const title = intl.formatMessage(messages.emoji);

    const { modifierOpen } = this.state;

    const categoriesSort = [
      'recent',
      'people',
      'nature',
      'foods',
      'activity',
      'places',
      'objects',
      'symbols',
      'flags',
    ];

    categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(custom_emojis)).sort());

    return (

        <EmojiPicker
          perLine={8}
          emojiSize={22}
          sheetSize={32}
          custom={buildCustomEmojis(custom_emojis)}
          color=''
          emoji=''
          set='twitter'
          title={title}
          i18n={this.getI18n()}
          onClick={this.handleClick}
          include={categoriesSort}
          recent={frequentlyUsedEmojis}
          skin={skinTone}
          showPreview={false}
          backgroundImageFn={backgroundImageFn}
          autoFocus
          emojiTooltip
          native={useSystemEmojiFont}
        />

        <ModifierPicker
          active={modifierOpen}
          modifier={skinTone}
          onOpen={this.handleModifierOpen}
          onClose={this.handleModifierClose}
          onChange={this.handleModifierChange}
        />

    );
  }

}

export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class EmojiPickerDropdown extends React.PureComponent {

  static propTypes = {
    custom_emojis: ImmutablePropTypes.list,
    frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
    intl: PropTypes.object.isRequired,
    onPickEmoji: PropTypes.func.isRequired,
    onSkinTone: PropTypes.func.isRequired,
    skinTone: PropTypes.number.isRequired,
    button: PropTypes.node,
  };

  state = {
    active: false,
    loading: false,
  };

  setRef = (c) => {
    this.dropdown = c;
  }

  onShowDropdown = ({ target }) => {
    this.setState({ active: true });

    if (!EmojiPicker) {
      this.setState({ loading: true });

      EmojiPickerAsync().then(EmojiMart => {
        EmojiPicker = EmojiMart.Picker;
        Emoji       = EmojiMart.Emoji;

        this.setState({ loading: false });
      }).catch(() => {
        this.setState({ loading: false });
      });
    }

    const { top } = target.getBoundingClientRect();
    this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
  }

  onHideDropdown = () => {
    this.setState({ active: false });
  }

  onToggle = (e) => {
    if (!this.state.loading && (!e.key || e.key === 'Enter')) {
      if (this.state.active) {
        this.onHideDropdown();
      } else {
        this.onShowDropdown(e);
      }
    }
  }

  handleKeyDown = e => {
    if (e.key === 'Escape') {
      this.onHideDropdown();
    }
  }

  setTargetRef = c => {
    this.target = c;
  }

  findTarget = () => {
    return this.target;
  }

  render () {
    const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
    const title = intl.formatMessage(messages.emoji);
    const { active, loading, placement } = this.state;

    return (

          {button || <img
            className={classNames('emojione', { 'pulse-loading': active && loading })}
            alt='🙂'
            src={`${assetHost}/emoji/1f602.svg`}
          />}

        <Overlay show={active} placement={placement} target={this.findTarget}>
          <EmojiPickerMenu
            custom_emojis={this.props.custom_emojis}
            loading={loading}
            onClose={this.onHideDropdown}
            onPick={onPickEmoji}
            onSkinTone={onSkinTone}
            skinTone={skinTone}
            frequentlyUsedEmojis={frequentlyUsedEmojis}
          />
        </Overlay>

    );
  }

}
<commit_msg>[Glitch] Fix modifier key to keep the EmojiPicker on macOS<commit_after>import { connect } from 'react-redux';
import { changeSetting } from 'flavours/glitch/actions/settings';
import { createSelector } from 'reselect';
import { Map as ImmutableMap } from 'immutable';
import { useEmoji } from 'flavours/glitch/actions/emojis';
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { EmojiPicker as EmojiPickerAsync } from 'flavours/glitch/util/async-components';
import Overlay from 'react-overlays/lib/Overlay';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import detectPassiveEvents from 'detect-passive-events';
import { buildCustomEmojis, categoriesFromEmojis } from 'flavours/glitch/util/emoji';
import { useSystemEmojiFont } from 'flavours/glitch/util/initial_state';

const messages = defineMessages({
  emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
  emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
  emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°）╯︵ ┻━┻' },
  custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' },
  recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' },
  search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' },
  people: { id: 'emoji_button.people', defaultMessage: 'People' },
  nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' },
  food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' },
  activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' },
  travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' },
  objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
  symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
  flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' },
});

const perLine = 8;
const lines   = 2;

const DEFAULTS = [
  '+1',
  'grinning',
  'kissing_heart',
  'heart_eyes',
  'laughing',
  'stuck_out_tongue_winking_eye',
  'sweat_smile',
  'joy',
  'yum',
  'disappointed',
  'thinking_face',
  'weary',
  'sob',
  'sunglasses',
  'heart',
  'ok_hand',
];

const getFrequentlyUsedEmojis = createSelector([
  state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()),
], emojiCounters => {
  let emojis = emojiCounters
    .keySeq()
    .sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b))
    .reverse()
    .slice(0, perLine * lines)
    .toArray();

  if (emojis.length < DEFAULTS.length) {
    emojis = emojis.concat(DEFAULTS.slice(0, DEFAULTS.length - emojis.length));
  }

  return emojis;
});

const getCustomEmojis = createSelector([
  state => state.get('custom_emojis'),
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
  const aShort = a.get('shortcode').toLowerCase();
  const bShort = b.get('shortcode').toLowerCase();

  if (aShort < bShort) {
    return -1;
  } else if (aShort > bShort ) {
    return 1;
  } else {
    return 0;
  }
}));

const mapStateToProps = state => ({
  custom_emojis: getCustomEmojis(state),
  skinTone: state.getIn(['settings', 'skinTone']),
  frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
});

const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
  onSkinTone: skinTone => {
    dispatch(changeSetting(['skinTone'], skinTone));
  },

  onPickEmoji: emoji => {
    dispatch(useEmoji(emoji));

    if (onPickEmoji) {
      onPickEmoji(emoji);
    }
  },
});

const assetHost = process.env.CDN_HOST || '';
let EmojiPicker, Emoji; // load asynchronously

const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`;
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;

class ModifierPickerMenu extends React.PureComponent {

  static propTypes = {
    active: PropTypes.bool,
    onSelect: PropTypes.func.isRequired,
    onClose: PropTypes.func.isRequired,
  };

  handleClick = e => {
    this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.active) {
      this.attachListeners();
    } else {
      this.removeListeners();
    }
  }

  componentWillUnmount () {
    this.removeListeners();
  }

  handleDocumentClick = e => {
    if (this.node && !this.node.contains(e.target)) {
      this.props.onClose();
    }
  }

  attachListeners () {
    document.addEventListener('click', this.handleDocumentClick, false);
    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
  }

  removeListeners () {
    document.removeEventListener('click', this.handleDocumentClick, false);
    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
  }

  setRef = c => {
    this.node = c;
  }

  render () {
    const { active } = this.props;

    return (

        <button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>
        <button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button>

    );
  }

}

class ModifierPicker extends React.PureComponent {

  static propTypes = {
    active: PropTypes.bool,
    modifier: PropTypes.number,
    onChange: PropTypes.func,
    onClose: PropTypes.func,
    onOpen: PropTypes.func,
  };

  handleClick = () => {
    if (this.props.active) {
      this.props.onClose();
    } else {
      this.props.onOpen();
    }
  }

  handleSelect = modifier => {
    this.props.onChange(modifier);
    this.props.onClose();
  }

  render () {
    const { active, modifier } = this.props;

    return (

        <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} />
        <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />

    );
  }

}

@injectIntl
class EmojiPickerMenu extends React.PureComponent {

  static propTypes = {
    custom_emojis: ImmutablePropTypes.list,
    frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
    loading: PropTypes.bool,
    onClose: PropTypes.func.isRequired,
    onPick: PropTypes.func.isRequired,
    style: PropTypes.object,
    placement: PropTypes.string,
    arrowOffsetLeft: PropTypes.string,
    arrowOffsetTop: PropTypes.string,
    intl: PropTypes.object.isRequired,
    skinTone: PropTypes.number.isRequired,
    onSkinTone: PropTypes.func.isRequired,
  };

  static defaultProps = {
    style: {},
    loading: true,
    frequentlyUsedEmojis: [],
  };

  state = {
    modifierOpen: false,
    placement: null,
  };

  handleDocumentClick = e => {
    if (this.node && !this.node.contains(e.target)) {
      this.props.onClose();
    }
  }

  componentDidMount () {
    document.addEventListener('click', this.handleDocumentClick, false);
    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
  }

  componentWillUnmount () {
    document.removeEventListener('click', this.handleDocumentClick, false);
    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
  }

  setRef = c => {
    this.node = c;
  }

  getI18n = () => {
    const { intl } = this.props;

    return {
      search: intl.formatMessage(messages.emoji_search),
      notfound: intl.formatMessage(messages.emoji_not_found),
      categories: {
        search: intl.formatMessage(messages.search_results),
        recent: intl.formatMessage(messages.recent),
        people: intl.formatMessage(messages.people),
        nature: intl.formatMessage(messages.nature),
        foods: intl.formatMessage(messages.food),
        activity: intl.formatMessage(messages.activity),
        places: intl.formatMessage(messages.travel),
        objects: intl.formatMessage(messages.objects),
        symbols: intl.formatMessage(messages.symbols),
        flags: intl.formatMessage(messages.flags),
        custom: intl.formatMessage(messages.custom),
      },
    };
  }

  handleClick = (emoji, event) => {
    if (!emoji.native) {
      emoji.native = emoji.colons;
    }
    if (!(event.ctrlKey || event.metaKey)) {
      this.props.onClose();
    }
    this.props.onPick(emoji);
  }

  handleModifierOpen = () => {
    this.setState({ modifierOpen: true });
  }

  handleModifierClose = () => {
    this.setState({ modifierOpen: false });
  }

  handleModifierChange = modifier => {
    this.props.onSkinTone(modifier);
  }

  render () {
    const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;

    if (loading) {
      return
;
    }

    const title = intl.formatMessage(messages.emoji);

    const { modifierOpen } = this.state;

    const categoriesSort = [
      'recent',
      'people',
      'nature',
      'foods',
      'activity',
      'places',
      'objects',
      'symbols',
      'flags',
    ];

    categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(custom_emojis)).sort());

    return (

        <EmojiPicker
          perLine={8}
          emojiSize={22}
          sheetSize={32}
          custom={buildCustomEmojis(custom_emojis)}
          color=''
          emoji=''
          set='twitter'
          title={title}
          i18n={this.getI18n()}
          onClick={this.handleClick}
          include={categoriesSort}
          recent={frequentlyUsedEmojis}
          skin={skinTone}
          showPreview={false}
          backgroundImageFn={backgroundImageFn}
          autoFocus
          emojiTooltip
          native={useSystemEmojiFont}
        />

        <ModifierPicker
          active={modifierOpen}
          modifier={skinTone}
          onOpen={this.handleModifierOpen}
          onClose={this.handleModifierClose}
          onChange={this.handleModifierChange}
        />

    );
  }

}

export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class EmojiPickerDropdown extends React.PureComponent {

  static propTypes = {
    custom_emojis: ImmutablePropTypes.list,
    frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
    intl: PropTypes.object.isRequired,
    onPickEmoji: PropTypes.func.isRequired,
    onSkinTone: PropTypes.func.isRequired,
    skinTone: PropTypes.number.isRequired,
    button: PropTypes.node,
  };

  state = {
    active: false,
    loading: false,
  };

  setRef = (c) => {
    this.dropdown = c;
  }

  onShowDropdown = ({ target }) => {
    this.setState({ active: true });

    if (!EmojiPicker) {
      this.setState({ loading: true });

      EmojiPickerAsync().then(EmojiMart => {
        EmojiPicker = EmojiMart.Picker;
        Emoji       = EmojiMart.Emoji;

        this.setState({ loading: false });
      }).catch(() => {
        this.setState({ loading: false });
      });
    }

    const { top } = target.getBoundingClientRect();
    this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
  }

  onHideDropdown = () => {
    this.setState({ active: false });
  }

  onToggle = (e) => {
    if (!this.state.loading && (!e.key || e.key === 'Enter')) {
      if (this.state.active) {
        this.onHideDropdown();
      } else {
        this.onShowDropdown(e);
      }
    }
  }

  handleKeyDown = e => {
    if (e.key === 'Escape') {
      this.onHideDropdown();
    }
  }

  setTargetRef = c => {
    this.target = c;
  }

  findTarget = () => {
    return this.target;
  }

  render () {
    const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
    const title = intl.formatMessage(messages.emoji);
    const { active, loading, placement } = this.state;

    return (

          {button || <img
            className={classNames('emojione', { 'pulse-loading': active && loading })}
            alt='🙂'
            src={`${assetHost}/emoji/1f602.svg`}
          />}

        <Overlay show={active} placement={placement} target={this.findTarget}>
          <EmojiPickerMenu
            custom_emojis={this.props.custom_emojis}
            loading={loading}
            onClose={this.onHideDropdown}
            onPick={onPickEmoji}
            onSkinTone={onSkinTone}
            skinTone={skinTone}
            frequentlyUsedEmojis={frequentlyUsedEmojis}
          />
        </Overlay>

    );
  }

}
<|endoftext|>


<commit_before>var bindings = require('./time');

// A "hack" of sorts to Force getting our own Date instance.
// Otherwise, in some cases, the global Natives are shared between
// contexts (not what we want)...
var _Date = process.env.NODE_MODULE_CONTEXTS ? Date : require('vm').runInNewContext("Date");

// During startup, we synchronously attempt to determine the location of the
// timezone dir, or TZDIR on some systems. This isn't necessary for the
// C bindings, however it's needed for the `listTimezones()` function and for
// resolving the 'initial' timezone to use.
var possibleTzdirs = [
    '/usr/share/zoneinfo'
  , '/usr/lib/zoneinfo'
];
var TZDIR = process.env.TZDIR;
while (!TZDIR && possibleTzdirs.length > 0) {
  try {
    var d = possibleTzdirs.shift();
    if (require('fs').statSync(d).isDirectory())
      TZDIR = d;
  } catch(e) {}
}
possibleTzdirs = null;
if (!TZDIR) throw new Error("FATAL: Couldn't determine the location of your timezone directory!");

// Older versions of node-time would require the user to have the TZ
// environment variable set, otherwise undesirable results would happen. Now
// node-time tries to automatically determine the current timezone for you.
if (!process.env.TZ) {
  try {
    var currentTimezonePath = require('fs').readlinkSync('/etc/localtime');
    if (currentTimezonePath.substring(0, TZDIR.length) === TZDIR)
      // Got It!
      process.env.TZ = currentTimezonePath.substring(TZDIR.length + 1);
  } catch(e) {}
}

var MILLIS_PER_SECOND = 1000;
var DAYS_OF_WEEK = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];

exports.time = bindings.time;
exports.localtime = bindings.localtime;
exports.mktime = bindings.mktime;

// The user-facing 'tzset' function accepts a timezone String
// to set to, and returns an object with the zoneinfo for the
// timezone.
function tzset(tz) {
  if (tz) {
    process.env.TZ = tz;
  }
  var usedTz = process.env.TZ;
  var rtn = bindings.tzset();
  if (!rtn.tzname[1] && rtn.timezone === 0) {
    var err = new Error("Unknown Timezone: '" + usedTz + "'");
    for (var i in rtn) {
      err[i] = rtn[i];
    }
    throw err;
  }
  exports.currentTimezone = usedTz;
  return rtn;
}
exports.tzset = tzset;
exports.currentTimezone = process.env.TZ;

// The "setTimezone" function is the "entry point" for a Date instance.
// It must be called after an instance has been created. After, the 'getSeconds()',
// 'getHours()', 'getDays()', etc. functions will return values relative
// to the time zone specified.
function setTimezone(timezone) {
  var oldTz = exports.currentTimezone;
  var tz = exports.tzset(timezone);
  var zoneInfo = exports.localtime(this / 1000);
  if (oldTz != timezone) {
    exports.tzset(oldTz);
    oldTz = null;
  }

  // If we got to here without throwing an Error, then
  // a valid timezone was requested, and we should have
  // a valid zoneInfo Object.
  this.getTimezone = function getTimezone() {
    return timezone;
  }

  // Returns the day of the month (1-31) for the specified date according to local time.
  this.getDate = function getDate() {
    return zoneInfo.dayOfMonth;
  }
  // Returns the day of the week (0-6) for the specified date according to local time.
  this.getDay = function getDay() {
    return zoneInfo.dayOfWeek;
  }
  // Deprecated. Returns the year (usually 2-3 digits) in the specified date according
  // to local time. Use `getFullYear()` instead.
  this.getYear = function getYear() {
    return zoneInfo.year;
  }
  // Returns the year (4 digits for 4-digit years) of the specified date according to local time.
  this.getFullYear = function getFullYear() {
    return zoneInfo.year + 1900;
  }
  // Returns the hour (0-23) in the specified date according to local time.
  this.getHours = function getHours() {
    return zoneInfo.hours;
  }
  // Returns the minutes (0-59) in the specified date according to local time.
  this.getMinutes = function getMinutes() {
    return zoneInfo.minutes;
  }
  // Returns the month (0-11) in the specified date according to local time.
  this.getMonth = function getMonth() {
    return zoneInfo.month;
  }
  // Returns the seconds (0-59) in the specified date according to local time.
  this.getSeconds = function getSeconds() {
    return zoneInfo.seconds;
  }
  // Returns the timezone offset from GMT the Date instance currently is in,
  // in minutes. Also, left of GMT is positive, right of GMT is negative.
  this.getTimezoneOffset = function getTimezoneOffset() {
    return -zoneInfo.gmtOffset / 60;
  }
  // NON-STANDARD: Returns the abbreviation (e.g. EST, EDT) for the specified time zone.
  this.getTimezoneAbbr = function getTimezoneAbbr() {
    return tz.tzname[zoneInfo.isDaylightSavings ? 1 : 0];
  }
  // Sets the day of the month (from 1-31) in the current timezone
  this.setDate = function setDate(v) {
    zoneInfo.dayOfMonth = v;
    return mktime.call(this);
  }
  // Sets the year (four digits) in the current timezone
  this.setFullYear = function setFullYear(v) {
    zoneInfo.year = v - 1900;
    return mktime.call(this);
  }
  // Sets the hour (from 0-23) in the current timezone
  this.setHours = function setHours(v) {
    zoneInfo.hours = v;
    return mktime.call(this);
  }
  // Sets the milliseconds (from 0-999) in the current timezone
  this.setMilliseconds = function setMilliseconds(v) {
    var diff = v - this.getMilliseconds();
    return this.setTime(+this + diff);
  }
  // Set the minutes (from 0-59) in the current timezone
  this.setMinutes = function setMinutes(v) {
    zoneInfo.minutes = v;
    return mktime.call(this);
  }
  // Sets the month (from 0-11) in the current timezone
  this.setMonth = function setMonth(v) {
    zoneInfo.month = v;
    return mktime.call(this);
  }
  // Sets the seconds (from 0-59) in the current timezone
  this.setSeconds = function setSeconds(v) {
    zoneInfo.seconds = v;
    return mktime.call(this);
  }
  // Sets a date and time by adding or subtracting a specified number of
  // milliseconds to/from midnight January 1, 1970.
  this.setTime = function setTime(v) {
    var rtn = _Date.prototype.setTime.call(this, v);
    // Since this function changes the internal UTC epoch date value, we need to
    // re-setup these timezone translation functions to reflect the new value
    reset.call(this);
    return rtn;
  }
  // Sets the day of the month, according to universal time (from 1-31)
  this.setUTCDate = function setUTCDate(v) {
    var rtn = _Date.prototype.setUTCDate.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Sets the year, according to universal time (four digits)
  this.setUTCFullYear = function setUTCFullYear(v) {
    var rtn = _Date.prototype.setUTCFullYear.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Sets the hour, according to universal time (from 0-23)
  this.setUTCHours = function setUTCHours(v) {
    var rtn = _Date.prototype.setUTCHours.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Sets the milliseconds, according to universal time (from 0-999)
  this.setUTCMilliseconds = function setUTCMillseconds(v) {
    var rtn = _Date.prototype.setUTCMilliseconds.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Set the minutes, according to universal time (from 0-59)
  this.setUTCMinutes = function setUTCMinutes(v) {
    var rtn = _Date.prototype.setUTCMinutes.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Sets the month, according to universal time (from 0-11)
  this.setUTCMonth = function setUTCMonth(v) {
    var rtn = _Date.prototype.setUTCMonth.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Set the seconds, according to universal time (from 0-59)
  this.setUTCSeconds = function setUTCSeconds(v) {
    var rtn = _Date.prototype.setUTCSeconds.call(this, v);
    reset.call(this);
    return rtn;
  }

  this.toDateString = function toDateString() {
    return DAYS_OF_WEEK[this.getDay()].substring(0, 3) + ' ' + MONTHS[this.getMonth()].substring(0, 3) + ' ' + pad(this.getDate(), 2) + ' ' + this.getFullYear();
  }

  this.toTimeString = function toTimeString() {
    var offset = zoneInfo.gmtOffset / 60 / 60;
    return this.toLocaleTimeString() + ' GMT' + (offset >= 0 ? '+' : '-') + pad(Math.abs(offset * 100), 4)
      + ' (' + tz.tzname[zoneInfo.isDaylightSavings ? 1 : 0] + ')';
  }

  this.toString = function toString() {
    return this.toDateString() + ' ' + this.toTimeString();
  }

  this.toLocaleDateString = function toLocaleDateString() {
    return DAYS_OF_WEEK[this.getDay()] + ', ' + MONTHS[this.getMonth()] + ' ' + pad(this.getDate(), 2) + ', ' + this.getFullYear();
  }

  this.toLocaleTimeString = function toLocaleTimeString() {
    return pad(this.getHours(), 2) + ':' + pad(this.getMinutes(), 2) + ':' + pad(this.getSeconds(), 2);
  }

  this.toLocaleString = this.toString;

  // Used internally by the 'set*' functions above...
  function reset() {
    this.setTimezone(this.getTimezone());
  }
  // 'mktime' calls 'reset' implicitly through 'setTime()'
  function mktime() {
    var oldTz = process.env.TZ;
    exports.tzset(this.getTimezone());
    zoneInfo.isDaylightSavings = -1; // Auto-detect the timezone
    var t = exports.mktime(zoneInfo);
    if (oldTz) {
      exports.tzset(oldTz);
      oldTz = null;
    }
    return this.setTime( (t * MILLIS_PER_SECOND) + this.getMilliseconds() );
  }

}
_Date.prototype.setTimezone = _Date.prototype.setTimeZone = setTimezone;

// Returns a "String" of the last value set in "setTimezone".
// TODO: Return something when 'setTimezone' hasn't been called yet.
function getTimezone() {
  throw new Error('You must call "setTimezone(tz)" before "getTimezone()" may be called');
}
_Date.prototype.getTimezone = _Date.prototype.getTimeZone = getTimezone;

// NON-STANDARD: Returns the abbreviated timezone name, also taking daylight
// savings into consideration. Useful for the presentation layer of a Date
// instance.
function getTimezoneAbbr() {
  var str = this.toString().match(/\([A-Z]+\)/)[0];
  return str.substring(1, str.length-1);
}
_Date.prototype.getTimezoneAbbr = getTimezoneAbbr;

// Export the modified 'Date' instance. Users should either use this with the
// 'new' operator, or extend an already existing Date instance with 'extend()'
exports.Date = _Date;

// Turns a "regular" Date instance into one of our "extended" Date instances.
// The return value is negligible, as the original Date instance is modified.
exports.extend = function extend(date) {
  date.getTimezone = getTimezone;
  date.setTimezone = setTimezone;
  date.getTimeZone = getTimezone; // Remove...
  date.setTimeZone = setTimezone; // Remove...
  date.getTimezoneAbbr = getTimezoneAbbr;
  return date;
}

// Pads a number with 0s if required
function pad(num, padLen) {
  var padding = '0000';
  num = String(num);
  return padding.substring(0, padLen - num.length) + num;
}
<commit_msg>Implement the Date constructor! An optional 'timezone' argument may be passed as the final argument.<commit_after>var bindings = require('./time');

// A "hack" of sorts to Force getting our own Date instance.
// Otherwise, in some cases, the global Natives are shared between
// contexts (not what we want)...
var _Date = process.env.NODE_MODULE_CONTEXTS ? Date : require('vm').runInNewContext("Date");

// During startup, we synchronously attempt to determine the location of the
// timezone dir, or TZDIR on some systems. This isn't necessary for the
// C bindings, however it's needed for the `listTimezones()` function and for
// resolving the 'initial' timezone to use.
var possibleTzdirs = [
    '/usr/share/zoneinfo'
  , '/usr/lib/zoneinfo'
];
var TZDIR = process.env.TZDIR;
while (!TZDIR && possibleTzdirs.length > 0) {
  try {
    var d = possibleTzdirs.shift();
    if (require('fs').statSync(d).isDirectory())
      TZDIR = d;
  } catch(e) {}
}
possibleTzdirs = null;
if (!TZDIR) throw new Error("FATAL: Couldn't determine the location of your timezone directory!");

// Older versions of node-time would require the user to have the TZ
// environment variable set, otherwise undesirable results would happen. Now
// node-time tries to automatically determine the current timezone for you.
if (!process.env.TZ) {
  try {
    var currentTimezonePath = require('fs').readlinkSync('/etc/localtime');
    if (currentTimezonePath.substring(0, TZDIR.length) === TZDIR)
      // Got It!
      process.env.TZ = currentTimezonePath.substring(TZDIR.length + 1);
  } catch(e) {}
}

var MILLIS_PER_SECOND = 1000;
var DAYS_OF_WEEK = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];

exports.time = bindings.time;
exports.localtime = bindings.localtime;
exports.mktime = bindings.mktime;

// The user-facing 'tzset' function accepts a timezone String
// to set to, and returns an object with the zoneinfo for the
// timezone.
function tzset(tz) {
  if (tz) {
    process.env.TZ = tz;
  }
  var usedTz = process.env.TZ;
  var rtn = bindings.tzset();
  if (!rtn.tzname[1] && rtn.timezone === 0) {
    var err = new Error("Unknown Timezone: '" + usedTz + "'");
    for (var i in rtn) {
      err[i] = rtn[i];
    }
    throw err;
  }
  exports.currentTimezone = usedTz;
  return rtn;
}
exports.tzset = tzset;
exports.currentTimezone = process.env.TZ;

// The "setTimezone" function is the "entry point" for a Date instance.
// It must be called after an instance has been created. After, the 'getSeconds()',
// 'getHours()', 'getDays()', etc. functions will return values relative
// to the time zone specified.
function setTimezone(timezone) {
  var oldTz = exports.currentTimezone;
  var tz = exports.tzset(timezone);
  var zoneInfo = exports.localtime(this / 1000);
  if (oldTz != timezone) {
    exports.tzset(oldTz);
    oldTz = null;
  }

  // If we got to here without throwing an Error, then
  // a valid timezone was requested, and we should have
  // a valid zoneInfo Object.
  this.getTimezone = function getTimezone() {
    return timezone;
  }

  // Returns the day of the month (1-31) for the specified date according to local time.
  this.getDate = function getDate() {
    return zoneInfo.dayOfMonth;
  }
  // Returns the day of the week (0-6) for the specified date according to local time.
  this.getDay = function getDay() {
    return zoneInfo.dayOfWeek;
  }
  // Deprecated. Returns the year (usually 2-3 digits) in the specified date according
  // to local time. Use `getFullYear()` instead.
  this.getYear = function getYear() {
    return zoneInfo.year;
  }
  // Returns the year (4 digits for 4-digit years) of the specified date according to local time.
  this.getFullYear = function getFullYear() {
    return zoneInfo.year + 1900;
  }
  // Returns the hour (0-23) in the specified date according to local time.
  this.getHours = function getHours() {
    return zoneInfo.hours;
  }
  // Returns the minutes (0-59) in the specified date according to local time.
  this.getMinutes = function getMinutes() {
    return zoneInfo.minutes;
  }
  // Returns the month (0-11) in the specified date according to local time.
  this.getMonth = function getMonth() {
    return zoneInfo.month;
  }
  // Returns the seconds (0-59) in the specified date according to local time.
  this.getSeconds = function getSeconds() {
    return zoneInfo.seconds;
  }
  // Returns the timezone offset from GMT the Date instance currently is in,
  // in minutes. Also, left of GMT is positive, right of GMT is negative.
  this.getTimezoneOffset = function getTimezoneOffset() {
    return -zoneInfo.gmtOffset / 60;
  }
  // NON-STANDARD: Returns the abbreviation (e.g. EST, EDT) for the specified time zone.
  this.getTimezoneAbbr = function getTimezoneAbbr() {
    return tz.tzname[zoneInfo.isDaylightSavings ? 1 : 0];
  }
  // Sets the day of the month (from 1-31) in the current timezone
  this.setDate = function setDate(v) {
    zoneInfo.dayOfMonth = v;
    return mktime.call(this);
  }
  // Sets the year (four digits) in the current timezone
  this.setFullYear = function setFullYear(v) {
    zoneInfo.year = v - 1900;
    return mktime.call(this);
  }
  // Sets the hour (from 0-23) in the current timezone
  this.setHours = function setHours(v) {
    zoneInfo.hours = v;
    return mktime.call(this);
  }
  // Sets the milliseconds (from 0-999) in the current timezone
  this.setMilliseconds = function setMilliseconds(v) {
    var diff = v - this.getMilliseconds();
    return this.setTime(+this + diff);
  }
  // Set the minutes (from 0-59) in the current timezone
  this.setMinutes = function setMinutes(v) {
    zoneInfo.minutes = v;
    return mktime.call(this);
  }
  // Sets the month (from 0-11) in the current timezone
  this.setMonth = function setMonth(v) {
    zoneInfo.month = v;
    return mktime.call(this);
  }
  // Sets the seconds (from 0-59) in the current timezone
  this.setSeconds = function setSeconds(v) {
    zoneInfo.seconds = v;
    return mktime.call(this);
  }
  // Sets a date and time by adding or subtracting a specified number of
  // milliseconds to/from midnight January 1, 1970.
  this.setTime = function setTime(v) {
    var rtn = _Date.prototype.setTime.call(this, v);
    // Since this function changes the internal UTC epoch date value, we need to
    // re-setup these timezone translation functions to reflect the new value
    reset.call(this);
    return rtn;
  }
  // Sets the day of the month, according to universal time (from 1-31)
  this.setUTCDate = function setUTCDate(v) {
    var rtn = _Date.prototype.setUTCDate.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Sets the year, according to universal time (four digits)
  this.setUTCFullYear = function setUTCFullYear(v) {
    var rtn = _Date.prototype.setUTCFullYear.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Sets the hour, according to universal time (from 0-23)
  this.setUTCHours = function setUTCHours(v) {
    var rtn = _Date.prototype.setUTCHours.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Sets the milliseconds, according to universal time (from 0-999)
  this.setUTCMilliseconds = function setUTCMillseconds(v) {
    var rtn = _Date.prototype.setUTCMilliseconds.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Set the minutes, according to universal time (from 0-59)
  this.setUTCMinutes = function setUTCMinutes(v) {
    var rtn = _Date.prototype.setUTCMinutes.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Sets the month, according to universal time (from 0-11)
  this.setUTCMonth = function setUTCMonth(v) {
    var rtn = _Date.prototype.setUTCMonth.call(this, v);
    reset.call(this);
    return rtn;
  }
  // Set the seconds, according to universal time (from 0-59)
  this.setUTCSeconds = function setUTCSeconds(v) {
    var rtn = _Date.prototype.setUTCSeconds.call(this, v);
    reset.call(this);
    return rtn;
  }

  this.toDateString = function toDateString() {
    return DAYS_OF_WEEK[this.getDay()].substring(0, 3) + ' ' + MONTHS[this.getMonth()].substring(0, 3) + ' ' + pad(this.getDate(), 2) + ' ' + this.getFullYear();
  }

  this.toTimeString = function toTimeString() {
    var offset = zoneInfo.gmtOffset / 60 / 60;
    return this.toLocaleTimeString() + ' GMT' + (offset >= 0 ? '+' : '-') + pad(Math.abs(offset * 100), 4)
      + ' (' + tz.tzname[zoneInfo.isDaylightSavings ? 1 : 0] + ')';
  }

  this.toString = function toString() {
    return this.toDateString() + ' ' + this.toTimeString();
  }

  this.toLocaleDateString = function toLocaleDateString() {
    return DAYS_OF_WEEK[this.getDay()] + ', ' + MONTHS[this.getMonth()] + ' ' + pad(this.getDate(), 2) + ', ' + this.getFullYear();
  }

  this.toLocaleTimeString = function toLocaleTimeString() {
    return pad(this.getHours(), 2) + ':' + pad(this.getMinutes(), 2) + ':' + pad(this.getSeconds(), 2);
  }

  this.toLocaleString = this.toString;

  // Used internally by the 'set*' functions above...
  function reset() {
    this.setTimezone(this.getTimezone());
  }
  // 'mktime' calls 'reset' implicitly through 'setTime()'
  function mktime() {
    var oldTz = process.env.TZ;
    exports.tzset(this.getTimezone());
    zoneInfo.isDaylightSavings = -1; // Auto-detect the timezone
    var t = exports.mktime(zoneInfo);
    if (oldTz) {
      exports.tzset(oldTz);
      oldTz = null;
    }
    return this.setTime( (t * MILLIS_PER_SECOND) + this.getMilliseconds() );
  }

}
_Date.prototype.setTimezone = _Date.prototype.setTimeZone = setTimezone;

// Returns a "String" of the last value set in "setTimezone".
// TODO: Return something when 'setTimezone' hasn't been called yet.
function getTimezone() {
  throw new Error('You must call "setTimezone(tz)" before "getTimezone()" may be called');
}
_Date.prototype.getTimezone = _Date.prototype.getTimeZone = getTimezone;

// NON-STANDARD: Returns the abbreviated timezone name, also taking daylight
// savings into consideration. Useful for the presentation layer of a Date
// instance.
function getTimezoneAbbr() {
  var str = this.toString().match(/\([A-Z]+\)/)[0];
  return str.substring(1, str.length-1);
}
_Date.prototype.getTimezoneAbbr = getTimezoneAbbr;

// Export the modified 'Date' instance. Users should either use this with the
// 'new' operator, or extend an already existing Date instance with 'extend()'.
// An optional, NON-STANDARD, "timezone" argument may be appended as the final
// argument, in order to specify the initial timezone the Date instance should
// be created with.
function Date (year, month, day, hour, minute, second, millisecond, timezone) {
  if (!(this instanceof Date)) {
    return new Date(year, month, day, hour, minute, second, millisecond, timezone).toString();
  }
  var argc = arguments.length
    , d;
  // So that we don't have to do the switch block below twice!
  while (typeof arguments[argc-1] === 'undefined') {
    argc--;
  }
  // An optional 'timezone' argument may be passed as the final argument
  if (argc >= 2 && typeof arguments[argc - 1] === 'string') {
    timezone = arguments[argc - 1];
    argc--;
  }
  // Ugly, but the native Date constructor depends on arguments.length in order
  // to create a Date instance in the intended fashion.
  switch (argc) {
    case 0:
      d = new _Date(); break;
    case 1:
      d = new _Date(year); break;
    case 2:
      d = new _Date(year, month); break;
    case 3:
      d = new _Date(year, month, day); break;
    case 4:
      d = new _Date(year, month, day, hour); break;
    case 5:
      d = new _Date(year, month, day, hour, minute); break;
    case 6:
      d = new _Date(year, month, day, hour, minute, second); break;
    case 7:
      d = new _Date(year, month, day, hour, minute, second, millisecond); break;
  }
  if (timezone) {
    // If a 'timezone' was supplied, then the "trick" is to create another Date
    // instance with the 'setTimezone()' function called on it. After that, we
    // can proxy all the 'set*()' calls to the new Date instance. This is
    // pretty much a "hack", but it allows us to re-use the native Date
    // constructor logic above...
    var realD = new _Date(0);
    realD.setTimezone(timezone);
    realD.setMilliseconds(d.getMilliseconds());
    realD.setSeconds(d.getSeconds());
    realD.setMinutes(d.getMinutes());
    realD.setHours(d.getHours());
    realD.setDate(d.getDate());
    realD.setMonth(d.getMonth());
    realD.setFullYear(d.getFullYear());
    d = realD;
  }
  return d;
}
Date.prototype = _Date.prototype;
exports.Date = Date;

// Turns a "regular" Date instance into one of our "extended" Date instances.
// The return value is negligible, as the original Date instance is modified.
exports.extend = function extend(date) {
  date.getTimezone = getTimezone;
  date.setTimezone = setTimezone;
  date.getTimeZone = getTimezone; // Remove...
  date.setTimeZone = setTimezone; // Remove...
  date.getTimezoneAbbr = getTimezoneAbbr;
  return date;
}

// Pads a number with 0s if required
function pad(num, padLen) {
  var padding = '0000';
  num = String(num);
  return padding.substring(0, padLen - num.length) + num;
}
<|endoftext|>