diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 892167f..66b63a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,15 @@ name: ci on: -- pull_request -- push + push: + branches: + - master + - '1.x' + paths-ignore: + - '*.md' + pull_request: + paths-ignore: + - '*.md' jobs: test: @@ -10,151 +17,35 @@ jobs: strategy: matrix: name: - - Node.js 0.6 - - Node.js 0.8 - - Node.js 0.10 - - Node.js 0.12 - - io.js 1.x - - io.js 2.x - - io.js 3.x - - Node.js 4.x - - Node.js 5.x - - Node.js 6.x - - Node.js 7.x - - Node.js 8.x - - Node.js 9.x - - Node.js 10.x - - Node.js 11.x - - Node.js 12.x - - Node.js 13.x - - Node.js 14.x - - Node.js 15.x - - Node.js 16.x - - Node.js 17.x - Node.js 18.x - Node.js 19.x - Node.js 20.x - Node.js 21.x + - Node.js 22.x include: - - name: Node.js 0.6 - node-version: "0.6" - npm-i: mocha@1.21.5 - npm-rm: nyc - - - name: Node.js 0.8 - node-version: "0.8" - npm-i: mocha@2.5.3 - npm-rm: nyc - - - name: Node.js 0.10 - node-version: "0.10" - npm-i: mocha@3.5.3 nyc@10.3.2 - - - name: Node.js 0.12 - node-version: "0.12" - npm-i: mocha@3.5.3 nyc@10.3.2 - - - name: io.js 1.x - node-version: "1.8" - npm-i: mocha@3.5.3 nyc@10.3.2 - - - name: io.js 2.x - node-version: "2.5" - npm-i: mocha@3.5.3 nyc@10.3.2 - - - name: io.js 3.x - node-version: "3.3" - npm-i: mocha@3.5.3 nyc@10.3.2 - - - name: Node.js 4.x - node-version: "4.9" - npm-i: mocha@5.2.0 nyc@11.9.0 - - - name: Node.js 5.x - node-version: "5.12" - npm-i: mocha@5.2.0 nyc@11.9.0 - - - name: Node.js 6.x - node-version: "6.17" - npm-i: mocha@6.2.2 nyc@14.1.1 - - - name: Node.js 7.x - node-version: "7.10" - npm-i: mocha@6.2.2 nyc@14.1.1 - - - name: Node.js 8.x - node-version: "8.17" - npm-i: mocha@7.1.2 nyc@14.1.1 - - - name: Node.js 9.x - node-version: "9.11" - npm-i: mocha@7.1.2 nyc@14.1.1 - - - name: Node.js 10.x - node-version: "10.24" - npm-i: mocha@8.4.0 - - - name: Node.js 11.x - node-version: "11.15" - npm-i: mocha@8.4.0 - - - name: Node.js 12.x - node-version: "12.22" - - - name: Node.js 13.x - node-version: "13.14" - - - name: Node.js 14.x - node-version: "14.18" - - - name: Node.js 15.x - node-version: "15.14" - - - name: Node.js 16.x - node-version: "16.13" - - - name: Node.js 17.x - node-version: "17.3" - - name: Node.js 18.x - node-version: "18.18" + node-version: "18" - name: Node.js 19.x - node-version: "19.9" + node-version: "19" - name: Node.js 20.x - node-version: "20.9" + node-version: "20" - name: Node.js 21.x - node-version: "21.1" + node-version: "21" + + - name: Node.js 22.x + node-version: "22" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Node.js ${{ matrix.node-version }} shell: bash -eo pipefail -l {0} run: | - if [[ "${{ matrix.node-version }}" == 0.6* ]]; then - sudo sh -c 'echo "deb http://us.archive.ubuntu.com/ubuntu/ bionic universe" >> /etc/apt/sources.list' - sudo sh -c 'echo "deb http://security.ubuntu.com/ubuntu bionic-security main" >> /etc/apt/sources.list' - sudo apt-get update - sudo apt-get install g++-4.8 gcc-4.8 libssl1.0-dev python2 python-is-python2 - export CC=/usr/bin/gcc-4.8 - export CXX=/usr/bin/g++-4.8 - fi nvm install --default ${{ matrix.node-version }} - if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then - nvm install --alias=npm 0.10 - nvm use ${{ matrix.node-version }} - if [[ "$(npm -v)" == 1.1.* ]]; then - nvm exec npm npm install -g npm@1.1 - ln -fs "$(which npm)" "$(dirname "$(nvm which npm)")/npm" - else - sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")" - fi - npm config set strict-ssl false - fi dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" - name: Configure npm @@ -165,26 +56,6 @@ jobs: npm config set shrinkwrap false fi - - name: Remove npm module(s) ${{ matrix.npm-rm }} - run: npm rm --silent --save-dev ${{ matrix.npm-rm }} - if: matrix.npm-rm != '' - - - name: Install npm module(s) ${{ matrix.npm-i }} - run: npm install --save-dev ${{ matrix.npm-i }} - if: matrix.npm-i != '' - - - name: Setup Node.js version-specific dependencies - shell: bash - run: | - # eslint for linting - # - remove on Node.js < 10 - if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then - node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ - grep -E '^eslint(-|$)' | \ - sort -r | \ - xargs -n1 npm rm --silent --save-dev - fi - - name: Install Node.js dependencies run: npm install diff --git a/HISTORY.md b/HISTORY.md index a9a5449..63d537d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,9 @@ +1.0.0 / 2024-08-31 +================== + + * Drop support for node <18 + * Added an option preferred encodings array #59 + 0.6.3 / 2022-01-22 ================== diff --git a/README.md b/README.md index 82915e5..6fb7f2d 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,10 @@ Returns the most preferred encoding from the client. Returns the most preferred encoding from a list of available encodings. +##### encoding(availableEncodings, { preferred }) + +Returns the most preferred encoding from a list of available encodings, while prioritizing based on `preferred` array between same-quality encodings. + ##### encodings() Returns an array of preferred encodings ordered by the client preference. @@ -181,6 +185,11 @@ Returns an array of preferred encodings ordered by the client preference. Returns an array of preferred encodings ordered by priority from a list of available encodings. +##### encodings(availableEncodings, { preferred }) + +Returns an array of preferred encodings ordered by priority from a list of +available encodings, while prioritizing based on `preferred` array between same-quality encodings. + ## See Also The [accepts](https://npmjs.org/package/accepts#readme) module builds on diff --git a/index.js b/index.js index 4788264..4f51315 100644 --- a/index.js +++ b/index.js @@ -44,13 +44,14 @@ Negotiator.prototype.charsets = function charsets(available) { return preferredCharsets(this.request.headers['accept-charset'], available); }; -Negotiator.prototype.encoding = function encoding(available) { - var set = this.encodings(available); +Negotiator.prototype.encoding = function encoding(available, opts) { + var set = this.encodings(available, opts); return set && set[0]; }; -Negotiator.prototype.encodings = function encodings(available) { - return preferredEncodings(this.request.headers['accept-encoding'], available); +Negotiator.prototype.encodings = function encodings(available, options) { + var opts = options || {}; + return preferredEncodings(this.request.headers['accept-encoding'], available, opts.preferred); }; Negotiator.prototype.language = function language(available) { diff --git a/lib/encoding.js b/lib/encoding.js index 8432cd7..9ebb633 100644 --- a/lib/encoding.js +++ b/lib/encoding.js @@ -96,7 +96,7 @@ function parseEncoding(str, i) { */ function getEncodingPriority(encoding, accepted, index) { - var priority = {o: -1, q: 0, s: 0}; + var priority = {encoding: encoding, o: -1, q: 0, s: 0}; for (var i = 0; i < accepted.length; i++) { var spec = specify(encoding, accepted[i], index); @@ -123,6 +123,7 @@ function specify(encoding, spec, index) { } return { + encoding: encoding, i: index, o: spec.i, q: spec.q, @@ -135,14 +136,34 @@ function specify(encoding, spec, index) { * @public */ -function preferredEncodings(accept, provided) { +function preferredEncodings(accept, provided, preferred) { var accepts = parseAcceptEncoding(accept || ''); + var comparator = preferred ? function comparator (a, b) { + if (a.q !== b.q) { + return b.q - a.q // higher quality first + } + + var aPreferred = preferred.indexOf(a.encoding) + var bPreferred = preferred.indexOf(b.encoding) + + if (aPreferred === -1 && bPreferred === -1) { + // consider the original specifity/order + return (b.s - a.s) || (a.o - b.o) || (a.i - b.i) + } + + if (aPreferred !== -1 && bPreferred !== -1) { + return aPreferred - bPreferred // consider the preferred order + } + + return aPreferred === -1 ? 1 : -1 // preferred first + } : compareSpecs; + if (!provided) { // sorted list of all encodings return accepts .filter(isQuality) - .sort(compareSpecs) + .sort(comparator) .map(getFullEncoding); } @@ -151,7 +172,7 @@ function preferredEncodings(accept, provided) { }); // sorted list of accepted encodings - return priorities.filter(isQuality).sort(compareSpecs).map(function getEncoding(priority) { + return priorities.filter(isQuality).sort(comparator).map(function getEncoding(priority) { return provided[priorities.indexOf(priority)]; }); } @@ -162,7 +183,7 @@ function preferredEncodings(accept, provided) { */ function compareSpecs(a, b) { - return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; + return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i); } /** diff --git a/lib/mediaType.js b/lib/mediaType.js index 67309dd..8e402ea 100644 --- a/lib/mediaType.js +++ b/lib/mediaType.js @@ -69,7 +69,7 @@ function parseMediaType(str, i) { // get the value, unwrapping quotes var value = val && val[0] === '"' && val[val.length - 1] === '"' - ? val.substr(1, val.length - 2) + ? val.slice(1, -1) : val; if (key === 'q') { @@ -238,8 +238,8 @@ function splitKeyValuePair(str) { if (index === -1) { key = str; } else { - key = str.substr(0, index); - val = str.substr(index + 1); + key = str.slice(0, index); + val = str.slice(index + 1); } return [key, val]; diff --git a/package.json b/package.json index 297635f..e4bdc1e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "negotiator", "description": "HTTP content negotiation", - "version": "0.6.3", + "version": "1.0.0", "contributors": [ "Douglas Christopher Wilson ", "Federico Romero ", @@ -36,6 +36,7 @@ "scripts": { "lint": "eslint .", "test": "mocha --reporter spec --check-leaks --bail test/", + "test:debug": "mocha --reporter spec --check-leaks --inspect --inspect-brk test/", "test-ci": "nyc --reporter=lcov --reporter=text npm test", "test-cov": "nyc --reporter=html --reporter=text npm test" } diff --git a/test/encoding.js b/test/encoding.js index d3da621..29801d4 100644 --- a/test/encoding.js +++ b/test/encoding.js @@ -190,6 +190,14 @@ describe('negotiator.encoding(array)', function () { it('should return first client-preferred encoding', function () { assert.strictEqual(this.negotiator.encoding(['deflate', 'compress']), 'deflate') }) + + it('should return developer-preferred encodings', function () { + assert.strictEqual(this.negotiator.encoding(['gzip', 'deflate'], { preferred: ['deflate'] }), 'deflate') + assert.strictEqual(this.negotiator.encoding(['deflate', 'gzip'], { preferred: ['deflate'] }), 'deflate') + assert.strictEqual(this.negotiator.encoding(['gzip', 'deflate'], { preferred: ['gzip'] }), 'gzip') + assert.strictEqual(this.negotiator.encoding(['deflate', 'gzip'], { preferred: ['gzip'] }), 'gzip') + assert.strictEqual(this.negotiator.encoding(['gzip'], { preferred: ['gzip'] }), 'gzip') + }) }) whenAcceptEncoding('gzip;q=0.8, deflate', function () { @@ -205,6 +213,12 @@ describe('negotiator.encoding(array)', function () { assert.strictEqual(this.negotiator.encoding(['gzip']), 'gzip') assert.strictEqual(this.negotiator.encoding(['compress', 'identity']), 'identity') }) + + it('should return developer-preferred encodings', function () { + assert.strictEqual(this.negotiator.encoding(['gzip', 'deflate'], { preferred: ['gzip'] }), 'gzip') + assert.strictEqual(this.negotiator.encoding(['deflate', 'gzip'], { preferred: ['gzip'] }), 'gzip') + assert.strictEqual(this.negotiator.encoding(['gzip'], { preferred: ['gzip'] }), 'gzip') + }) }) }) @@ -401,6 +415,14 @@ describe('negotiator.encodings(array)', function () { assert.deepEqual(this.negotiator.encodings(['deflate', 'gzip']), ['gzip', 'deflate']) assert.deepEqual(this.negotiator.encodings(['identity']), ['identity']) }) + + it('should return developer-preferred encodings', function () { + assert.deepEqual(this.negotiator.encodings(['gzip', 'deflate'], { preferred: ['deflate'] }), ['deflate', 'gzip']) + assert.deepEqual(this.negotiator.encodings(['deflate', 'gzip'], { preferred: ['deflate'] }), ['deflate', 'gzip']) + assert.deepEqual(this.negotiator.encodings(['gzip', 'deflate'], { preferred: ['gzip'] }), ['gzip', 'deflate']) + assert.deepEqual(this.negotiator.encodings(['deflate', 'gzip'], { preferred: ['gzip'] }), ['gzip', 'deflate']) + assert.deepEqual(this.negotiator.encodings(['gzip'], { preferred: ['gzip'] }), ['gzip']) + }) }) whenAcceptEncoding('gzip;q=0.8, deflate', function () { @@ -416,6 +438,12 @@ describe('negotiator.encodings(array)', function () { assert.deepEqual(this.negotiator.encodings(['gzip']), ['gzip']) assert.deepEqual(this.negotiator.encodings(['identity', 'gzip', 'compress']), ['gzip', 'identity', 'compress']) }) + + it('should return developer-preferred encodings', function () { + assert.deepEqual(this.negotiator.encodings(['gzip', 'deflate'], { preferred: ['gzip'] }), ['gzip', 'deflate']) + assert.deepEqual(this.negotiator.encodings(['deflate', 'gzip'], { preferred: ['gzip'] }), ['gzip', 'deflate']) + assert.deepEqual(this.negotiator.encodings(['gzip'], { preferred: ['gzip'] }), ['gzip']) + }) }) })