├── .npmrc ├── .gitignore ├── test ├── fixtures │ └── favicon.ico └── index.js ├── .travis.yml ├── .github └── dependabot.yml ├── example.js ├── History.md ├── package.json ├── Readme.md └── index.js /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | coverage 4 | -------------------------------------------------------------------------------- /test/fixtures/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koajs/favicon/HEAD/test/fixtures/favicon.ico -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | node_js: 2 | - 10 3 | - 12 4 | - 13 5 | language: node_js 6 | script: "npm run test-travis" 7 | after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 5 8 | versioning-strategy: increase-if-necessary 9 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | 2 | var koa = require('koa'); 3 | var favicon = require('./'); 4 | 5 | var app = koa(); 6 | 7 | app.use(favicon()); 8 | 9 | app.use(function *response (next){ 10 | this.body = 'Hello World'; 11 | }); 12 | 13 | app.listen(3000); 14 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | Unreleased 2 | ================= 3 | 4 | * Remove unused "mz" dependency 5 | 6 | 2.1.0 / 2020-02-27 7 | ================== 8 | 9 | * Support and test for specifying mime type of icon (#30) 10 | * bump deps 11 | 12 | 2.0.1 / 2018-03-18 13 | ================== 14 | 15 | * bump deps 16 | 17 | 1.2.0 / 2014-09-23 18 | ================== 19 | 20 | * use mz instead of co-fs 21 | 22 | 1.1.0 / 2014-05-05 23 | ================== 24 | 25 | * add favicon serving 26 | 27 | 1.0.1 / 2013-12-21 28 | ================== 29 | 30 | * update to new koa middleware signature 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-favicon", 3 | "description": "favicon bounce middleware for koa", 4 | "repository": "koajs/favicon", 5 | "version": "2.1.0", 6 | "keywords": [ 7 | "koa", 8 | "middleware", 9 | "favicon" 10 | ], 11 | "files": [ 12 | "index.js" 13 | ], 14 | "devDependencies": { 15 | "istanbul": "^0.4.5", 16 | "koa": "^2.11.0", 17 | "mocha": "^10.0.0", 18 | "supertest": "^6.2.4" 19 | }, 20 | "license": "MIT", 21 | "scripts": { 22 | "test": "NODE_ENV=test mocha --reporter spec --exit", 23 | "test-cov": "NODE_ENV=test istanbul cover ./node_modules/.bin/_mocha -- --exit", 24 | "test-travis": "NODE_ENV=test istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --exit" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # koa-favicon [![Build Status](https://travis-ci.org/koajs/favicon.svg)](https://travis-ci.org/koajs/favicon) 2 | 3 | Koa middleware for serving a favicon. Based on [serve-favicon](https://github.com/expressjs/serve-favicon). 4 | 5 | ## Installation 6 | 7 | ```js 8 | $ npm install koa-favicon 9 | ``` 10 | 11 | ## Example 12 | 13 | ```js 14 | const Koa = require('koa'); 15 | const favicon = require('koa-favicon'); 16 | const app = new Koa(); 17 | 18 | app.use(favicon(__dirname + '/public/favicon.ico')); 19 | ``` 20 | 21 | ## API 22 | 23 | ### favicon(path, [options]) 24 | 25 | Returns a middleware serving the favicon found on the given `path`. 26 | 27 | #### options 28 | 29 | - `maxAge` cache-control max-age directive in ms, defaulting to 1 day. 30 | - `mime` specify the mime-type of the favicon, defaults to "image/x-icon" 31 | 32 | ## License 33 | 34 | MIT 35 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const resolve = require('path').resolve; 8 | const fs = require('fs'); 9 | 10 | /** 11 | * Serve favicon.ico 12 | * 13 | * @param {String} path 14 | * @param {Object} [options] 15 | * @param {Number} [options.maxAge=null] 16 | * @param {String} [options.mime="image/x-icon"] MIME type of the file at path 17 | * @return {Function} 18 | * @api public 19 | */ 20 | 21 | module.exports = function (path, options){ 22 | if (!path) { 23 | return (ctx, next) => { 24 | if ('/favicon.ico' != ctx.path) { 25 | return next(); 26 | } 27 | }; 28 | } 29 | 30 | path = resolve(path); 31 | options = options || {}; 32 | 33 | let icon; 34 | const maxAge = options.maxAge == null 35 | ? 86400000 36 | : Math.min(Math.max(0, options.maxAge), 31556926000); 37 | const cacheControl = `public, max-age=${maxAge / 1000 | 0}`; 38 | const mime = options.mime || 'image/x-icon'; 39 | 40 | return (ctx, next) => { 41 | if ('/favicon.ico' != ctx.path) { 42 | return next(); 43 | } 44 | 45 | if ('GET' !== ctx.method && 'HEAD' !== ctx.method) { 46 | ctx.status = 'OPTIONS' == ctx.method ? 200 : 405; 47 | ctx.set('Allow', 'GET, HEAD, OPTIONS'); 48 | } else { 49 | // lazily read the icon 50 | if (!icon) icon = fs.readFileSync(path); 51 | ctx.set('Cache-Control', cacheControl); 52 | ctx.type = mime; 53 | ctx.body = icon; 54 | } 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const request = require('supertest'); 4 | const join = require('path').join; 5 | const assert = require('assert'); 6 | const favicon = require('..'); 7 | const Koa = require('koa'); 8 | const fs = require('fs'); 9 | 10 | describe('favicon()', function(){ 11 | const path = join(__dirname, 'fixtures', 'favicon.ico'); 12 | 13 | it('should only respond on /favicon.ico', done => { 14 | const app = new Koa(); 15 | 16 | app.use(favicon(path)); 17 | 18 | app.use((ctx, next) => { 19 | assert(!ctx.body); 20 | assert(!ctx.get('Content-Type')); 21 | ctx.body = 'hello'; 22 | }); 23 | 24 | request(app.listen()) 25 | .get('/') 26 | .expect('hello', done); 27 | }); 28 | 29 | it('should only respond on /favicon.ico if `path` is missing', done => { 30 | const app = new Koa(); 31 | 32 | app.use(favicon()); 33 | 34 | app.use((ctx, next) => { 35 | assert(!ctx.body); 36 | assert(!ctx.get('Content-Type')); 37 | 38 | ctx.body = 'hello'; 39 | }); 40 | 41 | request(app.listen()) 42 | .get('/') 43 | .expect('hello', done); 44 | }); 45 | 46 | it('should 404 if `path` is missing', done => { 47 | const app = new Koa(); 48 | app.use(favicon()); 49 | request(app.listen()) 50 | .post('/favicon.ico') 51 | .expect(404, done); 52 | }); 53 | 54 | it('should accept OPTIONS requests', done => { 55 | const app = new Koa(); 56 | app.use(favicon(path)); 57 | 58 | request(app.listen()) 59 | .options('/favicon.ico') 60 | .expect('Allow', 'GET, HEAD, OPTIONS') 61 | .expect(200, done); 62 | }); 63 | 64 | it('should not accept POST requests', done => { 65 | const app = new Koa(); 66 | app.use(favicon(path)); 67 | 68 | request(app.listen()) 69 | .post('/favicon.ico') 70 | .expect('Allow', 'GET, HEAD, OPTIONS') 71 | .expect(405, done); 72 | }); 73 | 74 | it('should send the favicon', done => { 75 | const body = fs.readFileSync(path); 76 | 77 | const app = new Koa(); 78 | app.use(favicon(path)); 79 | 80 | request(app.listen()) 81 | .get('/favicon.ico') 82 | .expect('Content-Type', 'image/x-icon') 83 | .expect(200, body, done); 84 | }); 85 | 86 | it('should send the favicon with a different mime type', done => { 87 | const body = fs.readFileSync(path); 88 | 89 | const app = new Koa(); 90 | app.use(favicon(path, {mime: 'image/png'})); 91 | 92 | request(app.listen()) 93 | .get('/favicon.ico') 94 | .expect('Content-Type', 'image/png') 95 | .expect(200, body, done); 96 | }); 97 | 98 | it('should set cache-control headers', done => { 99 | const app = new Koa(); 100 | app.use(favicon(path)); 101 | request(app.listen()) 102 | .get('/favicon.ico') 103 | .expect('Cache-Control', 'public, max-age=86400') 104 | .expect(200, done); 105 | }); 106 | 107 | describe('options.maxAge', function(){ 108 | it('should set max-age', done => { 109 | const app = new Koa(); 110 | app.use(favicon(path, { maxAge: 5000 })); 111 | request(app.listen()) 112 | .get('/favicon.ico') 113 | .expect('Cache-Control', 'public, max-age=5') 114 | .expect(200, done); 115 | }); 116 | 117 | it('should accept 0', done => { 118 | const app = new Koa(); 119 | app.use(favicon(path, { maxAge: 0 })); 120 | request(app.listen()) 121 | .get('/favicon.ico') 122 | .expect('Cache-Control', 'public, max-age=0') 123 | .expect(200, done); 124 | }); 125 | 126 | it('should be valid delta-seconds', done => { 127 | const app = new Koa(); 128 | app.use(favicon(path, { maxAge: 1234 })); 129 | request(app.listen()) 130 | .get('/favicon.ico') 131 | .expect('Cache-Control', 'public, max-age=1') 132 | .expect(200, done); 133 | }); 134 | 135 | it('should floor at 0', done => { 136 | const app = new Koa(); 137 | app.use(favicon(path, { maxAge: -4000 })); 138 | request(app.listen()) 139 | .get('/favicon.ico') 140 | .expect('Cache-Control', 'public, max-age=0') 141 | .expect(200, done); 142 | }); 143 | 144 | it('should ceil at 31556926', done => { 145 | const app = new Koa(); 146 | app.use(favicon(path, { maxAge: 900000000000 })); 147 | request(app.listen()) 148 | .get('/favicon.ico') 149 | .expect('Cache-Control', 'public, max-age=31556926') 150 | .expect(200, done); 151 | }); 152 | 153 | it('should accept Infinity', done => { 154 | const app = new Koa(); 155 | app.use(favicon(path, { maxAge: Infinity })); 156 | request(app.listen()) 157 | .get('/favicon.ico') 158 | .expect('Cache-Control', 'public, max-age=31556926') 159 | .expect(200, done); 160 | }); 161 | }) 162 | }) 163 | --------------------------------------------------------------------------------