# Contributing guide

Want to contribute to Clipboard.js? Awesome!
There are many ways you can contribute, see below.

## Opening issues

Open an issue to report bugs or to propose new features.

- Reporting bugs: describe the bug as clearly as you can, including steps to reproduce, what happened and what you were expecting to happen. Also include browser version, OS and other related software's (npm, Node.js, etc) versions when applicable.

- Proposing features: explain the proposed feature, what it should do, why it is useful, how users should use it. Give us as much info as possible so it will be easier to discuss, access and implement the proposed feature. When you're unsure about a certain aspect of the feature, feel free to leave it open for others to discuss and find an appropriate solution.

## Proposing pull requests

Pull requests are very welcome. Note that if you are going to propose drastic changes, be sure to open an issue for discussion first, to make sure that your PR will be accepted before you spend effort coding it.

Fork the Clipboard.js repository, clone it locally and create a branch for your proposed bug fix or new feature. Avoid working directly on the master branch.

Implement your bug fix or feature, write tests to cover it and make sure all tests are passing (run a final `npm test` to make sure everything is correct). Then commit your changes, push your bug fix/feature branch to the origin (your forked repo) and open a pull request to the upstream (the repository you originally forked)'s master branch.

## Documentation

Documentation is extremely important and takes a fair deal of time and effort to write and keep updated. Please submit any and all improvements you can make to the repository's docs.

## Known issues
If you're using npm@3 you'll probably face some issues related to peerDependencies.
https://github.com/npm/npm/issues/9204


keep keep keep keep keep replace replace replace keep keep keep keep keep

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>function-text</title>
</head>
<body>
    <!-- 1. Define some markup -->
    <button class="btn">Copy</button>

    <!-- 2. Include library -->
    <script src="../dist/clipboard.min.js"></script>

    <!-- 3. Instantiate clipboard -->
    <script>
    var clipboard = new Clipboard('.btn', {
        text: function() {
            return 'to be or not to be';
        }
    });

    clipboard.on('success', function(e) {
        console.log(e);
    });

    clipboard.on('error', function(e) {
        console.log(e);
    });
    </script>
</body>
</html>


keep keep keep keep keep replace replace replace replace keep

clipboard

Modern copy to clipboard. No Flash. Just 2kb

npm run build-debug && npm run build-min

browserify src/clipboard.js -s Clipboard -t [babelify --presets es2015] -p [bannerify --file .banner ] -o dist/clipboard.js

uglifyjs dist/clipboard.js --comments '/!/' -m screw_ie8=true -c screw_ie8=true,unused=false -o dist/clipboard.min.js

watchify src/clipboard.js -s Clipboard -t [babelify --presets es2015] -o dist/clipboard.js -v

babel src --out-dir lib --presets es2015


/*!
 * clipboard.js v1.5.6
 * https://zenorocha.github.io/clipboard.js
 *
 * Licensed MIT © Zeno Rocha
 */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var matches = require('matches-selector')

