diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1923d41 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintrc b/.eslintrc.json similarity index 95% rename from .eslintrc rename to .eslintrc.json index 69f9f14..97a5088 100644 --- a/.eslintrc +++ b/.eslintrc.json @@ -1,64 +1,63 @@ -{ - "env": { - "es6": true, - "amd": true, - "node": true, - "mongo": true, - "jquery": true, - "browser": true, - "commonjs": true - }, - "parserOptions": { - "ecmaVersion": 9, - "sourceType": "module", - "ecmaFeatures": { - "jsx": true, - "forOf": true, - "spread": true, - "modules": true, - "classes": true, - "generators": true, - "restParams": true, - "regexUFlag": true, - "regexYFlag": true, - "globalReturn": true, - "destructuring": true, - "impliedStrict": true, - "blockBindings": true, - "defaultParams": true, - "octalLiterals": true, - "arrowFunctions": true, - "binaryLiterals": true, - "templateStrings": true, - "superInFunctions": true, - "unicodeCodePointEscapes": true, - "objectLiteralShorthandMethods": true, - "objectLiteralComputedProperties": true, - "objectLiteralDuplicateProperties": true, - "objectLiteralShorthandProperties": true - } - }, - "plugins": [], - "rules": { - "semi": "warn", - "indent": [ 0, 2 ], - "strict": "off", - "eqeqeq": "error", - "no-var": "warn", - "no-undef": "warn", - "valid-jsdoc": "warn", - "comma-dangle": "warn", - "no-dupe-args": "warn", - "no-dupe-keys": "warn", - "require-await": "warn", - "spaced-comment": "error", - "space-in-parens": "error", - "no-global-assign": "warn", - "no-duplicate-imports": "error", - "no-dupe-class-members": "error" - }, - "globals": { - "_config": false, - "console": false - } -} \ No newline at end of file +{ + "env": { + "es6": true, + "amd": true, + "node": true, + "mongo": true, + "jquery": true, + "browser": true, + "commonjs": true + }, + "parserOptions": { + "ecmaVersion": 9, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + "forOf": true, + "spread": true, + "modules": true, + "classes": true, + "generators": true, + "restParams": true, + "regexUFlag": true, + "regexYFlag": true, + "globalReturn": true, + "destructuring": true, + "impliedStrict": true, + "blockBindings": true, + "defaultParams": true, + "octalLiterals": true, + "arrowFunctions": true, + "binaryLiterals": true, + "templateStrings": true, + "superInFunctions": true, + "unicodeCodePointEscapes": true, + "objectLiteralShorthandMethods": true, + "objectLiteralComputedProperties": true, + "objectLiteralDuplicateProperties": true, + "objectLiteralShorthandProperties": true + } + }, + "plugins": [], + "rules": { + "semi": "warn", + "indent": [ 0, 2 ], + "strict": "off", + "eqeqeq": "error", + "no-var": "warn", + "no-undef": "warn", + "valid-jsdoc": "warn", + "comma-dangle": "warn", + "no-dupe-args": "warn", + "no-dupe-keys": "warn", + "require-await": "warn", + "space-in-parens": "error", + "no-global-assign": "warn", + "no-duplicate-imports": "error", + "no-dupe-class-members": "error" + }, + "globals": { + "_config": false, + "console": false + } +} diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 62ebd32..29f763e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,14 +1,14 @@ -# Contributing - -**The issue tracker is only for bug reports and enhancement suggestions. If you have a question, please ask it in the [Discord](https://discord.gg/SV7DAE9) instead of opening an issue – you will get redirected there anyway.** - -If you wish to contribute to Wumpfetch, feel free to fork the repository and submit a pull request. -[ESLint](https://eslint.org/) is used to correct most typo's you make, so it would be helpful if you added [ESLint](https://eslint.org/) to your editor of choice) - -## Setup -To get ready to work on the codebase, please do the following: - -1. Fork & clone the repository, and make sure you're on the **master** branch -2. Run `yarn --dev` or `npm install --dev` -4. Code your heart out and test using `yarn test` or `npm run test`! +# Contributing + +**The issue tracker is only for bug reports and enhancement suggestions. If you have a question, please ask it in the [Discord](https://discord.gg/SV7DAE9) instead of opening an issue – you will get redirected there anyway.** + +If you wish to contribute to Wumpfetch, feel free to fork the repository and submit a pull request. +[ESLint](https://eslint.org/) is used to correct most typo's you make, so it would be helpful if you added [ESLint](https://eslint.org/) to your editor of choice) + +## Setup +To get ready to work on the codebase, please do the following: + +1. Fork & clone the repository, and make sure you're on the **master** branch +2. Run `yarn --dev` or `npm install --dev` +4. Code your heart out and test using `yarn test` or `npm run test`! 6. [Submit a pull request](https://github.com/PassTheWessel/wumpfetch/compare) \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index a51e87b..d4e9eba 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -patreon: wessel -ko_fi: wessel +patreon: wessel +ko_fi: wessel diff --git a/.github/ISSUE_TEMPLATE/add-your-project.md b/.github/ISSUE_TEMPLATE/add-your-project.md index c9157d9..f8c0730 100644 --- a/.github/ISSUE_TEMPLATE/add-your-project.md +++ b/.github/ISSUE_TEMPLATE/add-your-project.md @@ -1,18 +1,18 @@ ---- -name: Add your project -about: Add your project to the "projects using wumpfetch" list! -title: "[PROJECT] My awesome project!" -labels: enhancement -assignees: PassTheWessel - ---- - - - -| Project Name | Short description | Links -|----------|----------|:-------------: -| | | [🔩](https://npmjs.com/package/) [⚖](https://packagephobia.now.sh/result?p=) [📂](https://github.com/DiscordBoats/boats.js) [👾](https:///?referrer=wumpfetch) +--- +name: Add your project +about: Add your project to the "projects using wumpfetch" list! +title: "[PROJECT] My awesome project!" +labels: enhancement +assignees: PassTheWessel + +--- + + + +| Project Name | Short description | Links +|----------|----------|:-------------: +| | | [🔩](https://npmjs.com/package/) [⚖](https://packagephobia.now.sh/result?p=) [📂](https://github.com/:username/:repo) [👾](https:///?referrer=wumpfetch) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5fa8c4f..962c045 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,26 +1,26 @@ ---- -name: Bug Report -about: Report an issue with wumpfetch -title: "[BUG] Somenthing broke" -labels: bug -assignees: PassTheWessel - ---- - -**Please describe the problem you are having in as much detail as possible:** - - -**Include a reproducible code sample here, if possible:** -```js -// Place your code here -``` - -**Further details:** -- Node.js version: -- Operating system: - - - -- [ ] I have also tested the issue on latest master, commit hash: +--- +name: Bug Report +about: Report an issue with wumpfetch +title: "[BUG] Somenthing broke" +labels: bug +assignees: PassTheWessel + +--- + +**Please describe the problem you are having in as much detail as possible:** + + +**Include a reproducible code sample here, if possible:** +```js +// Place your code here +``` + +**Further details:** +- Node.js version: +- Operating system: + + + +- [ ] I have also tested the issue on latest master, commit hash: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c027ba6..9aabfea 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,20 +1,20 @@ ---- -name: Feature request -about: Request a feature for wumpfetch -title: "[ENHANCEMENT] This is an amazing new idea!" -labels: enhancement -assignees: PassTheWessel - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the ideal solution** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. +--- +name: Feature request +about: Request a feature for wumpfetch +title: "[ENHANCEMENT] This is an amazing new idea!" +labels: enhancement +assignees: PassTheWessel + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the ideal solution** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1f65093..3f3078b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,10 +1,10 @@ -**Please describe the changes this PR makes and why it should be merged:** - - -**Status** -- [ ] Code changes have been tested and there aren't any typos in it - -**Semantic versioning classification:** -- [ ] This PR changes wumpfetch's core codebase (methods or parameters added) - - [ ] This PR includes breaking changes (methods removed or renamed, parameters moved or removed) -- [ ] This PR **only** includes non-code changes, like changes to documentation, README, etc. \ No newline at end of file +**Please describe the changes this PR makes and why it should be merged:** + + +**Status** +- [ ] Code changes have been tested and there aren't any typos in it + +**Semantic versioning classification:** +- [ ] This PR changes wumpfetch's core codebase (methods or parameters added) + - [ ] This PR includes breaking changes (methods removed or renamed, parameters moved or removed) +- [ ] This PR **only** includes non-code changes, like changes to documentation, README, etc. diff --git a/.gitignore b/.gitignore index 167ab9f..975ba35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -node_modules/ +node_modules/ yarn.lock \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index b4d9fb5..0000000 --- a/.npmignore +++ /dev/null @@ -1,9 +0,0 @@ -dist/ -__test__/ -examples/ -node_modules/ - -yarn.lock -index.d.ts -.gitignore -gulpfile.js \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 2197832..8091b51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,3 @@ -language: node_js -node_js: - - "node" +language: node_js +node_js: + - "node" diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9f2aa75 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,159 @@ +/* + / General vscode settings for all my projects + / Made by Wessel "wesselgame" T (https://github.com/PassTheWessel) + + ? Extensions: rainglow, eslint, material icon theme, gitlens, fish-vscode, Docker, EditorConfig for VS Code + ? Popping and Locking theme, Sass, stylelint, SVG viewer, Trailing spaces, Auto renaming tags, Better Comments, + ? Regex previewer + + * Color themes: Banner (rainglow), Hyrule (rainglow), Azure (rainglow), Github (rainglow), + * Heroku (rainglow), Popping and Locking + + * Comment legend: + / Info + * Note + ? Question + ! Somenthing's wrong + > Changed + + Added + - Removed + todo TODO +*/ +{ + // ? Window - the window options + "window.zoomLevel": 0, + // ? Editor - the text editor config + "editor.fontSize": 13, + "editor.fontWeight": "200", + "editor.fontFamily": "'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier','monospace','Operator Mono SSm Lig','OperatorMonoSSmLig-Book'", + "editor.lineHeight": 20, + "editor.cursorStyle": "line", + "editor.lineNumbers": "relative", + "editor.cursorWidth": 0, + "editor.fontLigatures": true, + "editor.cursorBlinking": "blink", + "editor.minimap.enabled": true, + "editor.smoothScrolling": true, + "editor.multiCursorModifier": "ctrlCmd", + "editor.minimap.renderCharacters": true, + "editor.tabSize": 2, + "editor.autoIndent": true, + "editor.insertSpaces": true, + "editor.tabCompletion": "on", + "editor.formatOnPaste": true, + "editor.detectIndentation": false, + "editor.wordWrap": "off", + "editor.matchBrackets": true, + "editor.renderWhitespace": "none", + "editor.autoClosingBrackets": "always", + "editor.tokenColorCustomizations": { + "types": "#C59F61", + "strings": "#F6EB90", + "numbers": "#C54121", + "keywords": "#C59F49", + "comments": "#6a737d", + "variables": "#C55F45", + "functions": "#C59F55" + }, + // ? Workbench - the general working area + "workbench.iconTheme": "material-icon-theme", + "workbench.colorTheme": "Darkside (rainglow)", + "workbench.editor.showTabs": true, + "workbench.statusBar.visible": true, + "workbench.editor.tabSizing": "fit", + "workbench.sideBar.location": "left", + // ? Debug - personal prefrences for debugging + "debug.toolBarLocation": "floating", + "debug.allowBreakpointsEverywhere": true, + // ? Console - self explanitory + "terminal.integrated.shell.windows": "C:\\Windows\\System32\\bash.exe", // C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe + // ? Files - exclude extensions/files and turn autosave on/off + "files.autoSave": "off", + "files.exclude": { + "**/.git": false, + "**/.svn": false, + "**/.hg": false, + "**/CVS": false, + "**/.DS_Store": false + }, + // ? Breadcrumbs - the bar at the top with the current function path + "breadcrumbs.enabled": true, + "breadcrumbs.filePath": "last", + "breadcrumbs.symbolPath": "on", + "breadcrumbs.symbolSortOrder": "name", + // ? Git (version management) + "git.enableSmartCommit": true, + "git.ignoreLimitWarning": true, + // ? ESlint - linter to keep my code clean + "eslint.enable": true, + "eslint.packageManager": "yarn", + // ? Languages - language-specific settings + "[yaml]": { + "editor.tabSize": 2, + "editor.autoIndent": false, + "editor.insertSpaces": true + }, + "[markdown]": { + "editor.wordWrap": "on", + "editor.quickSuggestions": false + }, + // ? Live share - coding in or with friends + "liveshare.featureSet": "insiders", + // ? Better comments - Colorizes comments + "better-comments.tags": [ + { + "tag": "/", + "color": "#008080", + "strikethrough": false, + "backgroundColor": "transparent" + }, + { + "tag": "+", + "color": "#00ff00", + "strikethrough": false, + "backgroundColor": "transparent" + }, + { + "tag": ">", + "color": "#FF00FF", + "strikethrough": false, + "backgroundColor": "transparent" + }, + { + "tag": "-", + "color": "#ff0000", + "strikethrough": false, + "backgroundColor": "transparent" + }, + { + "tag": "!", + "color": "#FF2D00", + "strikethrough": false, + "backgroundColor": "transparent" + }, + { + "tag": "?", + "color": "#3498DB", + "strikethrough": false, + "backgroundColor": "transparent" + }, + { + "tag": "//", + "color": "#474747", + "strikethrough": true, + "backgroundColor": "transparent" + }, + { + "tag": "todo", + "color": "#FF8C00", + "strikethrough": false, + "backgroundColor": "transparent" + }, + { + "tag": "*", + "color": "#98C379", + "strikethrough": false, + "backgroundColor": "transparent" + } + ] +} diff --git a/.vscode/snippets.code-snippets b/.vscode/snippets.code-snippets new file mode 100644 index 0000000..84e0db8 --- /dev/null +++ b/.vscode/snippets.code-snippets @@ -0,0 +1,18 @@ +{ + // Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 76dfd82..38a773f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) 2019-present Wessel "wesselgame" T - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The MIT License (MIT) + +Copyright (c) 2019 Wessel "wesselgame" T + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 8abe36e..49f26d8 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,35 @@ - - - -> Wumpfetch - A fast and easy to use HTTP client - -[![MIT License](https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square)](/LICENSE) [![Travis Build Status](https://img.shields.io/travis/com/PassTheWessel/wumpfetch.svg?style=flat-square)](https://travis-ci.com/PassTheWessel/wumpfetch) - - -Wumpfetch is a fast, lightweight and easy to use HTTP client for Node.JS. - -> [`GitHub`](https://github.com/PassTheWessel/wumpfetch) **|** [`NPM`](https://npmjs.com/package/wumpfetch) - -
- -## Installing -```sh -$ yarn add wumpfetch # Install w/ Yarn -$ npm i wumpfetch # Install w/ NPM -``` - -## Benchmarking -> **URL used for benchmarks**: [https://httpbin.org/get](https://httpbin.org/get) - -| Library | 1 Request | 10 Requests | 100 Requests | -|------------|-----------|-------------|--------------| -| Wumpfetch | 429.571ms | 4135.043ms | 42182.140ms | -| got | 420.319ms | 4163.749ms | 43634.187ms | -| axios | 437.274ms | 4168.437ms | 47437.898ms | -| node-fetch | 543.618ms | 4217.365ms | 43813.187ms | - -## Documentation -Documentation can be found at [https://github.com/PassTheWessel/wumpfetch/wiki](https://github.com/PassTheWessel/wumpfetch/wiki) - -## License -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FPassTheWessel%2Fwumpfetch.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FPassTheWessel%2Fwumpfetch?ref=badge_large) + + + +> Wumpfetch - A fast and easy to use HTTP client + +[![MIT License](https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square)](/LICENSE) [![Travis Build Status](https://img.shields.io/travis/com/PassTheWessel/wumpfetch.svg?style=flat-square)](https://travis-ci.com/PassTheWessel/wumpfetch) + + +Wumpfetch is a fast, lightweight and easy to use HTTP client for Node.JS. + +> [`GitHub`](https://github.com/PassTheWessel/wumpfetch) **|** [`NPM`](https://npmjs.com/package/wumpfetch) + +
+ +## Installing +```sh +$ yarn add wumpfetch # Install w/ Yarn +$ npm i wumpfetch # Install w/ NPM +``` + +## Benchmarking +> **URL used for benchmarks**: [https://httpbin.org/get](https://httpbin.org/get) + +| Library | 1 Request | 10 Requests | 100 Requests | +|------------|-----------|-------------|--------------| +| Wumpfetch | 429.571ms | 4135.043ms | 42182.140ms | +| got | 420.319ms | 4163.749ms | 43634.187ms | +| axios | 437.274ms | 4168.437ms | 47437.898ms | +| node-fetch | 543.618ms | 4217.365ms | 43813.187ms | + +## Documentation +Documentation can be found at [https://github.com/PassTheWessel/wumpfetch/wiki](https://github.com/PassTheWessel/wumpfetch/wiki) + +## License +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FPassTheWessel%2Fwumpfetch.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FPassTheWessel%2Fwumpfetch?ref=badge_large) diff --git a/__test__/wumpfetch-tests.ts b/__test__/wumpfetch-tests.ts deleted file mode 100644 index 667ded2..0000000 --- a/__test__/wumpfetch-tests.ts +++ /dev/null @@ -1,14 +0,0 @@ -import w from '../index'; - -interface Yeetus -{ - yes: boolean; -} - -(async() => { - const res = await w('https://myurl.org', { method: 'GET' }).send(); - const res1 = await w('https://myurl.org', { method: 'GET', chaining: false }); - - const d = res.json(); - const d1 = res.json(); -})(); diff --git a/dist/wumpfetch.concat.js b/dist/wumpfetch.concat.js deleted file mode 100644 index 276530c..0000000 --- a/dist/wumpfetch.concat.js +++ /dev/null @@ -1,219 +0,0 @@ -const http = require('http'); -const https = require('https'); -const { URL } = require('url'); -const { join } = require('path'); -const { stringify } = require('querystring'); -const { createGunzip, createInflate } = require('zlib'); - -const c = [ 'gzip', 'deflate' ]; -const common = [ 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH' ]; -const { version } = require('../package.json'); - -const WumpResponse = class WumpResponse { - constructor (res) { - this.body = Buffer.alloc(0); - this.coreRes = res; - - this.headers = res.headers; - this.statusCode = res.statusCode; - } - - _addChunk (chunk) { - this.body = Buffer.concat([ this.body, chunk ]); - } - - text () { - return this.body.toString(); - } - - json () { - return JSON.parse(this.body); - } -}; - -const WumpRequest = class WumpRequest { - constructor (url = {}, method = {}) { - const o = (typeof url === 'string' ? method : url); - const obj = (typeof o === 'object'); - - if (typeof url !== 'string' && !o.hasOwnProperty('url')) { - throw new Error('Missing url parameter'); - } - - this.o = { - 'm': typeof o === 'string' ? o : obj && o.method ? o.method : 'GET', - 'url': typeof url === 'string' ? new URL(url) : obj && typeof o.url === 'string' ? new URL(o.url) : url, - 'SDA': obj && typeof o.sendDataAs === 'string' ? o.sendDataAs : obj && o.data && typeof o.data === 'object' ? 'json' : undefined, - 'data': obj && o.data ? o.data : obj && o.json ? o.json : obj && o.form ? stringify(o.form) : undefined, - 'parse': obj && o.parse ? o.parse : undefined, - 'follow': !!(obj && o.followRedirects), - 'rHeaders': obj && typeof o.headers === 'object' ? o.headers : {}, - 'streamed': !!(obj && o.streamed), - 'compressed': !!(obj && o.compressed), - 'timeoutTime': obj && typeof o.timeout === 'number' ? o.timeout : null, - 'coreOptions': obj && typeof o.coreOptions === 'object' ? o.coreOptions : {} - }; - - if (typeof o.core === 'object') { - Object.keys(o.core).forEach((v) => this.option(v, o.core[v])); - } - - return this; - } - - query (a, b) { - if (typeof a === 'object') Object.keys(a).forEach((v) => this.o.url.searchParams.append(v, a[v])); - else this.o.url.searchParams.append(a, b); - - return this; - } - - body (data, SA) { - this.o.SDA = typeof data === 'object' && !SA && !Buffer.isBuffer(data) ? 'json' : (SA ? SA.toLowerCase() : 'buffer'); - this.o.data = this.SDA === 'form' ? stringify(data) : (this.SDA === 'json' ? JSON.stringify(data) : data); - - return this; - } - - header (a, b) { - if (typeof a === 'object') Object.keys(a).forEach((v) => this.o.rHeaders[v.toLowerCase()] = a[v]); - else this.o.rHeaders[a.toLowerCase()] = b; - - return this; - } - - compress () { - this.compressed = true; - if (!this.o.rHeaders['accept-encoding']) this.o.rHeaders['accept-encoding'] = c.join(', '); - - return this; - } - - path (p) { - this.o.url.pathname = join(this.o.url.pathname, p); - - return this; - } - - stream () { - this.o.streamed = true; - - return this; - } - - option (n, v) { - this.o.coreOptions[n] = v; - - return this; - } - timeout (timeout) { - this.o.timeoutTime = timeout; - - return this; - } - - send () { - return new Promise((resolve, reject) => { - if (this.o.data) { - if (!this.o.rHeaders.hasOwnProperty('user-agent')) { - this.o.rHeaders['User-Agent'] = w.userAgent; - } - - if (this.o.SDA === 'json' && !this.o.rHeaders.hasOwnProperty('content-type')) this.o.rHeaders['Content-Type'] = 'application/json'; - if (this.o.SDA === 'form') { - if (!this.o.rHeaders.hasOwnProperty('content-type')) this.o.rHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; - if (!this.o.rHeaders.hasOwnProperty('content-length')) this.o.rHeaders['Content-Length'] = Buffer.byteLength(this.o.data); - } - } - - let req; - const options = { - 'host': this.o.url.hostname, - 'port': this.o.url.port, - 'path': this.o.url.pathname + this.o.url.search, - 'method': this.o.m, - 'headers': this.o.rHeaders, - 'protocol': this.o.url.protocol, - ...this.o.coreOptions - } - - const handler = async (res) => { - let stream = res; - - if (this.o.compressed) { - if (res.headers['content-encoding'] === 'gzip') stream = res.pipe(createGunzip()); - else if (res.headers['content-encoding'] === 'deflate') stream = res.pipe(createInflate()); - } - - let wumpRes; - - if (this.o.streamed) resolve(stream); - else { - wumpRes = new WumpResponse(res); - - if (res.headers.hasOwnProperty('location') && this.o.follow) { - this.o.url = (new URL(res.headers['location'], this.o.url)).toString(); - return await module.exports(this.o); - } - - stream.on('error', (e) => reject(e)); - stream.on('data', (c) => wumpRes._addChunk(c)); - stream.on('end', () => { - if (this.o.parse) { - if (this.o.parse === 'json') wumpRes.body = JSON.parse(wumpRes.body); - else if (this.o.parse === 'text') wumpRes.body = wumpRes.body.toString(); - else wumpRes.body = wumpRes.body; - } - - resolve(wumpRes); - }); - } - }; - - if (this.o.url.protocol === 'http:') req = http.request(options, handler); - else if (this.o.url.protocol === 'https:') req = https.request(options, handler); - else throw new Error(`Bad URL protocol: ${this.o.url.protocol}`); - - - if (this.o.timeoutTime) { - req.setTimeout(this.o.timeoutTime, () => { - req.abort(); - if (!this.o.streamed) reject(new Error('Timeout reached')); - }); - } - - req.on('error', (e) => reject(e)); - - if (this.o.data) { - if (this.o.SDA === 'json') { - req.write(JSON.stringify(this.o.data)); - } else { - if (typeof this.o.data === 'object') { - req.write(JSON.stringify(this.o.data)); - } else { - req.write(this.o.data); - } - } - } - - req.end(); - }); - } -}; - -module.exports = (url, method) => { - return new WumpRequest(url, method); -}; - -common.forEach((v) => { - module.exports[v.toLowerCase()] = (url, method) => { - if (typeof url === 'string') { - return new WumpRequest(url, { method: v, ...method }); - } else { - return new WumpRequest({ method: v, ...url }, method); - } - } -}); - -module.exports.version = version; -module.exports.userAgent = `wumpfetch/${version} (https://github.com/PassTheWessel/wumpfetch)`; diff --git a/dist/wumpfetch.min.js b/dist/wumpfetch.min.js deleted file mode 100644 index e66221d..0000000 --- a/dist/wumpfetch.min.js +++ /dev/null @@ -1 +0,0 @@ -const http=require("http"),https=require("https"),{URL:URL}=require("url"),{join:join}=require("path"),{stringify:stringify}=require("querystring"),{createGunzip:createGunzip,createInflate:createInflate}=require("zlib"),c=["gzip","deflate"],common=["GET","HEAD","POST","PUT","DELETE","CONNECT","OPTIONS","TRACE","PATCH"],{version:version}=require("../package.json"),WumpResponse=class{constructor(e){this.body=Buffer.alloc(0),this.coreRes=e,this.headers=e.headers,this.statusCode=e.statusCode}_addChunk(e){this.body=Buffer.concat([this.body,e])}text(){return this.body.toString()}json(){return JSON.parse(this.body)}},WumpRequest=class{constructor(e={},t={}){const o="string"==typeof e?t:e,r="object"==typeof o;if("string"!=typeof e&&!o.hasOwnProperty("url"))throw new Error("Missing url parameter");return this.o={m:"string"==typeof o?o:r&&o.method?o.method:"GET",url:"string"==typeof e?new URL(e):r&&"string"==typeof o.url?new URL(o.url):e,SDA:r&&"string"==typeof o.sendDataAs?o.sendDataAs:r&&o.data&&"object"==typeof o.data?"json":void 0,data:r&&o.data?o.data:r&&o.json?o.json:r&&o.form?stringify(o.form):void 0,parse:r&&o.parse?o.parse:void 0,follow:!(!r||!o.followRedirects),rHeaders:r&&"object"==typeof o.headers?o.headers:{},streamed:!(!r||!o.streamed),compressed:!(!r||!o.compressed),timeoutTime:r&&"number"==typeof o.timeout?o.timeout:null,coreOptions:r&&"object"==typeof o.coreOptions?o.coreOptions:{}},"object"==typeof o.core&&Object.keys(o.core).forEach(e=>this.option(e,o.core[e])),this}query(e,t){return"object"==typeof e?Object.keys(e).forEach(t=>this.o.url.searchParams.append(t,e[t])):this.o.url.searchParams.append(e,t),this}body(e,t){return this.o.SDA="object"!=typeof e||t||Buffer.isBuffer(e)?t?t.toLowerCase():"buffer":"json",this.o.data="form"===this.SDA?stringify(e):"json"===this.SDA?JSON.stringify(e):e,this}header(e,t){return"object"==typeof e?Object.keys(e).forEach(t=>this.o.rHeaders[t.toLowerCase()]=e[t]):this.o.rHeaders[e.toLowerCase()]=t,this}compress(){return this.compressed=!0,this.o.rHeaders["accept-encoding"]||(this.o.rHeaders["accept-encoding"]=c.join(", ")),this}path(e){return this.o.url.pathname=join(this.o.url.pathname,e),this}stream(){return this.o.streamed=!0,this}option(e,t){return this.o.coreOptions[e]=t,this}timeout(e){return this.o.timeoutTime=e,this}send(){return new Promise((e,t)=>{let o;this.o.data&&(this.o.rHeaders.hasOwnProperty("user-agent")||(this.o.rHeaders["User-Agent"]=w.userAgent),"json"!==this.o.SDA||this.o.rHeaders.hasOwnProperty("content-type")||(this.o.rHeaders["Content-Type"]="application/json"),"form"===this.o.SDA&&(this.o.rHeaders.hasOwnProperty("content-type")||(this.o.rHeaders["Content-Type"]="application/x-www-form-urlencoded"),this.o.rHeaders.hasOwnProperty("content-length")||(this.o.rHeaders["Content-Length"]=Buffer.byteLength(this.o.data))));const r={host:this.o.url.hostname,port:this.o.url.port,path:this.o.url.pathname+this.o.url.search,method:this.o.m,headers:this.o.rHeaders,protocol:this.o.url.protocol,...this.o.coreOptions},s=async o=>{let r,s=o;if(this.o.compressed&&("gzip"===o.headers["content-encoding"]?s=o.pipe(createGunzip()):"deflate"===o.headers["content-encoding"]&&(s=o.pipe(createInflate()))),this.o.streamed)e(s);else{if(r=new WumpResponse(o),o.headers.hasOwnProperty("location")&&this.o.follow)return this.o.url=new URL(o.headers.location,this.o.url).toString(),await module.exports(this.o);s.on("error",e=>t(e)),s.on("data",e=>r._addChunk(e)),s.on("end",()=>{this.o.parse&&("json"===this.o.parse?r.body=JSON.parse(r.body):"text"===this.o.parse?r.body=r.body.toString():r.body=r.body),e(r)})}};if("http:"===this.o.url.protocol)o=http.request(r,s);else{if("https:"!==this.o.url.protocol)throw new Error(`Bad URL protocol: ${this.o.url.protocol}`);o=https.request(r,s)}this.o.timeoutTime&&o.setTimeout(this.o.timeoutTime,()=>{o.abort(),this.o.streamed||t(new Error("Timeout reached"))}),o.on("error",e=>t(e)),this.o.data&&("json"===this.o.SDA?o.write(JSON.stringify(this.o.data)):"object"==typeof this.o.data?o.write(JSON.stringify(this.o.data)):o.write(this.o.data)),o.end()})}};module.exports=((e,t)=>new WumpRequest(e,t)),common.forEach(e=>{module.exports[e.toLowerCase()]=((t,o)=>"string"==typeof t?new WumpRequest(t,{method:e,...o}):new WumpRequest({method:e,...t},o))}),module.exports.version=version,module.exports.userAgent=`wumpfetch/${version} (https://github.com/PassTheWessel/wumpfetch)`; \ No newline at end of file diff --git a/examples/thronebutt-icons.js b/examples/thronebutt-icons.js index e4e077c..5ad12e3 100644 --- a/examples/thronebutt-icons.js +++ b/examples/thronebutt-icons.js @@ -1,52 +1,43 @@ -/** - * Download all death animations from nuclear throne - * - * @name Thronebutt-downloader - * @author Wessel "wesselgame" T - * @license MIT - */ - -// Check if node >= v8.0.0 is installed -if (Number(process.version.slice(1).split('.')[0]) < 8) throw new Error('Node 8.0.0 or higher is required. Update Node on your system.'); -// Declarations -const fs = require('fs'); -const wump = require('../lib'); -// Random string generator -const string = (iterations = 1, seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') => { - let text = ''; - - if (typeof seed !== 'string' || seed.length <= 0 || typeof iterations !== 'number') return text; - for (let i = 0; i < iterations; i++) text += seed.charAt(Math.floor(Math.random() * seed.length)); - - return text; -}; -// Set process values -process.name = 'Thronebutt'; -process.hash = string(8); -// Define some basic stuff -const start = Date.now(); -const settings = { - entries : 150, // Times to loop - baseURL : 'https://thronebutt.com/img/deathflags', // BaseURL for images - interval : 500, // Interval inbetween every request - fileType : 'gif', // The file type to save as - randomize: true // Add a random 0-500ms to every interval -}; -// Fetch `url` and save it to `dump/.` -const get = async(url, index) => { - const req = await wump(url).send(); - if (req.statusCode !== 200) return console.log(`[${process.name}] Failed on "${url}": ${req.statusCode}`) - else { - fs.writeFileSync(`dump/${index}.${settings.fileType}`, req.body); - console.log(`[${process.name}] Successfully copied "${url}" to "dump/${index}.${settings.fileType}" in ${Date.now() - start}ms from queue`); - } -} -// Create the `dump` directory if it doesn't exist -fs.mkdir('dump', (err) => { - if (err && err.code !== 'EEXIST') throw err; -}); -// Loop `settings.entries` times and download the files -for (let i = 0; i < settings.entries; i++) { - const interval = ((settings.interval + (settings.randomize ? Math.floor(Math.random() * 500) : 0)) * i); - setTimeout(() => get(`${settings.baseURL}/${i}.${settings.fileType}`, i), interval) -} \ No newline at end of file +/** + * Download all death animations from nuclear throne via thronebutt(dot)com + * + * @name Thronebutt-downloader + * @author Wessel "wesselgame" T + * @license MIT + */ + +/// Check if node.JS >= v8.0.0 is installed +if (Number(process.version.slice(1).split('.')[0]) < 8) throw new Error('Node 8.0.0 or higher is required. Update Node on your system.'); +/// Declarations +const fs = require('fs'); //* Used to write the buffers to files +const wump = require('../lib'); //* Used as HTTP client + +/// Define some basic stuff +const start = Date.now(); //* Note down when the script started +process.name = 'THRONEBUTT DOWNLOADER'; +const settings = { //* Some basic settings, you can change these if you want + entries : 150, //* Times to loop + baseURL : 'https://thronebutt.com/img/deathflags', //* BaseURL for images + interval : 500, //* Interval inbetween every request + fileType : 'gif', //* The file type to save as + randomize: true //* Add a random 0-500ms to every interval +}; +/// Method to fetch `url` and save it to `dump/:index.:fileType` +const get = async(url, index) => { + const req = await wump(url).send(); + + if (req.statusCode !== 200) return console.log(`[${process.name}] Failed on "${url}": ${req.statusCode}`); + else { + fs.writeFileSync(`dump/${index}.${settings.fileType}`, req.body); + console.log(`[${process.name}] Successfully copied "${url}" to "dump/${index}.${settings.fileType}" in ${Date.now() - start}ms from queue`); + } +}; +/// Create the `dump` directory if it doesn't exist +fs.mkdir('dump', (err) => { + if (err && err.code !== 'EEXIST') throw err; +}); +/// Loop `settings.entries` times and download the files +for (let i = 0; i < settings.entries; i++) { + const interval = ((settings.interval + (settings.randomize ? Math.floor(Math.random() * 500) : 0)) * i); + setTimeout(() => get(`${settings.baseURL}/${i}.${settings.fileType}`, i), interval); +} diff --git a/examples/utils/index.js b/examples/utils/index.js new file mode 100644 index 0000000..1c7ff53 --- /dev/null +++ b/examples/utils/index.js @@ -0,0 +1,3 @@ +module.exports = { + string: require('./string') +}; diff --git a/examples/utils/string.js b/examples/utils/string.js new file mode 100644 index 0000000..22a052f --- /dev/null +++ b/examples/utils/string.js @@ -0,0 +1,10 @@ +module.exports = (iterations = 1, seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') => { + let text = ''; + + if (typeof seed !== 'string' || seed.length <= 0 || typeof iterations !== 'number') return text; + for (let i = 0; i < iterations; i++) { + text += seed.charAt(Math.floor(Math.random() * seed.length)); + } + + return text; +}; diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 1d36b4c..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,17 +0,0 @@ -const g = require('gulp'); -const m = require('gulp-minify'); -const c = require('gulp-concat'); - -g.task('build:node', () => { - return g - .src('dist/wumpfetch.concat.js') - .pipe(m({ noSource: true, ext: { min: '.min.js' } })) - .pipe(g.dest('dist')); -}); - -g.task('concat', () => { - return g - .src('lib/**/*.js') - .pipe(c('wumpfetch.concat.js')) - .pipe(g.dest('dist')); -}); \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index 5979b25..a24baad 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,67 +1,80 @@ -declare module 'wumpfetch' { - import { URL } from 'url'; - import { IncomingMessage, ServerResponse } from 'http'; - - export function get(url: string | URLOptions, method?: URLMethod | MethodOptions): WumpRequest; - export function post(url: string | URLOptions, method?: URLMethod | MethodOptions): WumpRequest; - export function put(url: string | URLOptions, method?: URLMethod | MethodOptions): WumpRequest; - export function patch(url: string | URLOptions, method?: URLMethod | MethodOptions): WumpRequest; - export function connect(url: string | URLOptions, method?: URLMethod | MethodOptions): WumpRequest; - export function options(url: string | URLOptions, method?: URLMethod | MethodOptions): WumpRequest; - export function trace(url: string | URLOptions, method?: URLMethod | URLOptions): WumpRequest; - export class WumpRequest { - public o: { - m: string; - url: URL; - data: any; - parse: any; - follow: boolean; - streamed: boolean; - chaining: boolean; - compressed: boolean; - rHeaders: { [x: string]: string; } - timeoutTime: number; - coreOptions: object; - }; - constructor(url: string | URLOptions, method?: URLMethod | MethodOptions); - public query(a: string | object, b?: string): this; - public body(data: any, SA?: any): this; - public header(a: string | object, b?: string): this; - public compress(): this; - public path(p: string): this; - public stream(): this; - public option(n: string, v: string): this; - public timeout(timeout: number): this; - public send(): Promise; - } - export class WumpResponse { - public body: Buffer | string; - public coreRes: ServerResponse; - public headers: { [x: string]: string }; - public statusCode: number; - constructor(res: any); - private _addChunk(chunk: any): void; - public text(): string; - public json(): NormalObject; - public json(): T; - } - export type KVObject = { [x: string]: string }; - export type NormalObject = { [x: string]: any }; - export type URLMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | "DELETE" | 'CONNECT' | 'OPTIONS' | 'TRACE'; - export interface URLOptions { - url: string; - method: URLMethods; - data?: NormalObject; - headers?: KVObject; - chaining?: boolean; - parse?: 'json' | 'buffer' | 'form'; - } - export interface MethodOptions { - method?: URLMethods; - data?: NormalObject; - headers?: KVObject; - chaining?: boolean; - parse?: 'json' | 'buffer' | 'form'; - } - export default function w(url: string | URLOptions, method?: URLMethod | MethodOptions): WumpRequest; -} +declare module 'wumpfetch' { + import { URL } from 'url'; + import { IncomingMessage, ServerResponse } from 'http'; + + export default function w(url: string | ReqOptions, method?: string | ReqOptions): WumpRequest; + + export const version: string; + export const userAgent: string; + + export function get(url: string | URL | ReqOptions, method?: string | ReqOptions): WumpRequest; + export function put(url: string | URL | ReqOptions, method?: string | ReqOptions): WumpRequest; + export function post(url: string | URL | ReqOptions, method?: string | ReqOptions): WumpRequest; + export function head(url: string | URL | ReqOptions, method?: string | ReqOptions): WumpRequest; + export function patch(url: string | URL | ReqOptions, method?: string | ReqOptions): WumpRequest; + export function trace(url: string | URL | ReqOptions, method?: string | ReqOptions): WumpRequest; + export function connect(url: string | URL | ReqOptions, method?: string | ReqOptions): WumpRequest; + export function options(url: string | URL | ReqOptions, method?: string | ReqOptions): WumpRequest; + + export function getProfile(name: string): { [x: string]: any }; + export function setProfile(name: string, profileData: { [x: string]: any }): void; + export function setDefaults(profileData: { [x: string]: any }): void; + + export class WumpRequest { + public o: { + m: string; + url: URL; + parse: 'json' | 'form'; + follow: boolean; + streamed: boolean; + chaining: boolean; + compressed: boolean; + timeoutTime: number; + rHeaders: { [x: string]: string; } + coreOptions: { [x: string]: any }; + body, json: any; + data, form: any; + } + constructor(url: string | URL | ReqOptions, method?: string | ReqOptions); + public body(data: any, sendAs?: 'json' | 'form' | 'buffer'): this; + public path(path: string): this; + public query(name: string | { [x: string]: string }, value?: string): this; + public header(name: string | { [x: string]: string }, value?: string): this; + public option(name: string, value: string): this; + public timeout(timeout: number): this; + public stream(): this; + public compress(): this; + public send(): Promise; + } + + export class WumpResponse { + public body: Buffer | { [x: string]: any } | string; + public coreRes: ServerResponse; + public headers: { [x: string]: string }; + public statusCode: number; + + constructor(res: any); + public text(): string; + public json(): { [x: string]: any }; + public buffer(): Buffer; + public json(): T; + public parse(): string | { [x: string]: any }; + private _addChunk(chunk: any): void; + } + + export interface ReqOptions { + url?: string | URL; + parse?: 'json' | 'form'; + method?: string; + sendDataAs?: 'json' | 'form' | 'buffer'; + chain?: boolean; + streamed?: boolean; + compressed?: boolean; + followRedirects?: boolean; + timeout?: number; + headers?: { [x: string]: string }; + coreOptions?: { [x: string]: any }; + body?, json?: any; + data?, form?: any; + } +} diff --git a/lib/index.js b/lib/index.js index e198d53..3c8ce9b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,35 +1,80 @@ -const req = require('./model/WumpRequest'); -const metaData = require('../package.json'); -const Collection = require('./util/Collection'); - -const profiles = new Collection(); - -module.exports.default = {}; -module.exports.setDefault = (profileData = {}) => this.default = profileData; - -module.exports = (url, method = this.default) => { - if (url instanceof Object) url = Object.assign(this.default, url); - return new req(url, method); -}; - -module.exports.getProfile = (name = 'main') => { return profiles.get(name); }; -module.exports.setProfile = (name = 'main', profileData = {}) => { - if (typeof profileData !== 'object') throw new TypeError(`profileData must be of type object. Received type ${typeof profileData}`); - if (typeof name !== 'string') throw new TypeError(`name must be of type string. Recevied type ${typeof name}`); - return profiles.set(name, profileData); -}; - -for (const entry of [ 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH' ]) { - module.exports[entry.toLowerCase()] = (url, method = { method: entry }) => { - if (typeof url === 'object') { - url = Object.assign(this.default, url); - url.method = entry; - } - - return new req(url, method); - }; -} - -module.exports.version = metaData.version; -module.exports.userAgent = `${metaData.name}/${metaData.version} (${metaData.repository.url})`; - +const req = require('./model/WumpRequest'); +const metaData = require('../package.json'); +const MemoryCollection = require('./util/MemoryCollection'); + +const profiles = new MemoryCollection(); +profiles.set('__default__', { headers: { 'User-Agent': `${metaData.name}/${metaData.version} (${metaData.repository.url})` } }); + +/** + * Create a request with a specific method + * + * @param {object | string} url - The URL to send the request to or a `ReqOptions` object + * @param {object | string} [method=profiles.__default__] - The method to use when sending the request or a `ReqOptions` object + * @returns {req} - The request class + */ +module.exports = (url, method = profiles.get('__default__')) => { + if (typeof url === 'object' && typeof method === 'string') url.method = method; + if (typeof url === 'object' && !url.overwriteDefaults) Object.assign(url, profiles.get('__default__')); + if (typeof method === 'object' && !method.overwriteDefaults) Object.assign(method, profiles.get('__default__')); + + return new req(url, method); +}; + +/** + * Set the defaults for every request + * + * @param {object} [profileData={}] - The default request options to use + * @returns {object} - The new defaults + */ +module.exports.setDefaults = (profileData = {}) => profiles.set('__default__', profileData); + +/** + * Get a profile that you've previously saved + * + * @param {string} [name=main] - The profile to get + * @returns {object} - The profile if found + */ +module.exports.getProfile = (name = 'main') => { return profiles.get(name); }; + +/** + * Save a profile globally + * + * @param {string} [name=main] - The profile's name, used to identify later on + * @param {string} [profileData={}] - The profile's request data + * @returns {object} - The newly added profile + * + */ +module.exports.setProfile = (name = 'main', profileData = {}) => { + if (typeof name !== 'string') throw new TypeError(`name must be of type string. Recevied type ${typeof name}`); + if (typeof profileData !== 'object') throw new TypeError(`profileData must be of type object. Received type ${typeof profileData}`); + profiles.set(name, profileData); +}; + +for (const entry of [ 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH' ]) { + /** + * Create a request with a specific method + * + * @param {object | string} url - The URL to send the request to or a `ReqOptions` object + * @param {object | string} [method=profiles.__default__] - The method to use when sending the request or a `ReqOptions` object + * @returns {req} - The request class + */ + module.exports[entry.toLowerCase()] = (url, method = profiles.get('__default__')) => { + if (typeof url === 'object' && !url.overwriteDefaults) Object.assign(url, profiles.get('__default__')); + if (typeof method === 'object' && !method.overwriteDefaults) Object.assign(method, profiles.get('__default__')); + + if (typeof url === 'object') url.method = entry; + if (typeof method === 'object') method.method = entry; + + return new req(url, method); + }; +} + +/** + * The current running version of wumpfetch + */ +module.exports.version = metaData.version; +/** + * The default user-agent that's used when no user agent is provided + * @readonly + */ +module.exports.userAgent = `${metaData.name}/${metaData.version} (${metaData.repository.url})`; diff --git a/lib/model/WumpRequest.js b/lib/model/WumpRequest.js index 4e5d2e6..1b77993 100644 --- a/lib/model/WumpRequest.js +++ b/lib/model/WumpRequest.js @@ -1,176 +1,253 @@ -const zlib = require('zlib'); -const http = require('http'); -const https = require('https'); -const { URL } = require('url'); -const { stringify } = require('querystring'); -const { join: pJoin } = require('path'); - -const w = require('../index'); -const WumpRes = require('./WumpResponse'); -const metaData = require('../../package.json'); -const compressions = [ 'gzip', 'deflate' ]; - - -module.exports = class WumpRequest { - constructor (url = {}, method = {}) { - const o = (typeof url === 'string' ? method : url); - - if (typeof url !== 'string' && !o.hasOwnProperty('url')) throw new Error('Missing URL parameter'); - - this.o = { - 'm': typeof o === 'string' ? o : (o.method || 'GET'), - 'url': typeof url === 'string' ? new URL(url) : typeof o.url === 'string' ? new URL(o.url) : url, - 'SDA': typeof o.sendDataAs === 'string' ? o.sendDataAs : o.data && typeof o.data === 'object' ? 'json' : undefined, - 'data': o.body || o.data || o.json || (o.form ? stringify(o.form) : undefined), - 'parse': o.parse, - 'chain': typeof o.chain === 'boolean' ? o.chain : true, - 'follow': !!(o.followRedirects), - 'rHeaders': typeof o.headers === 'object' ? o.headers : {}, - 'streamed': !!(o.streamed), - 'compressed': !!(o.compressed), - 'timeoutTime': typeof o.timeout === 'number' ? o.timeout : null, - 'coreOptions': typeof o.coreOptions === 'object' ? o.coreOptions : {} - }; - - if (typeof o.core === 'object') for (const key of Object.keys(o.core)) this.option(key, o.core[key]); - if (!this.o.chain) return this.send(); - - return this; - } - - query (name, value) { - if (typeof a === 'object') for (const key of Object.keys(name)) this.o.url.searchParams.append(key, name[key]); - else this.o.url.searchParams.append(name, value); - - return this; - } - - body (data, sendAs) { - this.o.SDA = typeof data === 'object' && !sendAs && !Buffer.isBuffer(data) ? 'json' : sendAs ? sendAs.toLowerCase() : 'buffer'; - this.o.data = this.sendAs === 'form' ? stringify(data) : this.SDA === 'json' ? JSON.stringify(data) : data; - - return this; - } - - header (name, value) { - if (typeof name === 'object') for (const key of Object.keys(name)) this.o.rHeaders[key.toLowerCase()] = name[key]; - else this.o.rHeaders[name.toLowerCase()] = value; - - return this; - } - - compress () { - this.compressed = true; - if (!this.o.rHeaders['accept-encoding']) this.o.rHeaders['accept-encoding'] = compressions.join(', '); - - return this; - } - - path (p) { - this.o.url.pathname = pJoin(this.o.url.pathname, p); - - return this; - } - - stream () { - this.o.streamed = true; - - return this; - } - - option (name, value) { - this.o.coreOptions[name] = value; - - return this; - } - timeout (timeout = 0) { - this.o.timeoutTime = timeout; - - return this; - } - - send () { - return new Promise((resolve, reject) => { - if (this.o.data) { - if (!this.o.rHeaders.hasOwnProperty('user-agent')) this.o.rHeaders['User-Agent'] = w.userAgent || `${metaData.name}/${metaData.version} (${metaData.repository.url})`; - - if (this.o.SDA === 'json' && !this.o.rHeaders.hasOwnProperty('content-type')) this.o.rHeaders['Content-Type'] = 'application/json'; - if (this.o.SDA === 'form') { - if (!this.o.rHeaders.hasOwnProperty('content-type')) this.o.rHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; - if (!this.o.rHeaders.hasOwnProperty('content-length')) this.o.rHeaders['Content-Length'] = Buffer.byteLength(this.o.data); - } - } - - let req; - const options = { - 'host': this.o.url.hostname, - 'port': this.o.url.port, - 'path': `${this.o.url.pathname}${this.o.url.search}`, - 'method': this.o.m, - 'headers': this.o.rHeaders, - 'protocol': this.o.url.protocol, - ...this.o.coreOptions - }; - - const handler = async (res) => { - let stream = res; - - if (this.o.compressed) { - switch (res.headers['content-encoding']) { - case 'gzip': stream = res.pipe(zlib.createGunzip()); break; - case 'defalte': stream = res.pipe(zlib.createInflate()); break; - } - } - - let wumpRes; - - if (this.o.streamed) resolve(stream); - else { - wumpRes = new WumpRes(res); - - if (res.headers.hasOwnProperty('location') && this.o.follow) { - this.o.url = (new URL(res.headers['location'], this.o.url)).toString(); - return await w(this.o); - } - - stream.on('error', (e) => reject(e)); - stream.on('data', (c) => wumpRes._addChunk(c)); - stream.on('end', () => { - if (this.o.parse) { - switch (this.o.parse) { - case 'json': wumpRes.body = JSON.parse(wumpRes.body); break; - case 'text': wumpRes.body = wumpRes.body.toString(); break; - default: wumpRes.body = wumpRes.body; - } - } - - resolve(wumpRes); - }); - } - }; - - switch (this.o.url.protocol) { - case 'http:': req = http.request(options, handler); break; - case 'https:': req = https.request(options, handler); break; - default: throw new Error(`Bad URL protocol: ${this.o.url.protocol}`); - } - - if (this.o.timeoutTime) { - req.setTimeout(this.o.timeoutTime, () => { - req.abort(); - if (!this.o.streamed) reject(new Error('Timeout reached')); - }); - } - - req.on('error', (e) => reject(e)); - - if (this.o.data) { - if (this.o.SDA === 'json') req.write(JSON.stringify(this.o.data)); - else if (this.o.data instanceof Object) req.write(JSON.stringify(this.o.data)); - else req.write(this.o.data); - } - - req.end(); - }); - } -}; +const zlib = require('zlib'); +const http = require('http'); +const https = require('https'); +const { URL } = require('url'); +const { stringify } = require('querystring'); +const { join: pJoin } = require('path'); + +const wump = require('../index'); +const WumpRes = require('./WumpResponse'); +const metaData = require('../../package.json'); +const compressions = [ 'gzip', 'deflate' ]; + +module.exports = class WumpRequest { + /** + * Create the initial request + * + * @param {object | string} [url={}] - The URL to send the request to or a `ReqOptions` object + * @param {object | string} [method={}] - The method to use when sending the request or a `ReqOptions` object + * @throws {Error} - Missing URL parameter + * @returns {WumpRequest} - The request class + */ + constructor (url = {}, method = {}) { + const options = (typeof url === 'string' ? method : url); + + if (typeof url !== 'string' && !options.hasOwnProperty('url')) throw new Error('Missing URL parameter'); + this.o = { + 'm': typeof method === 'string' ? method : (options.method || 'GET'), + 'url': typeof url === 'string' ? new URL(url) : typeof options.url === 'string' ? new URL(options.url) : url, + 'SDA': typeof options.sendDataAs === 'string' ? options.sendDataAs : options.data && typeof options.data === 'object' ? 'json' : undefined, + 'data': options.body || options.data || options.json || (options.form ? stringify(options.form) : undefined), + 'parse': options.parse, + 'chain': typeof options.chain === 'boolean' ? options.chain : true, + 'follow': !!(options.followRedirects), + 'rHeaders': typeof options.headers === 'object' ? options.headers : {}, + 'streamed': !!(options.streamed), + 'compressed': !!(options.compressed), + 'timeoutTime': typeof options.timeout === 'number' ? options.timeout : null, + 'coreOptions': typeof options.coreOptions === 'object' ? options.coreOptions : {} + }; + + if (typeof options.coreOptions === 'object') { + for (const key of Object.keys(options.coreOptions)) { + this.option(key, options.coreOptions[key]); + } + } + + if (!this.o.chain) { + return this.send(); + } + + return this; + } + + /** + * Append the request's body + * + * @param {any} data - The data to append + * @param {string} sendAs - The way to send the data, either `json`, `form` or `buffer` + * @returns {WumpRequest} - The request class + */ + body(data, sendAs) { + this.o.SDA = typeof data === 'object' && !sendAs && !Buffer.isBuffer(data) ? 'json' : sendAs ? sendAs.toLowerCase() : 'buffer'; + this.o.data = this.sendAs === 'form' ? stringify(data) : this.SDA === 'json' ? JSON.stringify(data) : data; + + return this; + } + + /** + * Update the path of the URL (protocol://url.domain/:path) + * + * @param {string} path - The path to append to the url + * @returns {WumpRequest} - The request class + */ + path(path) { + this.o.url.pathname = pJoin(this.o.url.pathname, path); + + return this; + } + + /** + * Append the request's URL with a query (?name=value) + * + * @param {object | string} name - The name of the query or an object with multiple queries + * @param {string} value - The value of the query if `name` is of type `string` + * @returns {WumpRequest} - The request class + */ + query (name, value) { + if (typeof name === 'object') { + for (const key of Object.keys(name)) { + this.o.url.searchParams.append(key, name[key]); + } + } else this.o.url.searchParams.append(name, value); + + return this; + } + + /** + * + * @param {string | object} name - The name of the header or an object containing multiple headers + * @param {string} value - The content of the header if `name` is type of `string` + * @returns {WumpRequest} - The request class + */ + header (name, value) { + if (typeof name === 'object') { + for (const key of Object.keys(name)) { + this.o.rHeaders[key.toLowerCase()] = name[key]; + } + } else this.o.rHeaders[name.toLowerCase()] = value; + + return this; + } + + /** + * Change one of NodeJS' `http` library options + * + * @param {string} name - The name of the option + * @param {any} value - The value of the optiob + * @returns {WumpRequest} - The request class + */ + option(name, value) { + this.o.coreOptions[name] = value; + + return this; + } + + /** + * Add a request timeout + * + * @param {number} timeout - The time before timing out (ms) + * @returns {WumpRequest} - The request class + */ + timeout(timeout = 0) { + this.o.timeoutTime = timeout; + + return this; + } + + /** + * Keep a connection alive with the server + * + * @returns {WumpRequest} - The request class + */ + stream() { + this.o.streamed = true; + + return this; + } + + /** + * Accept all encodings from the server + * + * @returns {WumpRequest} - The request class + */ + compress() { + this.compressed = true; + if (!this.o.rHeaders['accept-encoding']) this.o.rHeaders['accept-encoding'] = compressions.join(', '); + + return this; + } + + /** + * Finish off the request by sending it to the specified URL + * + * @throws {Error} - Bad URL Protocol + * @throws {Error} - Timeout reached + * @returns {Promise} - Teh response data + */ + send () { + return new Promise((resolve, reject) => { + if (this.o.data) { + if (!this.o.rHeaders.hasOwnProperty('user-agent')) this.o.rHeaders['User-Agent'] = `${metaData.name}/${metaData.version} (${metaData.repository.url})`; + + if (this.o.SDA === 'json' && !this.o.rHeaders.hasOwnProperty('content-type')) this.o.rHeaders['Content-Type'] = 'application/json'; + if (this.o.SDA === 'form') { + if (!this.o.rHeaders.hasOwnProperty('content-type')) this.o.rHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; + if (!this.o.rHeaders.hasOwnProperty('content-length')) this.o.rHeaders['Content-Length'] = Buffer.byteLength(this.o.data); + } + } + + let req; + const options = { + 'host': this.o.url.hostname, + 'port': this.o.url.port, + 'path': `${this.o.url.pathname}${this.o.url.search}`, + 'method': this.o.m, + 'headers': this.o.rHeaders, + 'protocol': this.o.url.protocol, + ...this.o.coreOptions + }; + + const handler = async (res) => { + let stream = res; + + if (this.o.compressed) { + switch (res.headers['content-encoding']) { + case 'gzip': stream = res.pipe(zlib.createGunzip()); break; + case 'defalte': stream = res.pipe(zlib.createInflate()); break; + } + } + + let wumpRes; + + if (this.o.streamed) { + resolve(stream); + } else { + wumpRes = new WumpRes(res); + + if (res.headers.hasOwnProperty('location') && this.o.follow) { + this.o.url = (new URL(res.headers['location'], this.o.url)).toString(); + return await wump(this.o); + } + + stream.on('error', (e) => reject(e)); + stream.on('data', (c) => wumpRes._addChunk(c)); + stream.on('end', () => { + if (this.o.parse) { + switch (this.o.parse) { + case 'json': wumpRes.body = JSON.parse(wumpRes.body); break; + case 'text': wumpRes.body = wumpRes.body.toString(); break; + default: wumpRes.body = wumpRes.body; + } + } + + resolve(wumpRes); + }); + } + }; + + switch (this.o.url.protocol) { + case 'http:': req = http.request(options, handler); break; + case 'https:': req = https.request(options, handler); break; + default: throw new Error(`Bad URL protocol: ${this.o.url.protocol}`); + } + + if (this.o.timeoutTime) { + req.setTimeout(this.o.timeoutTime, () => { + req.abort(); + if (!this.o.streamed) reject(new Error('Timeout reached')); + }); + } + + req.on('error', (e) => reject(e)); + + if (this.o.data) { + if (this.o.SDA === 'json') req.write(JSON.stringify(this.o.data)); + else if (this.o.data instanceof Object) req.write(JSON.stringify(this.o.data)); + else req.write(this.o.data); + } + + req.end(); + }); + } +}; diff --git a/lib/model/WumpResponse.js b/lib/model/WumpResponse.js index 703ea8b..6e39e67 100644 --- a/lib/model/WumpResponse.js +++ b/lib/model/WumpResponse.js @@ -1,21 +1,21 @@ -module.exports = class WumpResponse { - constructor (res) { - this.body = Buffer.alloc(0); - this.coreRes = res; - this.headers = res.headers; - this.statusCode = res.statusCode; - } - - _addChunk (chunk) { - this.body = Buffer.concat([ this.body, chunk ]); - } - - parse () { - if (this.headers['content-type'].includes('application/json')) return JSON.parse(this.body); - else return this.body.toString(); - } - - text () { return this.body.toString(); } - json () { return JSON.parse(this.body); } - buffer () { return this.body; } -}; +module.exports = class WumpResponse { + constructor (res) { + this.body = Buffer.alloc(0); + this.coreRes = res; + this.headers = res.headers; + this.statusCode = res.statusCode; + } + + _addChunk (chunk) { + this.body = Buffer.concat([ this.body, chunk ]); + } + + parse () { + if (this.headers['content-type'].includes('application/json')) return this.json(); + else return this.text(); + } + + text() { return this.body.toString(); } + json() { return JSON.parse(this.body); } + buffer() { return this.body; } +}; diff --git a/lib/util/Collection.js b/lib/util/MemoryCollection.js similarity index 60% rename from lib/util/Collection.js rename to lib/util/MemoryCollection.js index 5241e7b..de01e00 100644 --- a/lib/util/Collection.js +++ b/lib/util/MemoryCollection.js @@ -1,21 +1,23 @@ -module.exports = class PikminCollection extends Map { - constructor() { - super(); - } - - filter(callback) { - let result = []; - for (const entry of Array.from(this.values())) { - if (callback(entry)) result.push(entry); - } - - return result; - } - - map(callback) { - let result = []; - for (const value of Array.from(this.values())) result.push(callback(value)); - - return result; - } -}; \ No newline at end of file +module.exports = class MemoryCollection extends Map { + constructor() { + super(); + } + + filter(callback) { + let result = []; + for (const entry of Array.from(this.values())) { + if (callback(entry)) result.push(entry); + } + + return result; + } + + map(callback) { + let result = []; + for (const value of Array.from(this.values())) { + result.push(callback(value)); + } + + return result; + } +}; diff --git a/package.json b/package.json index 0b35568..48d012b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wumpfetch", - "version": "0.2.13", - "description": "🚀 A lightweight and fast Node.JS HTTP Client", + "version": "0.3.0", + "description": "🚀🔗 A modern, lightweight, fast and easy to use Node.js HTTP client", "author": "Wessel \"wesselgame\" T ", "license": "MIT", "main": "lib/index.js", @@ -19,7 +19,7 @@ "url": "https://github.com/PassTheWessel/wumpfetch" }, "scripts": { - "test": "cd __test__ && node main.js" + "test": "cd tests && node main.js" }, "keywords": [ "http", diff --git a/__test__/main.js b/tests/main.js similarity index 53% rename from __test__/main.js rename to tests/main.js index 5a95f8f..2289298 100644 --- a/__test__/main.js +++ b/tests/main.js @@ -1,16 +1,16 @@ -const util = require('util'); -const wump = require('../lib'); - -console.log(`Using wumpfetch v${wump.version} [${wump.userAgent}]`); - -;(async() => { - const requests = [ - await wump({ url: 'https://jsonplaceholder.typicode.com/todos/1', parse: 'json' }).send(), - await wump('https://jsonplaceholder.typicode.com/todos/1', { chain: false }), - await wump('https://jsonplaceholder.typicode.com/posts', { chain: false, method: 'POST', body: { title: 'yeet', body: 'us', userId: 1 } }) - ]; - - console.log(`Test 1: \n${util.inspect(requests[0].body)}\n`); - console.log(`Test 2: \n${util.inspect(requests[1].json())}\n`); - console.log(`Test 3: \n${util.inspect(requests[2].parse())}\n`); -})(); \ No newline at end of file +const util = require('util'); +const wump = require('../lib'); + +console.log(`Using wumpfetch v${wump.version} [${wump.userAgent}]`); + +;(async() => { + const requests = [ + await wump({ url: 'https://httpbin.org/get', parse: 'json' }).send(), + await wump('https://httpbin.org/get', { chain: false }), + await wump('https://httpbin.org/post', { chain: false, method: 'POST', body: { title: 'Wump', body: 'is cool', userId: 1 } }) + ]; + + console.log(`Test 1: \n${util.inspect(requests[0].body)}\n`); + console.log(`Test 2: \n${util.inspect(requests[1].json())}\n`); + console.log(`Test 3: \n${util.inspect(requests[2].parse())}\n`); +})(); diff --git a/tests/profiles.js b/tests/profiles.js new file mode 100644 index 0000000..391e57b --- /dev/null +++ b/tests/profiles.js @@ -0,0 +1,22 @@ +const wump = require('../lib'); +const util = require('util'); + +console.log(`Using wumpfetch v${wump.version} [${wump.userAgent}]`); + +const profileData = { + headers: { + 'Authorization': 'Wumper' + } +}; + +wump.setProfile('main', profileData); +console.log(wump.getProfile('main')); + +wump.setDefaults(profileData); +console.log(wump.getProfile('__default__')); + +;(async () => { + const req = await wump({ url: 'https://httpbin.org/get' }).send(); + + console.log(`Test 1: \n${util.inspect(req.parse())}`); +})(); diff --git a/tests/wumpfetch-tests.ts b/tests/wumpfetch-tests.ts new file mode 100644 index 0000000..afe8db0 --- /dev/null +++ b/tests/wumpfetch-tests.ts @@ -0,0 +1,14 @@ +import wump from '../lib'; +import util from 'util'; + +console.log(`Using wumpfetch v${wump.version} [${wump.userAgent}]`); + +;(async() => { + const requests = [ + await wump('https://httpbin.org/get', 'GET').send(), + await wump('https://httpbin.org/get', { method: 'GET', chaining: false }) + ]; + + console.log(`Test 1: \n${util.inspect(requests[0].parse())}\n`); + console.log(`Test 2: \n${util.inspect(requests[1].json())}\n`); +})();