Skip to content

Commit e0fb043

Browse files
committed
chore: Prepare for publishing
Signed-off-by: Richie Bendall <[email protected]>
1 parent 881282b commit e0fb043

File tree

12 files changed

+2174
-2871
lines changed

12 files changed

+2174
-2871
lines changed

.travis.yml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ install: yarn install --non-interactive
1212

1313
script:
1414
- yarn lint
15+
- yarn test
1516
- yarn build
1617
- if [ -n "$TRAVIS_TAG" ]; then npm version $TRAVIS_TAG; fi
1718

@@ -23,14 +24,6 @@ deploy:
2324
on:
2425
tags: true
2526

26-
- provider: releases
27-
api_key: $github_token
28-
file_glob: true
29-
file: dist/*
30-
skip_cleanup: true
31-
on:
32-
tags: true
33-
3427
- provider: pages
3528
skip_cleanup: true
3629
github_commit: "chore: Published documentation [skip ci]"

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ Convert a buffer and headers to UTF-8 text, originally from `node-fetch`.
99
From your NodeJS application:
1010

1111
```js
12-
const charsetDetection = require("fetch-charset-detection");
12+
const {
13+
convertBody,
14+
extractContentType,
15+
getTotalBytes,
16+
writeToStream
17+
} = require("fetch-charset-detection");
1318
```
1419

15-
## Usage
20+
## API
1621

17-
Read the [documentation](https://richienb.github.io/fetch-charset-detection).
22+
Refer to the [documentation](https://richienb.github.io/fetch-charset-detection).

package.json

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,46 @@
1818
"author": "Richie Bendall <[email protected]>",
1919
"license": "MIT",
2020
"scripts": {
21-
"dev": "microbundle --target node --watch",
21+
"dev": "yarn js --watch",
2222
"build": "yarn js && yarn docs",
23-
"js": "microbundle --target node --format es,cjs --external http,https,stream,zlib,cheerio,content-type,iconv-lite",
23+
"js": "tsc --outDir dist/",
2424
"docs": "typedoc --out ./docs --mode file --target ES6 --ignoreCompilerErrors ./src",
25-
"lint": "eslint src/**/*"
25+
"lint": "eslint src/**/*",
26+
"test": "ava"
2627
},
2728
"dependencies": {
2829
"cheerio": "^1.0.0-rc.3",
2930
"content-type": "^1.0.4",
30-
"iconv-lite": "^0.5.0"
31+
"iconv-lite": "^0.5.0",
32+
"lodash": "^4.17.15"
3133
},
3234
"devDependencies": {
3335
"@types/cheerio": "^0.22.13",
3436
"@types/content-type": "^1.1.3",
37+
"@types/lodash": "^4.14.144",
38+
"ava": "^2.4.0",
3539
"eslint": "^6.6.0",
3640
"eslint-config-richienb": "^0.2.2",
37-
"microbundle": "^0.11.0",
38-
"typedoc": "^0.15.0"
39-
},
40-
"resolutions": {
41-
"microbundle/rollup-plugin-typescript2/rollup-pluginutils/micromatch/braces": "^3.0.2",
42-
"microbundle/rollup-plugin-postcss/cssnano/postcss-svgo/svgo/js-yaml": "^3.13.1"
41+
"express": "^4.17.1",
42+
"fetch-blob": "^1.0.4",
43+
"form-data": "^2.5.1",
44+
"get-port": "^5.0.0",
45+
"node-fetch": "^2.6.0",
46+
"resumer": "^0.0.0",
47+
"ts-node": "^8.4.1",
48+
"typedoc": "^0.15.0",
49+
"typescript": "^3.6.4"
4350
},
4451
"eslintConfig": {
4552
"extends": "richienb/ts"
53+
},
54+
"ava": {
55+
"compileEnhancements": false,
56+
"extensions": [
57+
"ts"
58+
],
59+
"require": [
60+
"ts-node/register"
61+
]
4662
}
4763
}

src/lib/convert-body.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import getCharset from "../utils/get-charset"
22
import { decode as convert } from "iconv-lite"
33
import { load as $ } from "cheerio"
4+
import _ from "lodash"
45