module.exports = function (element, selector, checkYoSelf) {
  var parent = checkYoSelf ? element : element.parentNode

  while (parent && parent !== document) {
    if (matches(parent, selector)) return parent;
    parent = parent.parentNode
  }
}

},{"matches-selector":5}],2:[function(require,module,exports){
var closest = require('closest');

/**
 * Delegates event to a selector.
 *
 * @param {Element} element
 * @param {String} selector
 * @param {String} type
 * @param {Function} callback
 * @param {Boolean} useCapture
 * @return {Object}
 */
function delegate(element, selector, type, callback, useCapture) {
    var listenerFn = listener.apply(this, arguments);

    element.addEventListener(type, listenerFn, useCapture);

    return {
        destroy: function() {
            element.removeEventListener(type, listenerFn, useCapture);
        }
    }
}

/**
 * Finds closest match and invokes callback.
 *
 * @param {Element} element
 * @param {String} selector
 * @param {String} type
 * @param {Function} callback
 * @return {Function}
 */
function listener(element, selector, type, callback) {
    return function(e) {
        e.delegateTarget = closest(e.target, selector, true);

        if (e.delegateTarget) {
            callback.call(element, e);
        }
    }
}

module.exports = delegate;

},{"closest":1}],3:[function(require,module,exports){
/**
 * Check if argument is a HTML element.
 *
 * @param {Object} value
 * @return {Boolean}
 */
exports.node = function(value) {
    return value !== undefined
        && value instanceof HTMLElement
        && value.nodeType === 1;
};

/**
 * Check if argument is a list of HTML elements.
 *
 * @param {Object} value
 * @return {Boolean}
 */
exports.nodeList = function(value) {
    var type = Object.prototype.toString.call(value);

    return value !== undefined
        && (type === '[object NodeList]' || type === '[object HTMLCollection]')
        && ('length' in value)
        && (value.length === 0 || exports.node(value[0]));
};

/**
 * Check if argument is a string.
 *
 * @param {Object} value
 * @return {Boolean}
 */
exports.string = function(value) {
    return typeof value === 'string'
        || value instanceof String;
};

/**
 * Check if argument is a function.
 *
 * @param {Object} value
 * @return {Boolean}
 */
exports.fn = function(value) {
    var type = Object.prototype.toString.call(value);

    return type === '[object Function]';
};

},{}],4:[function(require,module,exports){
var is = require('./is');
var delegate = require('delegate');

/**
 * Validates all params and calls the right
 * listener function based on its target type.
 *
 * @param {String|HTMLElement|HTMLCollection|NodeList} target
 * @param {String} type
 * @param {Function} callback
 * @return {Object}
 */
function listen(target, type, callback) {
    if (!target && !type && !callback) {
        throw new Error('Missing required arguments');
    }

    if (!is.string(type)) {
        throw new TypeError('Second argument must be a String');
    }

    if (!is.fn(callback)) {
        throw new TypeError('Third argument must be a Function');
    }

    if (is.node(target)) {
        return listenNode(target, type, callback);
    }
    else if (is.nodeList(target)) {
        return listenNodeList(target, type, callback);
    }
    else if (is.string(target)) {
        return listenSelector(target, type, callback);
    }
    else {
        throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');
    }
}

/**
 * Adds an event listener to a HTML element
 * and returns a remove listener function.
 *
 * @param {HTMLElement} node
 * @param {String} type
 * @param {Function} callback
 * @return {Object}
 */
function listenNode(node, type, callback) {
    node.addEventListener(type, callback);

    return {
        destroy: function() {
            node.removeEventListener(type, callback);
        }
    }
}

/**
 * Add an event listener to a list of HTML elements
 * and returns a remove listener function.
 *
 * @param {NodeList|HTMLCollection} nodeList
 * @param {String} type
 * @param {Function} callback
 * @return {Object}
 */
function listenNodeList(nodeList, type, callback) {
    Array.prototype.forEach.call(nodeList, function(node) {
        node.addEventListener(type, callback);
    });

    return {
        destroy: function() {
            Array.prototype.forEach.call(nodeList, function(node) {
                node.removeEventListener(type, callback);
            });
        }
    }
}

/**
 * Add an event listener to a selector
 * and returns a remove listener function.
 *
 * @param {String} selector
 * @param {String} type
 * @param {Function} callback
 * @return {Object}
 */
function listenSelector(selector, type, callback) {
    return delegate(document.body, selector, type, callback);
}

module.exports = listen;

},{"./is":3,"delegate":2}],5:[function(require,module,exports){

/**
 * Element prototype.
 */

var proto = Element.prototype;

/**
 * Vendor function.
 */

var vendor = proto.matchesSelector
  || proto.webkitMatchesSelector
  || proto.mozMatchesSelector
  || proto.msMatchesSelector
  || proto.oMatchesSelector;

/**
 * Expose `match()`.
 */

module.exports = match;

/**
 * Match `el` to `selector`.
 *
 * @param {Element} el
 * @param {String} selector
 * @return {Boolean}
 * @api public
 */

function match(el, selector) {
  if (vendor) return vendor.call(el, selector);
  var nodes = el.parentNode.querySelectorAll(selector);
  for (var i = 0; i < nodes.length; ++i) {
    if (nodes[i] == el) return true;
  }
  return false;
}
},{}],6:[function(require,module,exports){
function select(element) {
    var selectedText;

    if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
        element.focus();
        element.setSelectionRange(0, element.value.length);

        selectedText = element.value;
    }
    else {
        if (element.hasAttribute('contenteditable')) {
            element.focus();
        }

        var selection = window.getSelection();
        var range = document.createRange();

        range.selectNodeContents(element);
        selection.removeAllRanges();
        selection.addRange(range);

        selectedText = selection.toString();
    }

    return selectedText;
}

module.exports = select;

},{}],7:[function(require,module,exports){
function E () {
	// Keep this empty so it's easier to inherit from
  // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
}

E.prototype = {
	on: function (name, callback, ctx) {
    var e = this.e || (this.e = {});

    (e[name] || (e[name] = [])).push({
      fn: callback,
      ctx: ctx
    });

    return this;
  },

  once: function (name, callback, ctx) {
    var self = this;
    function listener () {
      self.off(name, listener);
      callback.apply(ctx, arguments);
    };

    listener._ = callback
    return this.on(name, listener, ctx);
  },

  emit: function (name) {
    var data = [].slice.call(arguments, 1);
    var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
    var i = 0;
    var len = evtArr.length;

    for (i; i < len; i++) {
      evtArr[i].fn.apply(evtArr[i].ctx, data);
    }

    return this;
  },

  off: function (name, callback) {
    var e = this.e || (this.e = {});
    var evts = e[name];
    var liveEvents = [];

    if (evts && callback) {
      for (var i = 0, len = evts.length; i < len; i++) {
        if (evts[i].fn !== callback && evts[i].fn._ !== callback)
          liveEvents.push(evts[i]);
      }
    }

    // Remove event from queue to prevent memory leak
    // Suggested by https://github.com/lazd
    // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910

    (liveEvents.length)
      ? e[name] = liveEvents
      : delete e[name];

    return this;
  }
};

module.exports = E;

},{}],8:[function(require,module,exports){
'use strict';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

Object.defineProperty(exports, "__esModule", {
    value: true
});

var _select = require('select');

var _select2 = _interopRequireDefault(_select);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

/**
 * Inner class which performs selection from either `text` or `target`
 * properties and then executes copy or cut operations.
 */

var ClipboardAction = function () {
    /**
     * @param {Object} options
     */

    function ClipboardAction(options) {
        _classCallCheck(this, ClipboardAction);

        this.resolveOptions(options);
        this.initSelection();
    }

    /**
     * Defines base properties passed from constructor.
     * @param {Object} options
     */

    _createClass(ClipboardAction, [{
        key: 'resolveOptions',
        value: function resolveOptions() {
            var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];

            this.action = options.action;
            this.emitter = options.emitter;
            this.target = options.target;
            this.text = options.text;
            this.trigger = options.trigger;

            this.selectedText = '';
        }

        /**
         * Decides which selection strategy is going to be applied based
         * on the existence of `text` and `target` properties.
         */

    }, {
        key: 'initSelection',
        value: function initSelection() {
            if (this.text && this.target) {
                throw new Error('Multiple attributes declared, use either "target" or "text"');
            } else if (this.text) {
                this.selectFake();
            } else if (this.target) {
                this.selectTarget();
            } else {
                throw new Error('Missing required attributes, use either "target" or "text"');
            }
        }

        /**
         * Creates a fake textarea element, sets its value from `text` property,
         * and makes a selection on it.
         */

    }, {
        key: 'selectFake',
        value: function selectFake() {
            var _this = this;

            var isRTL = document.documentElement.getAttribute('dir') == 'rtl';

            this.removeFake();

            this.fakeHandler = document.body.addEventListener('click', function () {
                return _this.removeFake();
            });

            this.fakeElem = document.createElement('textarea');
            this.fakeElem.style.position = 'absolute';
            this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
            this.fakeElem.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
            this.fakeElem.setAttribute('readonly', '');
            this.fakeElem.value = this.text;

            document.body.appendChild(this.fakeElem);

            this.selectedText = (0, _select2.default)(this.fakeElem);
            this.copyText();
        }

        /**
         * Only removes the fake element after another click event, that way
         * a user can hit `Ctrl+C` to copy because selection still exists.
         */

    }, {
        key: 'removeFake',
        value: function removeFake() {
            if (this.fakeHandler) {
                document.body.removeEventListener('click');
                this.fakeHandler = null;
            }

            if (this.fakeElem) {
                document.body.removeChild(this.fakeElem);
                this.fakeElem = null;
            }
        }

        /**
         * Selects the content from element passed on `target` property.
         */

    }, {
        key: 'selectTarget',
        value: function selectTarget() {
            this.selectedText = (0, _select2.default)(this.target);
            this.copyText();
        }

        /**
         * Executes the copy operation based on the current selection.
         */

    }, {
        key: 'copyText',
        value: function copyText() {
            var succeeded = undefined;

            try {
                succeeded = document.execCommand(this.action);
            } catch (err) {
                succeeded = false;
            }

            this.handleResult(succeeded);
        }

        /**
         * Fires an event based on the copy operation result.
         * @param {Boolean} succeeded
         */

    }, {
        key: 'handleResult',
        value: function handleResult(succeeded) {
            if (succeeded) {
                this.emitter.emit('success', {
                    action: this.action,
                    text: this.selectedText,
                    trigger: this.trigger,
                    clearSelection: this.clearSelection.bind(this)
                });
            } else {
                this.emitter.emit('error', {
                    action: this.action,
                    trigger: this.trigger,
                    clearSelection: this.clearSelection.bind(this)
                });
            }
        }

        /**
         * Removes current selection and focus from `target` element.
         */

    }, {
        key: 'clearSelection',
        value: function clearSelection() {
            if (this.target) {
                this.target.blur();
            }

            window.getSelection().removeAllRanges();
        }

        /**
         * Sets the `action` to be performed which can be either 'copy' or 'cut'.
         * @param {String} action
         */

    }, {
        key: 'destroy',

        /**
         * Destroy lifecycle.
         */
        value: function destroy() {
            this.removeFake();
        }
    }, {
        key: 'action',
        set: function set() {
            var action = arguments.length <= 0 || arguments[0] === undefined ? 'copy' : arguments[0];

            this._action = action;

            if (this._action !== 'copy' && this._action !== 'cut') {
                throw new Error('Invalid "action" value, use either "copy" or "cut"');
            }
        }

        /**
         * Gets the `action` property.
         * @return {String}
         */
        ,
        get: function get() {
            return this._action;
        }

        /**
         * Sets the `target` property using an element
         * that will be have its content copied.
         * @param {Element} target
         */

    }, {
        key: 'target',
        set: function set(target) {
            if (target !== undefined) {
                if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {
                    this._target = target;
                } else {
                    throw new Error('Invalid "target" value, use a valid Element');
                }
            }
        }

        /**
         * Gets the `target` property.
         * @return {String|HTMLElement}
         */
        ,
        get: function get() {
            return this._target;
        }
    }]);

    return ClipboardAction;
}();

exports.default = ClipboardAction;

},{"select":6}],9:[function(require,module,exports){
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

Object.defineProperty(exports, "__esModule", {
    value: true
});

var _clipboardAction = require('./clipboard-action');

var _clipboardAction2 = _interopRequireDefault(_clipboardAction);

var _tinyEmitter = require('tiny-emitter');

var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter);

var _goodListener = require('good-listener');

var _goodListener2 = _interopRequireDefault(_goodListener);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

/**
 * Base class which takes one or more elements, adds event listeners to them,
 * and instantiates a new `ClipboardAction` on each click.
 */

var Clipboard = function (_Emitter) {
    _inherits(Clipboard, _Emitter);

    /**
     * @param {String|HTMLElement|HTMLCollection|NodeList} trigger
     * @param {Object} options
     */

    function Clipboard(trigger, options) {
        _classCallCheck(this, Clipboard);

        var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Clipboard).call(this));

        _this.resolveOptions(options);
        _this.listenClick(trigger);
        return _this;
    }

    /**
     * Defines if attributes would be resolved using internal setter functions
     * or custom functions that were passed in the constructor.
     * @param {Object} options
     */

    _createClass(Clipboard, [{
        key: 'resolveOptions',
        value: function resolveOptions() {
            var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];

            this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
            this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
            this.text = typeof options.text === 'function' ? options.text : this.defaultText;
        }

        /**
         * Adds a click event listener to the passed trigger.
         * @param {String|HTMLElement|HTMLCollection|NodeList} trigger
         */

    }, {
        key: 'listenClick',
        value: function listenClick(trigger) {
            var _this2 = this;

            this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) {
                return _this2.onClick(e);
            });
        }

        /**
         * Defines a new `ClipboardAction` on each click event.
         * @param {Event} e
         */

    }, {
        key: 'onClick',
        value: function onClick(e) {
            var trigger = e.delegateTarget || e.currentTarget;

            if (this.clipboardAction) {
                this.clipboardAction = null;
            }

            this.clipboardAction = new _clipboardAction2.default({
                action: this.action(trigger),
                target: this.target(trigger),
                text: this.text(trigger),
                trigger: trigger,
                emitter: this
            });
        }

        /**
         * Default `action` lookup function.
         * @param {Element} trigger
         */

    }, {
        key: 'defaultAction',
        value: function defaultAction(trigger) {
            return getAttributeValue('action', trigger);
        }

        /**
         * Default `target` lookup function.
         * @param {Element} trigger
         */

    }, {
        key: 'defaultTarget',
        value: function defaultTarget(trigger) {
            var selector = getAttributeValue('target', trigger);

            if (selector) {
                return document.querySelector(selector);
            }
        }

        /**
         * Default `text` lookup function.
         * @param {Element} trigger
         */

    }, {
        key: 'defaultText',
        value: function defaultText(trigger) {
            return getAttributeValue('text', trigger);
        }

        /**
         * Destroy lifecycle.
         */

    }, {
        key: 'destroy',
        value: function destroy() {
            this.listener.destroy();

            if (this.clipboardAction) {
                this.clipboardAction.destroy();
                this.clipboardAction = null;
            }
        }
    }]);

    return Clipboard;
}(_tinyEmitter2.default);

/**
 * Helper function to retrieve attribute value.
 * @param {String} suffix
 * @param {Element} element
 */

exports.default = Clipboard;
function getAttributeValue(suffix, element) {
    var attribute = 'data-clipboard-' + suffix;

    if (!element.hasAttribute(attribute)) {
        return;
    }

    return element.getAttribute(attribute);
}

},{"./clipboard-action":8,"good-listener":4,"tiny-emitter":7}]},{},[9])(9)
});


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>constructor-selector</title>
</head>
<body>
    <!-- 1. Define some markup -->
    <button class="btn" data-clipboard-text="1">Copy</button>
    <button class="btn" data-clipboard-text="2">Copy</button>
    <button class="btn" data-clipboard-text="3">Copy</button>

    <!-- 2. Include library -->
    <script src="../dist/clipboard.min.js"></script>

    <!-- 3. Instantiate clipboard by passing a string selector -->
    <script>
    var clipboard = new Clipboard('.btn');

    clipboard.on('success', function(e) {
        console.log(e);
    });

    clipboard.on('error', function(e) {
        console.log(e);
    });
    </script>
</body>
</html>


<!DOCTYPE html>

    <meta charset="UTF-8">
    <title>function-target</title>

    <button class="btn">Copy</button>

hello


module.exports = function(karma) {
    karma.set({
        plugins: ['karma-browserify', 'karma-chai', 'karma-sinon', 'karma-mocha', 'karma-phantomjs-launcher'],

        frameworks: ['browserify', 'chai', 'sinon', 'mocha'],

        files: [
            'src/**/*.js',
            'test/**/*.js',
            './node_modules/phantomjs-polyfill/bind-polyfill.js'
        ],

        exclude: ['test/module-systems.js'],

        preprocessors: {
            'src/**/*.js' : ['browserify'],
            'test/**/*.js': ['browserify']
        },

        browserify: {
            debug: true,
            transform: [
                ['babelify', { presets: ['es2015'] }]
            ]
        },

        browsers: ['PhantomJS']
    });
}