56
/**
67
* Detect buffer encoding and convert to target encoding
@@ -10,27 +11,27 @@ import { load as $ } from "cheerio"
1011
* @param headers Headers provided with the request.
1112
*/
1213
export function convertBody(buffer: Buffer, headers?: Headers): string {
13-
const contentType = headers instanceof Headers ? headers.get("content-type") : null
14+
const contentType = !_.isNil(headers) ? headers.get("content-type") : null
1415
let charset: string
1516

1617
// Header
1718
if (contentType) charset = getCharset(contentType)
1819

1920
// No charset in content type, peek at response body for at most 1024 bytes
20-
const res = buffer.slice(0, 1024).toString()
21+
const res = _.toString(buffer.slice(0, 1024))
2122

2223
// HTML5, HTML4 and XML
2324
if (!charset && res) {
2425
charset = getCharset(
2526
$(res)("meta[charset]").attr("charset") || // HTML5
2627
$(res)("meta[http-equiv][content]").attr("content") || // HTML4
27-
$(res.replace(/<\?(.*)\?>/im, "<$1>"), { xmlMode: true }).root().find("xml").attr("encoding"), // XML
28+
$(_.replace(res, /<\?(.*)\?>/im, "<$1>"), { xmlMode: true }).root().find("xml").attr("encoding"), // XML
2829
)
2930
}
3031

3132
// Prevent decode issues when sites use incorrect encoding
3233
// ref: https://hsivonen.fi/encoding-menu/
33-
if (charset && charset.toLowerCase() in ["gb2312", "gbk"]) charset = "gb18030"
34+
if (charset && _.lowerCase(charset) in ["gb2312", "gbk"]) charset = "gb18030"
3435

3536
// Turn raw buffers into a single utf-8 buffer
3637
return convert(

src/lib/extract-content-type.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Stream } from "stream"
22
import { isBlob, isURLSearchParams, isArrayBuffer } from "../utils/is"
3+
import _ from "lodash"
34

45
/**
56
* Performs the operation "extract a `Content-Type` value from |object|" as
@@ -11,8 +12,11 @@ import { isBlob, isURLSearchParams, isArrayBuffer } from "../utils/is"
1112
* @param body Any options.body input
1213
*/
1314
export function extractContentType(body: any): string | null {
15+
// Body is null or undefined
16+
if (_.isNil(body)) return null
17+
1418
// Body is string
15-
if (typeof body === "string") return "text/plain;charset=UTF-8"
19+
if (_.isString(body)) return "text/plain;charset=UTF-8"
1620

1721
// Body is a URLSearchParams
1822
if (isURLSearchParams(body)) return "application/x-www-form-urlencoded;charset=UTF-8"
@@ -24,7 +28,7 @@ export function extractContentType(body: any): string | null {
2428
if (Buffer.isBuffer(body) || isArrayBuffer(body) || ArrayBuffer.isView(body)) return null
2529

2630
// Detect form data input from form-data module
27-
if (typeof body.getBoundary === "function") return `multipart/form-data;boundary=${body.getBoundary()}`
31+
if (body && _.isFunction(body.getBoundary)) return `multipart/form-data;boundary=${body.getBoundary()}`
2832

2933
// Body is stream - can't really do much about this
3034
if (body instanceof Stream) return null

src/lib/get-total-bytes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isBlob } from "../utils/is"
2+
import _ from "lodash"
23

34
/**
45
* The Fetch Standard treats this as if "total bytes" is a property on the body.
@@ -8,7 +9,7 @@ import { isBlob } from "../utils/is"
89
*
910
* @param body Body object from the Body instance.
1011
*/
11-
export function getTotalBytes(body: any): number | null {
12+
export function getTotalBytes({ body }: { body: any }): number | null {
1213
// Body is null or undefined
1314
if (body == null) return 0
1415

@@ -19,7 +20,7 @@ export function getTotalBytes(body: any): number | null {
1920
if (Buffer.isBuffer(body)) return body.length
2021

2122
// Detect form data input from form-data module
22-
if (body && typeof body.getLengthSync === "function") return body.hasKnownLength && body.hasKnownLength() ? body.getLengthSync() : null
23+
if (body && _.isFunction(body.getLengthSync)) return body.hasKnownLength && body.hasKnownLength() ? body.getLengthSync() : null
2324

2425
// Body is stream
2526
return null

src/lib/write-to-stream.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { isBlob } from "../utils/is"
77
* @param body Body object from the Body instance.
88
* @param dest The stream to write to.
99
*/
10-
export function writeToStream(body: any, dest: Writable): void {
10+
export function writeToStream(dest: Writable, { body }: { body: any }): void {
1111
// Body is null
1212
if (body == null) dest.end()
1313

src/utils/get-charset.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { parse as parseContentType } from "content-type"
2+
import _ from "lodash"
23

34
/**
45
* Get the character set from a Content-Type header.
56
* @param contentType The Content-Type HTTP header.
67
*/
78
export default function getCharSet(contentType: string): string | null {
8-
return contentType != null ? parseContentType(contentType).parameters.charset : null
9+
return !_.isNil(contentType) ? parseContentType(contentType).parameters.charset : null
910
}

src/utils/is.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import _ from "lodash"
2+
13
const NAME = Symbol.toStringTag
24

35
/**
@@ -8,14 +10,14 @@ const NAME = Symbol.toStringTag
810
*/
911
export function isURLSearchParams(obj: any): obj is URLSearchParams {
1012
return (
11-
typeof obj === "object" &&
12-
typeof obj.append === "function" &&
13-
typeof obj.delete === "function" &&
14-
typeof obj.get === "function" &&
15-
typeof obj.getAll === "function" &&
16-
typeof obj.has === "function" &&
17-
typeof obj.set === "function" &&
18-
typeof obj.sort === "function" &&
13+
!_.isNil(obj) &&
14+
_.isFunction(obj.append) &&
15+
_.isFunction(obj.delete) &&
16+
_.isFunction(obj.get) &&
17+
_.isFunction(obj.getAll) &&
18+
_.isFunction(obj.has) &&
19+
_.isFunction(obj.set) &&
20+
_.isFunction(obj.sort) &&
1921
obj[NAME] === "URLSearchParams"
2022
)
2123
}
@@ -34,11 +36,11 @@ declare interface FetchBlob extends Blob {
3436
*/
3537
export function isBlob(obj: any): obj is FetchBlob {
3638
return (
37-
typeof obj === "object" &&
38-
typeof obj.arrayBuffer === "function" &&
39-
typeof obj.type === "string" &&
40-
typeof obj.stream === "function" &&
41-
typeof obj.constructor === "function" &&
39+
!_.isNil(obj) &&
40+
_.isFunction(obj.arrayBuffer) &&
41+
_.isString(obj.type) &&
42+
_.isFunction(obj.stream) &&
43+
_.isFunction(obj.constructor) &&
4244
/^(Blob|File)$/.test(obj[NAME])
4345
)
4446
}
@@ -50,7 +52,7 @@ export function isBlob(obj: any): obj is FetchBlob {
5052
*/
5153
export function isAbortSignal(obj: any): obj is AbortSignal {
5254
return (
53-
typeof obj === "object" &&
55+
_.isObject(obj) &&
5456
obj[NAME] === "AbortSignal"
5557
)
5658
}
@@ -61,7 +63,10 @@ export function isAbortSignal(obj: any): obj is AbortSignal {
6163
* @param obj The object to check.
6264
*/
6365
export function isArrayBuffer(obj: any): obj is ArrayBuffer {
64-
return obj[NAME] === "ArrayBuffer"
66+
return (
67+
_.isObject(obj) &&
68+
obj[NAME] === "ArrayBuffer"
69+
)
6570
}
6671

6772
declare class AbortError extends Error {

test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import test, { before, after } from "ava"
2+
import resumer from "resumer"
3+
import FormData from "form-data"
4+
import * as stream from "stream"
5+
import { extractContentType, getTotalBytes } from "./src"
6+
import express from "express"
7+
import getPort from "get-port" // eslint-disable-line import/default
8+
import { Request } from "node-fetch"
9+
import Blob from "fetch-blob"
10+
const app = express()
11+
12+
before(async (t) => {
13+
t.context.port = await getPort()
14+
t.context.server = app.listen(t.context.port)
15+
t.context.baseURL = `http://localhost:${t.context.port}/`
16+
})
17+
18+
after.always((t) => t.context.server.close())
19+
20+
test("should calculate content length and extract content type for each body type", (t) => {
21+
const url = `${t.context.baseURL}hello`
22+
const bodyContent = "a=1"
23+
24+
let streamBody = resumer().queue(bodyContent).end()
25+
streamBody = streamBody.pipe(new stream.PassThrough())
26+
const streamRequest = new Request(url, {
27+
method: "POST",
28+
body: streamBody,
29+
size: 1024,
30+
})
31+
32+
const blobBody = new Blob([bodyContent], { type: "text/plain" })
33+
const blobRequest = new Request(url, {
34+
method: "POST",
35+
body: blobBody,
36+
size: 1024,
37+
})
38+
39+
const formBody = new FormData()
40+
formBody.append("a", "1")
41+
const formRequest = new Request(url, {
42+
method: "POST",
43+
body: formBody,
44+
size: 1024,
45+
})
46+
47+
const bufferBody = Buffer.from(bodyContent)
48+
const bufferRequest = new Request(url, {
49+
method: "POST",
50+
body: bufferBody,
51+
size: 1024,
52+
})
53+
54+
const stringRequest = new Request(url, {
55+
method: "POST",
56+
body: bodyContent,
57+
size: 1024,
58+
})
59+
60+
const nullRequest = new Request(url, {
61+
method: "GET",
62+
body: null,
63+
size: 1024,
64+
})
65+
66+
t.is(getTotalBytes(streamRequest), null)
67+
t.is(getTotalBytes(blobRequest), blobBody.size)
68+
t.not(getTotalBytes(formRequest), null)
69+
t.is(getTotalBytes(bufferRequest), bufferBody.length)
70+
t.is(getTotalBytes(stringRequest), bodyContent.length)
71+
t.is(getTotalBytes(nullRequest), 0)
72+
73+
t.is(extractContentType(streamBody), null)
74+
t.is(extractContentType(blobBody), "text/plain")
75+
t.true(extractContentType(formBody).startsWith("multipart/form-data"))
76+
t.is(extractContentType(bufferBody), null)
77+
t.is(extractContentType(bodyContent), "text/plain;charset=UTF-8")
78+
t.is(extractContentType(null), null)
79+
})

0 commit comments

Comments
 (0)