<!DOCTYPE html>

    <meta charset="UTF-8">
    <title>constructor-node</title>

        Copy


# clipboard.js

[![Build Status](http://img.shields.io/travis/zenorocha/clipboard.js/master.svg?style=flat)](https://travis-ci.org/zenorocha/clipboard.js)
![Killing Flash](https://img.shields.io/badge/killing-flash-brightgreen.svg?style=flat)

> Modern copy to clipboard. No Flash. Just 2kb

## Why

Copying text to the clipboard shouldn't be hard. It shouldn't require dozens of steps to configure or hundreds of KBs to load. But most of all, it shouldn't depend on Flash or any bloated framework.

That's why clipboard.js exists.

## Install

You can get it on npm.

```
npm install clipboard --save
```

Or bower, too.

```
bower install clipboard --save
```

If you're not into package management, just [download a ZIP](https://github.com/zenorocha/clipboard.js/archive/master.zip) file.

## Setup

First, include the script located on the `dist` folder or load it from [a third-party CDN provider](https://github.com/zenorocha/clipboard.js/wiki/CDN-Providers).

```html
<script src="dist/clipboard.min.js"></script>
```

Now, you need to instantiate it by [passing a DOM selector](https://github.com/zenorocha/clipboard.js/blob/master/demo/constructor-selector.html#L18), [HTML element](https://github.com/zenorocha/clipboard.js/blob/master/demo/constructor-node.html#L16-L17), or [list of HTML elements](https://github.com/zenorocha/clipboard.js/blob/master/demo/constructor-nodelist.html#L18-L19).

```js
new Clipboard('.btn');
```

Internally, we need to fetch all elements that matches with your selector and attach event listeners for each one. But guess what? If you have hundreds of matches, this operation can consume a lot of memory.

For this reason we use [event delegation](http://stackoverflow.com/questions/1687296/what-is-dom-event-delegation) which replaces multiple event listeners with just a single listener. After all, [#perfmatters](https://twitter.com/hashtag/perfmatters).

# Usage

We're living a _declarative renaissance_, that's why we decided to take advantage of [HTML5 data attributes](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Using_data_attributes) for better usability.

### Copy text from another element

A pretty common use case is to copy content from another element. You can do that by adding a `data-clipboard-target` attribute in your trigger element.

The value you include on this attribute needs to match another's element selector.

```html
<!-- Target -->
<input id="foo" value="https://github.com/zenorocha/clipboard.js.git">

<!-- Trigger -->
<button class="btn" data-clipboard-target="#foo">
    <img src="assets/clippy.svg" alt="Copy to clipboard">
</button>
```

### Cut text from another element

Additionally, you can define a `data-clipboard-action` attribute to specify if you want to either `copy` or `cut` content.

If you omit this attribute, `copy` will be used by default.

```html
<!-- Target -->
<textarea id="bar">Mussum ipsum cacilds...</textarea>

<!-- Trigger -->
<button class="btn" data-clipboard-action="cut" data-clipboard-target="#bar">
    Cut to clipboard
</button>
```

As you may expect, the `cut` action only works on `<input>` or `<textarea>` elements.

### Copy text from attribute

Truth is, you don't even need another element to copy its content from. You can just include a `data-clipboard-text` attribute in your trigger element.

```html
<!-- Trigger -->
<button class="btn" data-clipboard-text="Just because you can doesn't mean you should — clipboard.js">
    Copy to clipboard
</button>
```

## Events

There are cases where you'd like to show some user feedback or capture what has been selected after a copy/cut operation.

That's why we fire custom events such as `success` and `error` for you to listen and implement your custom logic.

```js
var clipboard = new Clipboard('.btn');

clipboard.on('success', function(e) {
    console.info('Action:', e.action);
    console.info('Text:', e.text);
    console.info('Trigger:', e.trigger);

    e.clearSelection();
});

clipboard.on('error', function(e) {
    console.error('Action:', e.action);
    console.error('Trigger:', e.trigger);
});
```

For a live demonstration, open this [site](http://clipboardjs.com/) and just your console :)

## Advanced Options

If you don't want to modify your HTML, there's a pretty handy imperative API for you to use. All you need to do is declare a function, do your thing, and return a value.

For instance, if you want to dynamically set a `target`, you'll need to return a Node.

```js
new Clipboard('.btn', {
    target: function(trigger) {
        return trigger.nextElementSibling;
    }
});
```

If you want to dynamically set a `text`, you'll return a String.

```js
new Clipboard('.btn', {
    text: function(trigger) {
        return trigger.getAttribute('aria-label');
    }
});
```

Also, if you are working with single page apps, you may want to manage the lifecycle of the DOM more precisely. Here's how you clean up the events and objects that we create.

```js
var clipboard = new Clipboard('.btn');
clipboard.destroy();
```

## Browser Support

This library relies on both [Selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection) and [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) APIs. The second one is supported in the following browsers.

|  |  |  |  |  |
|:---:|:---:|:---:|:---:|:---:|
| 42+ ✔ | 41+ ✔ | 9+ ✔ | 29+ ✔ | Nope ✘ |

Although copy/cut operations with [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) aren't supported on Safari yet (including mobile), it gracefully degrades because [Selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection) is supported.

That means you can show a tooltip saying `Copied!` when `success` event is called and `Press Ctrl+C to copy` when `error` event is called because the text is already selected.

For a live demonstration, open this [site](http://clipboardjs.com) on Safari.

## License

[MIT License](http://zenorocha.mit-license.org/) © Zeno Rocha


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>target-input</title>
</head>
<body>
    <!-- 1. Define some markup -->
    <input id="foo" type="text" value="hello">
    <button class="btn" data-clipboard-action="copy" data-clipboard-target="#foo">Copy</button>

    <!-- 2. Include library -->
    <script src="../dist/clipboard.min.js"></script>

    <!-- 3. Instantiate clipboard -->
    <script>
    var clipboard = new Clipboard('.btn');

    clipboard.on('success', function(e) {
        console.log(e);
    });

    clipboard.on('error', function(e) {
        console.log(e);
    });
    </script>
</body>
</html>


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>constructor-nodelist</title>
</head>
<body>
    <!-- 1. Define some markup -->
    <button data-clipboard-text="1">Copy</button>
    <button data-clipboard-text="2">Copy</button>
    <button data-clipboard-text="3">Copy</button>

    <!-- 2. Include library -->
    <script src="../dist/clipboard.min.js"></script>

    <!-- 3. Instantiate clipboard by passing a list of HTML elements -->
    <script>
    var btns = document.querySelectorAll('button');
    var clipboard = new Clipboard(btns);

    clipboard.on('success', function(e) {
        console.log(e);
    });

    clipboard.on('error', function(e) {
        console.log(e);
    });
    </script>
</body>
</html>


import select from 'select';

/**
 * Inner class which performs selection from either `text` or `target`
 * properties and then executes copy or cut operations.
 */
export default class ClipboardAction {
    /**
     * @param {Object} options
     */
    constructor(options) {
        this.resolveOptions(options);
        this.initSelection();
    }

    /**
     * Defines base properties passed from constructor.
     * @param {Object} options
     */
    resolveOptions(options = {}) {
        this.action  = options.action;
        this.emitter = options.emitter;
        this.target  = options.target;
        this.text    = options.text;
        this.trigger = options.trigger;

        this.selectedText = '';
    }

    /**
     * Decides which selection strategy is going to be applied based
     * on the existence of `text` and `target` properties.
     */
    initSelection() {
        if (this.text && this.target) {
            throw new Error('Multiple attributes declared, use either "target" or "text"');
        }
        else if (this.text) {
            this.selectFake();
        }
        else if (this.target) {
            this.selectTarget();
        }
        else {
            throw new Error('Missing required attributes, use either "target" or "text"');
        }
    }

    /**
     * Creates a fake textarea element, sets its value from `text` property,
     * and makes a selection on it.
     */
    selectFake() {
        let isRTL = document.documentElement.getAttribute('dir') == 'rtl';

        this.removeFake();

        this.fakeHandler = document.body.addEventListener('click', () => this.removeFake());

        this.fakeElem = document.createElement('textarea');
        this.fakeElem.style.position = 'absolute';
        this.fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px';
        this.fakeElem.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
        this.fakeElem.setAttribute('readonly', '');
        this.fakeElem.value = this.text;

        document.body.appendChild(this.fakeElem);

        this.selectedText = select(this.fakeElem);
        this.copyText();
    }

    /**
     * Only removes the fake element after another click event, that way
     * a user can hit `Ctrl+C` to copy because selection still exists.
     */
    removeFake() {
        if (this.fakeHandler) {
            document.body.removeEventListener('click');
            this.fakeHandler = null;
        }

        if (this.fakeElem) {
            document.body.removeChild(this.fakeElem);
            this.fakeElem = null;
        }
    }

    /**
     * Selects the content from element passed on `target` property.
     */
    selectTarget() {
        this.selectedText = select(this.target);
        this.copyText();
    }

    /**
     * Executes the copy operation based on the current selection.
     */
    copyText() {
        let succeeded;

        try {
            succeeded = document.execCommand(this.action);
        }
        catch (err) {
            succeeded = false;
        }

        this.handleResult(succeeded);
    }

    /**
     * Fires an event based on the copy operation result.
     * @param {Boolean} succeeded
     */
    handleResult(succeeded) {
        if (succeeded) {
            this.emitter.emit('success', {
                action: this.action,
                text: this.selectedText,
                trigger: this.trigger,
                clearSelection: this.clearSelection.bind(this)
            });
        }
        else {
            this.emitter.emit('error', {
                action: this.action,
                trigger: this.trigger,
                clearSelection: this.clearSelection.bind(this)
            });
        }
    }

    /**
     * Removes current selection and focus from `target` element.
     */
    clearSelection() {
        if (this.target) {
            this.target.blur();
        }

        window.getSelection().removeAllRanges();
    }

    /**
     * Sets the `action` to be performed which can be either 'copy' or 'cut'.
     * @param {String} action
     */
    set action(action = 'copy') {
        this._action = action;

        if (this._action !== 'copy' && this._action !== 'cut') {
            throw new Error('Invalid "action" value, use either "copy" or "cut"');
        }
    }

    /**
     * Gets the `action` property.
     * @return {String}
     */
    get action() {
        return this._action;
    }

    /**
     * Sets the `target` property using an element
     * that will be have its content copied.
     * @param {Element} target
     */
    set target(target) {
        if (target !== undefined) {
            if (target && typeof target === 'object' && target.nodeType === 1) {
                this._target = target;
            }
            else {
                throw new Error('Invalid "target" value, use a valid Element');
            }
        }
    }

    /**
     * Gets the `target` property.
     * @return {String|HTMLElement}
     */
    get target() {
        return this._target;
    }

    /**
     * Destroy lifecycle.
     */
    destroy() {
        this.removeFake();
    }
}


/*!
 * clipboard.js v<%= pkg.version %>
 * https://zenorocha.github.io/clipboard.js
 *
 * Licensed MIT © Zeno Rocha
 */


lib
npm-debug.log
bower_components
node_modules


# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# http://editorconfig.org

root = true

[*]
# Change these settings to your own preference
indent_style = space
indent_size = 4

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[{package.json,bower.json}]
indent_size = 2


"build": "npm run build-debug && npm run build-min",


"build-debug": "browserify src/clipboard.js -s Clipboard -t [babelify --presets es2015] -p [bannerify --file .banner ] -o dist/clipboard.js",

"build-min": "uglifyjs dist/clipboard.js --comments '/!/' -m screw_ie8=true -c screw_ie8=true,unused=false -o dist/clipboard.min.js",

"build-debug": "browserify src/clipboard.js -s Clipboard -t [babelify --loose all] -p [bannerify --file .banner ] -o dist/clipboard.js",


/*!
 * clipboard.js v1.5.6
 * https://zenorocha.github.io/clipboard.js
 *
 * Licensed MIT © Zeno Rocha
 */
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Clipboard=e()}}(function(){var e,t,n;return function e(t,n,r){function i(a,u){if(!n[a]){if(!t[a]){var c="function"==typeof require&&require;if(!u&&c)return c(a,!0);if(o)return o(a,!0);var s=new Error("Cannot find module '"+a+"'");throw s.code="MODULE_NOT_FOUND",s}var l=n[a]={exports:{}};t[a][0].call(l.exports,function(e){var n=t[a][1][e];return i(n?n:e)},l,l.exports,e,t,n,r)}return n[a].exports}for(var o="function"==typeof require&&require,a=0;a<r.length;a++)i(r[a]);return i}({1:[function(e,t,n){var r=e("matches-selector");t.exports=function(e,t,n){for(var i=n?e:e.parentNode;i&&i!==document;){if(r(i,t))return i;i=i.parentNode}}},{"matches-selector":5}],2:[function(e,t,n){function r(e,t,n,r,o){var a=i.apply(this,arguments);return e.addEventListener(n,a,o),{destroy:function(){e.removeEventListener(n,a,o)}}}function i(e,t,n,r){return function(n){n.delegateTarget=o(n.target,t,!0),n.delegateTarget&&r.call(e,n)}}var o=e("closest");t.exports=r},{closest:1}],3:[function(e,t,n){n.node=function(e){return void 0!==e&&e instanceof HTMLElement&&1===e.nodeType},n.nodeList=function(e){var t=Object.prototype.toString.call(e);return void 0!==e&&("[object NodeList]"===t||"[object HTMLCollection]"===t)&&"length"in e&&(0===e.length||n.node(e[0]))},n.string=function(e){return"string"==typeof e||e instanceof String},n.fn=function(e){var t=Object.prototype.toString.call(e);return"[object Function]"===t}},{}],4:[function(e,t,n){function r(e,t,n){if(!e&&!t&&!n)throw new Error("Missing required arguments");if(!u.string(t))throw new TypeError("Second argument must be a String");if(!u.fn(n))throw new TypeError("Third argument must be a Function");if(u.node(e))return i(e,t,n);if(u.nodeList(e))return o(e,t,n);if(u.string(e))return a(e,t,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function i(e,t,n){return e.addEventListener(t,n),{destroy:function(){e.removeEventListener(t,n)}}}function o(e,t,n){return Array.prototype.forEach.call(e,function(e){e.addEventListener(t,n)}),{destroy:function(){Array.prototype.forEach.call(e,function(e){e.removeEventListener(t,n)})}}}function a(e,t,n){return c(document.body,e,t,n)}var u=e("./is"),c=e("delegate");t.exports=r},{"./is":3,delegate:2}],5:[function(e,t,n){function r(e,t){if(o)return o.call(e,t);for(var n=e.parentNode.querySelectorAll(t),r=0;r<n.length;++r)if(n[r]==e)return!0;return!1}var i=Element.prototype,o=i.matchesSelector||i.webkitMatchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector;t.exports=r},{}],6:[function(e,t,n){function r(e){var t;if("INPUT"===e.nodeName||"TEXTAREA"===e.nodeName)e.focus(),e.setSelectionRange(0,e.value.length),t=e.value;else{e.hasAttribute("contenteditable")&&e.focus();var n=window.getSelection(),r=document.createRange();r.selectNodeContents(e),n.removeAllRanges(),n.addRange(r),t=n.toString()}return t}t.exports=r},{}],7:[function(e,t,n){function r(){}r.prototype={on:function(e,t,n){var r=this.e||(this.e={});return(r[e]||(r[e]=[])).push({fn:t,ctx:n}),this},once:function(e,t,n){function r(){i.off(e,r),t.apply(n,arguments)}var i=this;return r._=t,this.on(e,r,n)},emit:function(e){var t=[].slice.call(arguments,1),n=((this.e||(this.e={}))[e]||[]).slice(),r=0,i=n.length;for(r;i>r;r++)n[r].fn.apply(n[r].ctx,t);return this},off:function(e,t){var n=this.e||(this.e={}),r=n[e],i=[];if(r&&t)for(var o=0,a=r.length;a>o;o++)r[o].fn!==t&&r[o].fn._!==t&&i.push(r[o]);return i.length?n[e]=i:delete n[e],this}},t.exports=r},{}],8:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e},a=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0});var u=e("select"),c=r(u),s=function(){function e(t){i(this,e),this.resolveOptions(t),this.initSelection()}return a(e,[{key:"resolveOptions",value:function e(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.action=t.action,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function e(){if(this.text&&this.target)throw new Error('Multiple attributes declared, use either "target" or "text"');if(this.text)this.selectFake();else{if(!this.target)throw new Error('Missing required attributes, use either "target" or "text"');this.selectTarget()}}},{key:"selectFake",value:function e(){var t=this,n="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandler=document.body.addEventListener("click",function(){return t.removeFake()}),this.fakeElem=document.createElement("textarea"),this.fakeElem.style.position="absolute",this.fakeElem.style[n?"right":"left"]="-9999px",this.fakeElem.style.top=(window.pageYOffset||document.documentElement.scrollTop)+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,document.body.appendChild(this.fakeElem),this.selectedText=(0,c.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function e(){this.fakeHandler&&(document.body.removeEventListener("click"),this.fakeHandler=null),this.fakeElem&&(document.body.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function e(){this.selectedText=(0,c.default)(this.target),this.copyText()}},{key:"copyText",value:function e(){var t=void 0;try{t=document.execCommand(this.action)}catch(n){t=!1}this.handleResult(t)}},{key:"handleResult",value:function e(t){t?this.emitter.emit("success",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)}):this.emitter.emit("error",{action:this.action,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function e(){this.target&&this.target.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function e(){this.removeFake()}},{key:"action",set:function e(){var t=arguments.length<=0||void 0===arguments[0]?"copy":arguments[0];if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function e(){return this._action}},{key:"target",set:function e(t){if(void 0!==t){if(!t||"object"!==("undefined"==typeof t?"undefined":o(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');this._target=t}},get:function e(){return this._target}}]),e}();n.default=s},{select:6}],9:[function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function u(e,t){var n="data-clipboard-"+e;if(t.hasAttribute(n))return t.getAttribute(n)}var c=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();Object.defineProperty(n,"__esModule",{value:!0});var s=e("./clipboard-action"),l=r(s),f=e("tiny-emitter"),d=r(f),h=e("good-listener"),p=r(h),g=function(e){function t(e,n){i(this,t);var r=o(this,Object.getPrototypeOf(t).call(this));return r.resolveOptions(n),r.listenClick(e),r}return a(t,e),c(t,[{key:"resolveOptions",value:function e(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText}},{key:"listenClick",value:function e(t){var n=this;this.listener=(0,p.default)(t,"click",function(e){return n.onClick(e)})}},{key:"onClick",value:function e(t){var n=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l.default({action:this.action(n),target:this.target(n),text:this.text(n),trigger:n,emitter:this})}},{key:"defaultAction",value:function e(t){return u("action",t)}},{key:"defaultTarget",value:function e(t){var n=u("target",t);return n?document.querySelector(n):void 0}},{key:"defaultText",value:function e(t){return u("text",t)}},{key:"destroy",value:function e(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}]),t}(d.default);n.default=g},{"./clipboard-action":8,"good-listener":4,"tiny-emitter":7}]},{},[9])(9)});


"build-watch": "watchify src/clipboard.js -s Clipboard -t [babelify --presets es2015] -o dist/clipboard.js -v",

"build-watch": "watchify src/clipboard.js -s Clipboard -t [babelify --loose all] -o dist/clipboard.js -v",


sudo: false
language: node_js
node_js:
  - stable


"prepublish": "babel src --out-dir lib --presets es2015"

"prepublish": "babel src --out-dir lib --loose all"


Using async functions means that you can try-catch `next`.

This example adds a `.status` to all errors:

Using async functions mean that you can try-catch `next`.

# Guide

  This guide covers Koa topics that are not directly API related, such as best practices for writing middleware and application structure suggestions. In these examples we use async functions as middleware - you can also use commonFunction or generatorFunction which will be a little different.

## Table of Contents

- [Writing Middleware](#writing-middleware)
- [Middleware Best Practices](#middleware-best-practices)
  - [Middleware options](#middleware-options)
  - [Named middleware](#named-middleware)
  - [Combining multiple middleware with koa-compose](#combining-multiple-middleware-with-koa-compose)
  - [Response Middleware](#response-middleware)
- [Async operations](#async-operations)
- [Debugging Koa](#debugging-koa)

## Writing Middleware

  Koa middleware are simple functions which return a `MiddlewareFunction` with signature (ctx, next). When
  the middleware is run, it must manually invoke `next()` to run the "downstream" middleware.

  For example if you wanted to track how long it takes for a request to propagate through Koa by adding an
  `X-Response-Time` header field the middleware would look like the following:

```js
async function responseTime(ctx, next) {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
}

app.use(responseTime);
```

  If you're a front-end developer you can think any code before `next();` as the "capture" phase,
  while any code after is the "bubble" phase. This crude gif illustrates how async function allow us
  to properly utilize stack flow to implement request and response flows:

![Koa middleware](/docs/middleware.gif)

   1. Create a date to track response time
   2. Await control to the next middleware
   3. Create another date to track duration
   4. Await control to the next middleware
   5. Set the response body to "Hello World"
   6. Calculate duration time
   7. Output log line
   8. Calculate response time
   9. Set `X-Response-Time` header field
   10. Hand off to Koa to handle the response

 Next we'll look at the best practices for creating Koa middleware.

## Middleware Best Practices

  This section covers middleware authoring best practices, such as middleware
  accepting options, named middleware for debugging, among others.

### Middleware options

  When creating public middleware it's useful to conform to the convention of
  wrapping the middleware in a function that accepts options, allowing users to
  extend functionality. Even if your middleware accepts _no_ options, this is still
  a good idea to keep things uniform.

  Here our contrived `logger` middleware accepts a `format` string for customization,
  and returns the middleware itself:

```js
function logger(format) {
  format = format || ':method ":url"';

  return async function (ctx, next) {
    const str = format
      .replace(':method', ctx.method)
      .replace(':url', ctx.url);

    console.log(str);

    await next();
  };
}

app.use(logger());
app.use(logger(':method :url'));
```

### Named middleware

  Naming middleware is optional, however it's useful for debugging purposes to assign a name.

```js
function logger(format) {
  return async function logger(ctx, next) {

  };
}
```

### Combining multiple middleware with koa-compose

  Sometimes you want to "compose" multiple middleware into a single middleware for easy re-use or exporting. You can use [koa-compose](https://github.com/koajs/compose)

```js
const compose = require('koa-compose');

async function random(ctx, next) {
  if ('/random' == ctx.path) {
    ctx.body = Math.floor(Math.random() * 10);
  } else {
    await next();
  }
};

async function backwards(ctx, next) {
  if ('/backwards' == ctx.path) {
    ctx.body = 'sdrawkcab';
  } else {
    await next();
  }
}

async function pi(ctx, next) {
  if ('/pi' == ctx.path) {
    ctx.body = String(Math.PI);
  } else {
    await next();
  }
}

const all = compose([random, backwards, pi]);

app.use(all);
```

### Response Middleware

  Middleware that decide to respond to a request and wish to bypass downstream middleware may
  simply omit `next()`. Typically this will be in routing middleware, but this can be performed by
  any. For example the following will respond with "two", however all three are executed, giving the
  downstream "three" middleware a chance to manipulate the response.

```js
app.use(async function (ctx, next) {
  console.log('>> one');
  await next();
  console.log('<< one');
});

app.use(async function (ctx, next) {
  console.log('>> two');
  ctx.body = 'two';
  await next();
  console.log('<< two');
});

app.use(async function (ctx, next) {
  console.log('>> three');
  await next();
  console.log('<< three');
});
```

  The following configuration omits `next()` in the second middleware, and will still respond
  with "two", however the third (and any other downstream middleware) will be ignored:

```js
app.use(async function (ctx, next) {
  console.log('>> one');
  await next();
  console.log('<< one');
});

app.use(async function (ctx, next) {
  console.log('>> two');
  ctx.body = 'two';
  console.log('<< two');
});

app.use(async function (ctx, next) {
  console.log('>> three');
  await next();
  console.log('<< three');
});
```

  When the furthest downstream middleware executes `next();`, it's really yielding to a noop
  function, allowing the middleware to compose correctly anywhere in the stack.

## Async operations

  Async function and promise forms Koa's foundation, allowing
  you to write non-blocking sequential code. For example this middleware reads the filenames from `./docs`,
  and then reads the contents of each markdown file in parallel before assigning the body to the joint result.

```js
const fs = require('mz/fs');

app.use(async function (ctx, next) {
  const paths = await fs.readdir('docs');
  const files = await Promise.all(paths.map(path => fs.readFile(`docs/${path}`, 'utf8')));

  ctx.type = 'markdown';
  ctx.body = files.join('');
});
```

## Debugging Koa

  Koa along with many of the libraries it's built with support the __DEBUG__ environment variable from [debug](https://github.com/visionmedia/debug) which provides simple conditional logging.

  For example
  to see all Koa-specific debugging information just pass `DEBUG=koa*` and upon boot you'll see the list of middleware used, among other things.

```
$ DEBUG=koa* node --harmony examples/simple
  koa:application use responseTime +0ms
  koa:application use logger +4ms
  koa:application use contentLength +0ms
  koa:application use notfound +0ms
  koa:application use response +0ms
  koa:application listen +0ms
```

  Since JavaScript does not allow defining function names at
  runtime, you can also set a middleware's name as `._name`.
  This is useful when you don't have control of a middleware's name.
  For example:

```js
const path = require('path');
const serve = require('koa-static');

const publicFiles = serve(path.join(__dirname, 'public'));
publicFiles._name = 'static /public';

app.use(publicFiles);
```

  Now, instead of just seeing "serve" when debugging, you will see:

```
  koa:application use static /public +0ms
```


'use strict';

const request = require('../helpers/context').request;
const assert = require('assert');

describe('req.hostname', () => {
  it('should return hostname void of port', () => {
    const req = request();
    req.header.host = 'foo.com:3000';
    assert.equal(req.hostname, 'foo.com');
  });

  describe('with no host present', () => {
    it('should return ""', () => {
      const req = request();
      assert.equal(req.hostname, '');
    });
  });

  describe('with IPv6 in host', () => {
    it('should parse localhost void of port', () => {
      const req = request();
      req.header.host = '[::1]';
      assert.equal(req.hostname, '[::1]');
    });

    it('should parse localhost with port 80', () => {
      const req = request();
      req.header.host = '[::1]:80';
      assert.equal(req.hostname, '[::1]');
    });

    it('should parse localhost with non-special schema port', () => {
      const req = request();
      req.header.host = '[::1]:1337';
      assert.equal(req.hostname, '[::1]');
    });

    it('should reduce IPv6 with non-special schema port as hostname', () => {
      const req = request();
      req.header.host = '[2001:cdba:0000:0000:0000:0000:3257:9652]:1337';
      assert.equal(req.hostname, '[2001:cdba::3257:9652]');
    });

    it('should return empty string when invalid', () => {
      const req = request();
      req.header.host = '[invalidIPv6]';
      assert.equal(req.hostname, '');
    });
  });

  describe('when X-Forwarded-Host is present', () => {
    describe('and proxy is not trusted', () => {
      it('should be ignored', () => {
        const req = request();
        req.header['x-forwarded-host'] = 'bar.com';
        req.header.host = 'foo.com';
        assert.equal(req.hostname, 'foo.com');
      });
    });

    describe('and proxy is trusted', () => {
      it('should be used', () => {
        const req = request();
        req.app.proxy = true;
        req.header['x-forwarded-host'] = 'bar.com, baz.com';
        req.header.host = 'foo.com';
        assert.equal(req.hostname, 'bar.com');
      });
    });
  });
});


'use strict';

const response = require('../helpers/context').response;
const assert = require('assert');
const util = require('util');

describe('res.inspect()', () => {
  describe('with no response.res present', () => {
    it('should return null', () => {
      const res = response();
      res.body = 'hello';
      delete res.res;
      assert.equal(res.inspect(), null);
      assert.equal(util.inspect(res), 'undefined');
    });
  });

  it('should return a json representation', () => {
    const res = response();
    res.body = 'hello';

    const expected = {
      status: 200,
      message: 'OK',
      header: {
        'content-type': 'text/plain; charset=utf-8',
        'content-length': '5'
      },
      body: 'hello'
    };

    assert.deepEqual(res.inspect(), expected);
    assert.deepEqual(util.inspect(res), util.inspect(expected));
  });
});


The default error handler is essentially a `try-catch` at

# Error Handling

## Try-Catch

  Using async functions means that you can try-catch `next`.
  This example adds a `.status` to all errors:

  ```js
  app.use(async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      err.status = err.statusCode || err.status || 500;
      throw err;
    }
  });
  ```

### Default Error Handler

  The default error handler is essentially a try-catch at
  the very beginning of the middleware chain. To use a
  different error handler, simply put another try-catch at
  the beginning of the middleware chain, and handle the error
  there. However, the default error handler is good enough for
  most use cases. It will use a status code of `err.status`,
  or by default 500. If `err.expose` is true, then `err.message`
  will be the reply. Otherwise, a message generated from the
  error code will be used (e.g. for the code 500 the message
  "Internal Server Error" will be used). All headers will be
  cleared from the request, but any headers in `err.headers`
  will then be set. You can use a try-catch, as specified
  above, to add a header to this list.

  Here is an example of creating your own error handler:

```js
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    // will only respond with JSON
    ctx.status = err.statusCode || err.status || 500;
    ctx.body = {
      message: err.message
    };
  }
})
```

## The Error Event

  Error event listeners can be specified with `app.on('error')`.
  If no error listener is specified, a default error listener
  is used. Error listener receive all errors that make their
  way back through the middleware chain, if an error is caught
  and not thrown again, it will not be passed to the error
  listener. If no error event listener is specified, then
  `app.onerror` will be used, which simply log the error unless
  `error.expose`is true or `app.silent` is true or `error.status`
  is 404.


'use strict';

const request = require('../helpers/context').request;
const assert = require('assert');

describe('req.charset', () => {
  describe('with no content-type present', () => {
    it('should return ""', () => {
      const req = request();
      assert('' === req.charset);
    });
  });

  describe('with charset present', () => {
    it('should return ""', () => {
      const req = request();
      req.header['content-type'] = 'text/plain';
      assert('' === req.charset);
    });
  });

  describe('with a charset', () => {
    it('should return the charset', () => {
      const req = request();
      req.header['content-type'] = 'text/plain; charset=utf-8';
      assert.equal(req.charset, 'utf-8');
    });

    it('should return "" if content-type is invalid', () => {
      const req = request();
      req.header['content-type'] = 'application/json; application/text; charset=utf-8';
      assert.equal(req.charset, '');
    });
  });
});


'use strict';

const assert = require('assert');
const request = require('../helpers/context').request;

describe('req.ips', () => {
  describe('when X-Forwarded-For is present', () => {
    describe('and proxy is not trusted', () => {
      it('should be ignored', () => {
        const req = request();
        req.app.proxy = false;
        req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2';
        assert.deepEqual(req.ips, []);
      });
    });

    describe('and proxy is trusted', () => {
      it('should be used', () => {
        const req = request();
        req.app.proxy = true;
        req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2';
        assert.deepEqual(req.ips, ['127.0.0.1', '127.0.0.2']);
      });
    });
  });

  describe('when options.proxyIpHeader is present', () => {
    describe('and proxy is not trusted', () => {
      it('should be ignored', () => {
        const req = request();
        req.app.proxy = false;
        req.app.proxyIpHeader = 'x-client-ip';
        req.header['x-client-ip'] = '127.0.0.1,127.0.0.2';
        assert.deepEqual(req.ips, []);
      });
    });

    describe('and proxy is trusted', () => {
      it('should be used', () => {
        const req = request();
        req.app.proxy = true;
        req.app.proxyIpHeader = 'x-client-ip';
        req.header['x-client-ip'] = '127.0.0.1,127.0.0.2';
        assert.deepEqual(req.ips, ['127.0.0.1', '127.0.0.2']);
      });
    });
  });

  describe('when options.maxIpsCount is present', () => {
    describe('and proxy is not trusted', () => {
      it('should be ignored', () => {
        const req = request();
        req.app.proxy = false;
        req.app.maxIpsCount = 1;
        req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2';
        assert.deepEqual(req.ips, []);
      });
    });

    describe('and proxy is trusted', () => {
      it('should be used', () => {
        const req = request();
        req.app.proxy = true;
        req.app.maxIpsCount = 1;
        req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2';
        assert.deepEqual(req.ips, ['127.0.0.2']);
      });
    });
  });
});


'use strict';

const assert = require('assert');
const context = require('../helpers/context');
const parseurl = require('parseurl');

describe('ctx.querystring', () => {
  it('should return the querystring', () => {
    const ctx = context({ url: '/store/shoes?page=2&color=blue' });
    assert.equal(ctx.querystring, 'page=2&color=blue');
  });

  describe('when ctx.req not present', () => {
    it('should return an empty string', () => {
      const ctx = context();
      ctx.request.req = null;
      assert.equal(ctx.querystring, '');
    });
  });
});

describe('ctx.querystring=', () => {
  it('should replace the querystring', () => {
    const ctx = context({ url: '/store/shoes' });
    ctx.querystring = 'page=2&color=blue';
    assert.equal(ctx.url, '/store/shoes?page=2&color=blue');
    assert.equal(ctx.querystring, 'page=2&color=blue');
  });

  it('should update ctx.search and ctx.query', () => {
    const ctx = context({ url: '/store/shoes' });
    ctx.querystring = 'page=2&color=blue';
    assert.equal(ctx.url, '/store/shoes?page=2&color=blue');
    assert.equal(ctx.search, '?page=2&color=blue');
    assert.equal(ctx.query.page, '2');
    assert.equal(ctx.query.color, 'blue');
  });

  it('should change .url but not .originalUrl', () => {
    const ctx = context({ url: '/store/shoes' });
    ctx.querystring = 'page=2&color=blue';
    assert.equal(ctx.url, '/store/shoes?page=2&color=blue');
    assert.equal(ctx.originalUrl, '/store/shoes');
    assert.equal(ctx.request.originalUrl, '/store/shoes');
  });

  it('should not affect parseurl', () => {
    const ctx = context({ url: '/login?foo=bar' });
    ctx.querystring = 'foo=bar';
    const url = parseurl(ctx.req);
    assert.equal(url.path, '/login?foo=bar');
  });
});


different error handler, simply put another try-catch at

the beginning of the middleware chain, and handle the error

there. However, the default error handler is good enough for

most use cases. It will use a status code of `err.status`,

different error handler, simply put another `try-catch` at


'use strict';

const request = require('supertest');
const assert = require('assert');
const Koa = require('../..');
const http = require('http');

describe('ctx.flushHeaders()', () => {
  it('should set headersSent', () => {
    const app = new Koa();

    app.use((ctx, next) => {
      ctx.body = 'Body';
      ctx.status = 200;
      ctx.flushHeaders();
      assert.equal(ctx.res.headersSent, true);
    });

    const server = app.listen();

    return request(server)
      .get('/')
      .expect(200)
      .expect('Body');
  });

  it('should allow a response afterwards', () => {
    const app = new Koa();

    app.use((ctx, next) => {
      ctx.status = 200;
      ctx.res.setHeader('Content-Type', 'text/plain');
      ctx.flushHeaders();
      ctx.body = 'Body';
    });

    const server = app.listen();
    return request(server)
      .get('/')
      .expect(200)
      .expect('Content-Type', 'text/plain')
      .expect('Body');
  });

  it('should send the correct status code', () => {
    const app = new Koa();

    app.use((ctx, next) => {
      ctx.status = 401;
      ctx.res.setHeader('Content-Type', 'text/plain');
      ctx.flushHeaders();
      ctx.body = 'Body';
    });

    const server = app.listen();
    return request(server)
      .get('/')
      .expect(401)
      .expect('Content-Type', 'text/plain')
      .expect('Body');
  });

  it('should ignore set header after flushHeaders', async() => {
    const app = new Koa();

    app.use((ctx, next) => {
      ctx.status = 401;
      ctx.res.setHeader('Content-Type', 'text/plain');
      ctx.flushHeaders();
      ctx.body = 'foo';
      ctx.set('X-Shouldnt-Work', 'Value');
      ctx.remove('Content-Type');
      ctx.vary('Content-Type');
    });

    const server = app.listen();
    const res = await request(server)
      .get('/')
      .expect(401)
      .expect('Content-Type', 'text/plain');

    assert.equal(res.headers['x-shouldnt-work'], undefined, 'header set after flushHeaders');
    assert.equal(res.headers.vary, undefined, 'header set after flushHeaders');
  });

  it('should flush headers first and delay to send data', done => {
    const PassThrough = require('stream').PassThrough;
    const app = new Koa();

    app.use(ctx => {
      ctx.type = 'json';
      ctx.status = 200;
      ctx.headers['Link'] = '</css/mycss.css>; as=style; rel=preload, <https://img.craftflair.com>; rel=preconnect; crossorigin';
      const stream = ctx.body = new PassThrough();
      ctx.flushHeaders();

      setTimeout(() => {
        stream.end(JSON.stringify({ message: 'hello!' }));
      }, 10000);
    });

    app.listen(function(err){
      if (err) return done(err);

      const port = this.address().port;

      http.request({
        port
      })
        .on('response', res => {
          const onData = () => done(new Error('boom'));
          res.on('data', onData);

          // shouldn't receive any data for a while
          setTimeout(() => {
            res.removeListener('data', onData);
            done();
          }, 1000);
        })
        .on('error', done)
        .end();
    });
  });

  it('should catch stream error', done => {
    const PassThrough = require('stream').PassThrough;
    const app = new Koa();
    app.once('error', err => {
      assert(err.message === 'mock error');
      done();
    });

    app.use(ctx => {
      ctx.type = 'json';
      ctx.status = 200;
      ctx.headers['Link'] = '</css/mycss.css>; as=style; rel=preload, <https://img.craftflair.com>; rel=preconnect; crossorigin';
      ctx.length = 20;
      ctx.flushHeaders();
      const stream = ctx.body = new PassThrough();

      setTimeout(() => {
        stream.emit('error', new Error('mock error'));
      }, 100);
    });

    const server = app.listen();

    request(server).get('/').end();
  });
});


小菜 <xtx1130@gmail.com>
Aaron Heckmann <aaron.heckmann+github@gmail.com>
Adam L <skyros@gmail.com>
Adam Lau <skyros@gmail.com>
Aesop Wolf <aesopwolf@users.noreply.github.com>
AlexeyKhristov <AlexeyKhristov@users.noreply.github.com>
Alexsey <agat00@gmail.com>
Amit Portnoy <amit.portnoy@gmail.com>
Anton Harniakou <anton.harniakou@gmail.com>
Arjun <arjun453@gmail.com>
Asiel Leal <lealceldeiro@gmail.com>
Avindra Goolcharan <aavindraa@gmail.com>
Bartol Karuza <bartol.k@gmail.com>
Ben Reinhart <breinhart@groupon.com>
Bernie Stern <bernzs@gmail.com>
Bryan Bess <squarejaw@bsbess.com>
C.T. Lin <chentsulin@gmail.com>
Chiahao Lin <purepennons@users.noreply.github.com>
Chris Tarquini <chris@ilsken.com>
Christoffer Hallas <hallas@users.noreply.github.com>
Clark Du <clark.duxin@gmail.com>
Darren Cauthon <darren@cauthon.com>
Debjeet Biswas <debjeet@vxtindia.com>
Dmitry Mazuro <dmitry.mazuro@icloud.com>
Douglas Christopher Wilson <doug@somethingdoug.com>
Eivind Fjeldstad <eivind.fjeldstad@gmail.com>
Equim <sayaka@ekyu.moe>
Fangdun Cai <fundon@users.noreply.github.com>
Felix Becker <felix.b@outlook.com>
Filip Skokan <panva.ip@gmail.com>
Francisco Presencia <franciscop@users.noreply.github.com>
George Chung <Gerhut@GMail.com>
Gilles De Mey <gilles.de.mey@gmail.com>
Grand <sungg12138@163.com>
Guilherme Pacheco <guilherme.f.pacheco@hotmail.com>
HanHor Wu <hanhor.wu@gmail.com>
Hartley Melamed <hartley@melamed.biz>
Hrvoje Šimić <hrvoje@twobucks.co>
Hugh Kennedy <hughskennedy@gmail.com>
Ian Storm Taylor <ian@ianstormtaylor.com>
Ilkka Oksanen <iao@iki.fi>
Ivan Kleshnin <ivan@paqmind.com>
Ivan Lyons <iliyang.cn@gmail.com>
Jacob Bass <jacob@jacobbass.net>
JamesWang <likegun94@gmail.com>
Jan Buschtöns <buschtoens@gmail.com>
Jan Carlo Viray <virayjancarlo@yahoo.com>
Jason Macgowan <jason.macgowan@icloud.com>
Jed Schmidt <where@jed.is>
Jeff Moore <jeff@procata.com>
Jesus Rodriguez <foxandxss@gmail.com>
Jesús Rodríguez Rodríguez <Foxandxss@gmail.com>
Jingwei "John" Liu <liujingwei@gmail.com>
Johan Bergström <bugs@bergstroem.nu>
Jonas Zhang <106856363@qq.com>
Jonathan Ong <jonathanrichardong@gmail.com>
Jonathan Ong <me@jongleberry.com>
Joseph Lin <josephlin55555@gmail.com>
Julian Gruber <julian@juliangruber.com>
Kareem Kwong <kareem.kwong@gmail.com>
Karl Böhlmark <karl.bohlmark@gmail.com>
Kenneth Ormandy <kenneth@chloi.io>
Kim Joar Bekkelund <kjbekkelund@gmail.com>
Kwyn Alice Meagher <kwyn.meagher@gmail.com>
Kyle Suss <susskyle@gmail.com>
Lee Bousfield <ljbousfield@gmail.com>
Louis DeScioli <louis.descioli@gmail.com>
Luke Bousfield <math.master.champion@gmail.com>
Malcolm <noinkling@users.noreply.github.com>
Marceli.no <me@marceli.no>
Mars Wong <marswong618@gmail.com>
Martin Iwanowski <martin@iwanowski.se>
Martin Iwanowski <me@fl0w.io>
Martin fl0w Iwanowski <martin@iwanowski.se>
Matheus Azzi <matheuslazzi@gmail.com>
Mathieu Gallé-Tessonneau <mathieu.galletessonneau@gmail.com>
Matthew Chase Whittemore <matthew@socialtables.com>
Matthew King <mking@users.noreply.github.com>
Matthew Mueller <mattmuelle@gmail.com>
Mengdi Gao <gaomdev@gmail.com>
Michaël Zasso <mic.besace@gmail.com>
Michał Gołębiowski-Owczarek <m.goleb@gmail.com>
Nathan Rajlich <nathan@tootallnate.net>
New Now Nohow <empty@cqdr.es>
Nick McCurdy <nick@nickmccurdy.com>
Nicolae Vartolomei <nvartolomei@gmail.com>
PatrickJS <github@gdi2290.com>
Paul Anderson <thesamuraipanda@gmail.com>
Pedro Pablo Aste Kompen <wachunei@gmail.com>
Peeyush Kushwaha <peeyush.p97@gmail.com>
Phillip Alexander <git@phillipalexander.io>
PlasmaPower <ljbousfield@gmail.com>
Prayag Verma <prayag.verma@gmail.com>
Qiming zhao <chemzqm@gmail.com>
Remek Ambroziak <remek.ambroziak@gmail.com>
Riceball LEE <snowyu.lee@gmail.com>
Richard Marmorstein <twitchard@users.noreply.github.com>
Rico Sta. Cruz <rstacruz@users.noreply.github.com>
Robert Sköld <robert@publicclass.se>
Robin Pokorný <me@robinpokorny.com>
Ruben Bridgewater <ruben@bridgewater.de>
Rui Marinho <rpm@seegno.com>
Rui Marinho <ruipmarinho@gmail.com>
Ryunosuke SATO <tricknotes.rs@gmail.com>
Saad Quadri <saad@saadq.com>
Santiago Sotomayor <sansoto2003@yahoo.com.ar>
Sergei Osipov <hcz@users.noreply.github.com>
Shaun Warman <shaunwarman1@gmail.com>
Shawn Cheung <958033967@qq.com>
Shawn Sit <xueqingxiao@gmail.com>
Slobodan Stojanovic <slobodan@cloudhorizon.com>
Sonny Piers <sonny@fastmail.net>
Sterling Williams <sterlingw@qualtrics.com>
Stéphane Bisinger <stephane.bisinger@protonmail.com>
TJ Holowaychuk <tj@apex.sh>
TJ Holowaychuk <tj@vision-media.ca>
Taehwan, No <taehwanno.dev@gmail.com>
Tejas Manohar <me@tejas.io>
Teoman Soygul <teo@soygul.com>
Thiago Lagden <lagden@gmail.com>
Tiago Ribeiro <tlr@seegno.com>
Tim Schaub <tim.schaub@gmail.com>
Todor Stoychev <pretodor@gmail.com>
Tomas Ruud <tomasruud@users.noreply.github.com>
Travis Jeffery <tj@travisjeffery.com>
Usman Hussain <usmandap@gmail.com>
Veselin Todorov <veselin@veselin.bg>
Wang Dàpéng <wonderfuly@gmail.com>
Xavier Damman <xdamman@gmail.com>
Xiang Gao <geekplux@qq.com>
Yanick Rochon <yanick.rochon@gmail.com>
Yazhong Liu <l900422@vip.qq.com>
Yazhong Liu <yorkiefixer@gmail.com>
Yiyu He <dead-horse@users.noreply.github.com>
Yiyu He <dead_horse@qq.com>
Yoshua Wuyts <yoshuawuyts@gmail.com>
Yu Qi <iyuq@outlook.com>
Yu Qi <njuyuqi@gmail.com>
Zack Tanner <zacktanner@gmail.com>
alsotang <alsotang@gmail.com>
bananaappletw <bananaappletw@gmail.com>
bhanuc <bhanuc@iitk.ac.in>
blaz <blaz@menems.net>
broucz <broucapierre@gmail.com>
d3v <cr1s@users.noreply.github.com>
dead-horse <dead_horse@qq.com>
dead_horse <dead_horse@qq.com>
designgrill <anshul@designgrill.com>
fengmk2 <fengmk2@gmail.com>
fengmk2 <m@fengmk2.com>
frank <frankxin93@hotmail.com>
fundon <cfddream@gmail.com>
gyson <eilian.yunsong@gmail.com>
haoxin <coderhaoxin@outlook.com>
haoxin <haoxins@icloud.com>
iamchenxin <iamchenxin@gmail.com>
initial-wu <initial-wu@outlook.com>
jeromew <jerome.wagner@m4x.org>
joehecn <leanbrown@live.cn>
jongleberry <jonathanong@users.noreply.github.com>
jongleberry <me@jongleberry.com>
llambda <xxgsoftware@gmail.com>
mako-taco <jake.y.scott@gmail.com>
mdemo <mds@xue.bi>
nicoder <nicolas.dermine@gmail.com>
nswbmw <gxqzk@126.com>
pana <pana.wang@outlook.com>
qingming <358242939@qq.com>
song <xiongsongsong@outlook.com>
superchink <superchink@gmail.com>
tmilewski <tmilewski@gmail.com>
yoshuawuyts <i@yoshuawuyts.com>
yosssi <yoshida.keiji.84@gmail.com>
zensh <admin@zensh.com>
ziyunfei <446240525@qq.com>
石发磊 <sshsfl@yeah.net>


'use strict';

const request = require('supertest');
const assert = require('assert');
const Koa = require('../..');

describe('app.context', () => {
  const app1 = new Koa();
  app1.context.msg = 'hello';
  const app2 = new Koa();

  it('should merge properties', () => {
    app1.use((ctx, next) => {
      assert.equal(ctx.msg, 'hello');
      ctx.status = 204;
    });

    return request(app1.listen())
      .get('/')
      .expect(204);
  });

  it('should not affect the original prototype', () => {
    app2.use((ctx, next) => {
      assert.equal(ctx.msg, undefined);
      ctx.status = 204;
    });

    return request(app2.listen())
      .get('/')
      .expect(204);
  });
});


will be the reply. Otherwise, a message generated from the

error code will be used (e.g. for the code 500 the message

"Internal Server Error" will be used). All headers will be

cleared from the request, but any headers in `err.headers`

will then be set. You can use a try-catch, as specified

Here is an example of creating your own error handler:

keep keep keep keep replace keep keep keep

will then be set. You can use a `try-catch`, as specified


'use strict';

const Koa = require('..');
const app = new Koa();

// number of middleware

let n = parseInt(process.env.MW || '1', 10);
const useAsync = process.env.USE_ASYNC === 'true';

console.log(`  ${n}${useAsync ? ' async' : ''} middleware`);

while (n--) {
  if (useAsync) {
    app.use(async(ctx, next) => next());
  } else {
    app.use((ctx, next) => next());
  }
}

const body = Buffer.from('Hello World');

if (useAsync) {
  app.use(async(ctx, next) => { await next(); ctx.body = body; });
} else {
  app.use((ctx, next) => next().then(() => ctx.body = body));
}

app.listen(3333);


'use strict';

const request = require('../helpers/context').request;
const assert = require('assert');
const util = require('util');

describe('req.inspect()', () => {
  describe('with no request.req present', () => {
    it('should return null', () => {
      const req = request();
      req.method = 'GET';
      delete req.req;
      assert(undefined === req.inspect());
      assert('undefined' === util.inspect(req));
    });
  });

  it('should return a json representation', () => {
    const req = request();
    req.method = 'GET';
    req.url = 'example.com';
    req.header.host = 'example.com';

    const expected = {
      method: 'GET',
      url: 'example.com',
      header: {
        host: 'example.com'
      }
    };

    assert.deepEqual(req.inspect(), expected);
    assert.deepEqual(util.inspect(req), util.inspect(expected));
  });
});


'use strict';

const context = require('../helpers/context');
const assert = require('assert');

describe('ctx.is(type)', () => {
  it('should ignore params', () => {
    const ctx = context();
    ctx.header['content-type'] = 'text/html; charset=utf-8';
    ctx.header['transfer-encoding'] = 'chunked';

    assert.equal(ctx.is('text/*'), 'text/html');
  });

  describe('when no body is given', () => {
    it('should return null', () => {
      const ctx = context();

      assert.equal(ctx.is(), null);
      assert.equal(ctx.is('image/*'), null);
      assert.equal(ctx.is('image/*', 'text/*'), null);
    });
  });

  describe('when no content type is given', () => {
    it('should return false', () => {
      const ctx = context();
      ctx.header['transfer-encoding'] = 'chunked';

      assert.equal(ctx.is(), false);
      assert.equal(ctx.is('image/*'), false);
      assert.equal(ctx.is('text/*', 'image/*'), false);
    });
  });

  describe('give no types', () => {
    it('should return the mime type', () => {
      const ctx = context();
      ctx.header['content-type'] = 'image/png';
      ctx.header['transfer-encoding'] = 'chunked';

      assert.equal(ctx.is(), 'image/png');
    });
  });

  describe('given one type', () => {
    it('should return the type or false', () => {
      const ctx = context();
      ctx.header['content-type'] = 'image/png';
      ctx.header['transfer-encoding'] = 'chunked';

      assert.equal(ctx.is('png'), 'png');
      assert.equal(ctx.is('.png'), '.png');
      assert.equal(ctx.is('image/png'), 'image/png');
      assert.equal(ctx.is('image/*'), 'image/png');
      assert.equal(ctx.is('*/png'), 'image/png');

      assert.equal(ctx.is('jpeg'), false);
      assert.equal(ctx.is('.jpeg'), false);
      assert.equal(ctx.is('image/jpeg'), false);
      assert.equal(ctx.is('text/*'), false);
      assert.equal(ctx.is('*/jpeg'), false);
    });
  });

  describe('given multiple types', () => {
    it('should return the first match or false', () => {
      const ctx = context();
      ctx.header['content-type'] = 'image/png';
      ctx.header['transfer-encoding'] = 'chunked';

      assert.equal(ctx.is('png'), 'png');
      assert.equal(ctx.is('.png'), '.png');
      assert.equal(ctx.is('text/*', 'image/*'), 'image/png');
      assert.equal(ctx.is('image/*', 'text/*'), 'image/png');
      assert.equal(ctx.is('image/*', 'image/png'), 'image/png');
      assert.equal(ctx.is('image/png', 'image/*'), 'image/png');

      assert.equal(ctx.is(['text/*', 'image/*']), 'image/png');
      assert.equal(ctx.is(['image/*', 'text/*']), 'image/png');
      assert.equal(ctx.is(['image/*', 'image/png']), 'image/png');
      assert.equal(ctx.is(['image/png', 'image/*']), 'image/png');

      assert.equal(ctx.is('jpeg'), false);
      assert.equal(ctx.is('.jpeg'), false);
      assert.equal(ctx.is('text/*', 'application/*'), false);
      assert.equal(ctx.is('text/html', 'text/plain', 'application/json; charset=utf-8'), false);
    });
  });

  describe('when Content-Type: application/x-www-form-urlencoded', () => {
    it('should match "urlencoded"', () => {
      const ctx = context();
      ctx.header['content-type'] = 'application/x-www-form-urlencoded';
      ctx.header['transfer-encoding'] = 'chunked';

      assert.equal(ctx.is('urlencoded'), 'urlencoded');
      assert.equal(ctx.is('json', 'urlencoded'), 'urlencoded');
      assert.equal(ctx.is('urlencoded', 'json'), 'urlencoded');
    });
  });
});


Error event listeners can be specified with `app.on('error')`.

If no error listener is specified, a default error listener

is used. Error listener receive all errors that make their

way back through the middleware chain, if an error is caught

and not thrown again, it will not be passed to the error

listener. If no error event listener is specified, then

keep keep keep keep keep replace keep keep keep

is used. Error listeners receive all errors that make their


'use strict';

const context = require('../helpers/context');
const assert = require('assert');

describe('ctx.type=', () => {
  describe('with a mime', () => {
    it('should set the Content-Type', () => {
      const ctx = context();
      ctx.type = 'text/plain';
      assert.equal(ctx.type, 'text/plain');
      assert.equal(ctx.response.header['content-type'], 'text/plain; charset=utf-8');
    });
  });

  describe('with an extension', () => {
    it('should lookup the mime', () => {
      const ctx = context();
      ctx.type = 'json';
      assert.equal(ctx.type, 'application/json');
      assert.equal(ctx.response.header['content-type'], 'application/json; charset=utf-8');
    });
  });

  describe('without a charset', () => {
    it('should default the charset', () => {
      const ctx = context();
      ctx.type = 'text/html';
      assert.equal(ctx.type, 'text/html');
      assert.equal(ctx.response.header['content-type'], 'text/html; charset=utf-8');
    });
  });

  describe('with a charset', () => {
    it('should not default the charset', () => {
      const ctx = context();
      ctx.type = 'text/html; charset=foo';
      assert.equal(ctx.type, 'text/html');
      assert.equal(ctx.response.header['content-type'], 'text/html; charset=foo');
    });
  });

  describe('with an unknown extension', () => {
    it('should not set a content-type', () => {
      const ctx = context();
      ctx.type = 'asdf';
      assert(!ctx.type);
      assert(!ctx.response.header['content-type']);
    });
  });
});

describe('ctx.type', () => {
  describe('with no Content-Type', () => {
    it('should return ""', () => {
      const ctx = context();
      assert(!ctx.type);
    });
  });

  describe('with a Content-Type', () => {
    it('should return the mime', () => {
      const ctx = context();
      ctx.type = 'json';
      assert.equal(ctx.type, 'application/json');
    });
  });
});


# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at tj@tjholowaychuk.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


'use strict';

const assert = require('assert');
const context = require('../helpers/context');

describe('ctx.vary(field)', () => {
  describe('when Vary is not set', () => {
    it('should set it', () => {
      const ctx = context();
      ctx.vary('Accept');
      assert.equal(ctx.response.header.vary, 'Accept');
    });
  });

  describe('when Vary is set', () => {
    it('should append', () => {
      const ctx = context();
      ctx.vary('Accept');
      ctx.vary('Accept-Encoding');
      assert.equal(ctx.response.header.vary, 'Accept, Accept-Encoding');
    });
  });

  describe('when Vary already contains the value', () => {
    it('should not append', () => {
      const ctx = context();
      ctx.vary('Accept');
      ctx.vary('Accept-Encoding');
      ctx.vary('Accept');
      ctx.vary('Accept-Encoding');
      assert.equal(ctx.response.header.vary, 'Accept, Accept-Encoding');
    });
  });
});


- [Middleware Best Practices](#middleware-best-practices)

- [Combining multiple middleware with koa-compose](#combining-multiple-middleware-with-koa-compose)

keep keep keep replace replace replace replace replace replace replace replace keep keep keep

- [Table of Contents](#table-of-contents)


'use strict';

const assert = require('assert');
const Stream = require('stream');
const context = require('../helpers/context');

describe('ctx.origin', () => {
  it('should return the origin of url', () => {
    const socket = new Stream.Duplex();
    const req = {
      url: '/users/1?next=/dashboard',
      headers: {
        host: 'localhost'
      },
      socket: socket,
      __proto__: Stream.Readable.prototype
    };
    const ctx = context(req);
    assert.equal(ctx.origin, 'http://localhost');
    // change it also work
    ctx.url = '/foo/users/1?next=/dashboard';
    assert.equal(ctx.origin, 'http://localhost');
  });
});


'use strict';

const assert = require('assert');
const Koa = require('../../');
const net = require('net');

describe('res.writable', () => {
  describe('when continuous requests in one persistent connection', () => {
    function requestTwice(server, done){
      const port = server.address().port;
      const buf = Buffer.from('GET / HTTP/1.1\r\nHost: localhost:' + port + '\r\nConnection: keep-alive\r\n\r\n');
      const client = net.connect(port);
      const datas = [];
      client
        .on('error', done)
        .on('data', data => datas.push(data))
        .on('end', () => done(null, datas));
      setImmediate(() => client.write(buf));
      setImmediate(() => client.write(buf));
      setTimeout(() => client.end(), 100);
    }

    it('should always be writable and respond to all requests', done => {
      const app = new Koa();
      let count = 0;
      app.use(ctx => {
        count++;
        ctx.body = 'request ' + count + ', writable: ' + ctx.writable;
      });

      const server = app.listen();
      requestTwice(server, (_, datas) => {
        const responses = Buffer.concat(datas).toString();
        assert.equal(/request 1, writable: true/.test(responses), true);
        assert.equal(/request 2, writable: true/.test(responses), true);
        done();
      });
    });
  });

  describe('when socket closed before response sent', () => {
    function requestClosed(server){
      const port = server.address().port;
      const buf = Buffer.from('GET / HTTP/1.1\r\nHost: localhost:' + port + '\r\nConnection: keep-alive\r\n\r\n');
      const client = net.connect(port);
      setImmediate(() => {
        client.write(buf);
        client.end();
      });
    }

    it('should not be writable', done => {
      const app = new Koa();
      app.use(ctx => {
        sleep(1000)
          .then(() => {
            if (ctx.writable) return done(new Error('ctx.writable should not be true'));
            done();
          });
      });
      const server = app.listen();
      requestClosed(server);
    });
  });

  describe('when response finished', () => {
    function request(server){
      const port = server.address().port;
      const buf = Buffer.from('GET / HTTP/1.1\r\nHost: localhost:' + port + '\r\nConnection: keep-alive\r\n\r\n');
      const client = net.connect(port);
      setImmediate(() => {
        client.write(buf);
      });
      setTimeout(() => {
        client.end();
      }, 100);
    }

    it('should not be writable', done => {
      const app = new Koa();
      app.use(ctx => {
        ctx.res.end();
        if (ctx.writable) return done(new Error('ctx.writable should not be true'));
        done();
      });
      const server = app.listen();
      request(server);
    });
  });
});

function sleep(time){
  return new Promise(resolve => setTimeout(resolve, time));
}