diff --git a/README.md b/README.md index 9779e60..8a45371 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,85 @@ # node-fetch-cookies -node-fetch wrapper that adds support for cookie-jars +A [node-fetch](https://github.com/bitinn/node-fetch) wrapper with support for cookies. +It supports reading/writing from/to a JSON cookie jar and keeps cookies in memory until you call `CookieJar.save()` to reduce disk I/O. -## Example Usage +## Usage Example ```javascript -import cookies from "node-fetch-cookies"; -// "rw" are the flags, meaning cookies will be read and written from/to the cookie jar. r = read, w = write -// second argument is the filename, can also be undefined if you don't want to read/write the cookies from/to a file -let cookieJar = new cookies.CookieJar("rw", "path/to/file.json"); -// first parameter is the url, second the options, just like node-fetch. third parameter is the cookie jar (can also be an array) -// returns a response object just like node-fetch -cookies.fetch("https://example.page/example/path", null, cookieJar); +import {fetch, CookieJar} from "node-fetch-cookies"; + +(async () => { + // creates a CookieJar instance + let cookieJar = new CookieJar("rw", "jar.json"); + + // usual fetch usage, except with one or multiple cookie jars as first parameter + const response = await fetch(cookieJar, "https://google.de"); + + // save the received cookies to disk + cookieJar.save(); +})(); ``` + +## Documentation + +### fetch(cookieJar, url, options) +- `cookieJar` A [CookieJar](#class-cookiejar) instance or an array of CookieJar instances +- `url` and `options` as in https://github.com/bitinn/node-fetch#fetchurl-options + + +### Class: CookieJar +A class that stores cookies. + +#### Properties +- `flags` The read/write flags as specified below. +- `file` The path of the cookie jar on the disk. +- `cookies` A [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) mapping cookie names to their properties. + +#### new CookieJar(flags, file[, cookies]) +- `flags` A string specifying whether cookies should be read and/or written from/to the jar when passing it as parameter to [fetch](#fetchcookiejar-url-options). + - `r`: only read from this jar + - `w`: only write to this jar + - `rw` or `wr`: read/write from/to this jar +- `file` A string containing a relative or absolute path to the file on the disk to use. +- `cookies` An optional initializer for the cookie jar - either an array of [Cookie](#class-cookie) instances or a single Cookie instance. + +#### addCookie(cookie[, url]) +Adds a cookie to the jar. +- `cookie` A [Cookie](#class-cookie) instance to add to the cookie jar. Alternatively this can also be a string, for example the string received from a website. In this case `url` should be specified. +- `url` The url a cookie has been received from. + +#### forEach(callback) +Just a wrapper for `CookieJar.cookies.forEach(callback)`. + +#### save() +Saves the cookie jar to disk. Only non-expired cookies are saved. + + +### Class: Cookie +An abstract representation of a cookie. + +#### Properties +- `name` The identifier of the cookie. +- `value` The value of the cookie. +- `expiry` A [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object of the cookies expiry date. +- `domain` The domain the cookie is valid for. +- `path` The path the cookie is valid for. +- `secure` A boolean value representing the cookie's secure attribute. If set the cookie will only be used for `https` requests. +- `subdomains` A boolean value specifying whether the cookie should be used for subdomains of the domain or not. + +#### new Cookie(cookie, url) +- `cookie` The string representation of a cookie as send by a webserver. +- `url` The url the cookie has been received from. + +#### static fromObject(obj) +Creates a cookie instance from an already existing object with the same properties. + +#### serialize() +Serializes the cookie, transforming it to `name=value` so it can be used in requests. + +#### hasExpired() +Returns whether the cookie has expired or not. + +#### isValidForRequest(url) +Returns whether the cookie is valid for a request to `url`. If not, it won't be send by the fetch wrapper. + +## License +This project is licensed under the MIT license, see [LICENSE](LICENSE). diff --git a/package-lock.json b/package-lock.json index 9fb95c3..0b7efd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "node-fetch-cookies", - "version": "1.0.0", + "version": "1.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { "node-fetch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", - "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" } } } diff --git a/package.json b/package.json index 396a9fd..e1ac4ac 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "node-fetch-cookies", - "version": "1.0.2", + "version": "1.0.3", "description": "node-fetch wrapper that adds support for cookie-jars", "main": "src/index.mjs", "engines": { - "node": ">=10.0.0" + "node": ">=10.0.0" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -26,6 +26,6 @@ }, "homepage": "https://github.com/jkhsjdhjs/node-fetch-cookies#readme", "dependencies": { - "node-fetch": "^2.3.0" + "node-fetch": "^2.6.0" } } diff --git a/src/cookie-jar.mjs b/src/cookie-jar.mjs index 9bcab6c..550bbf3 100644 --- a/src/cookie-jar.mjs +++ b/src/cookie-jar.mjs @@ -35,7 +35,7 @@ export default class CookieJar { // only save cookies that haven't expired let cookiesToSave = new Map(); this.forEach(cookie => { - if(cookie.expiry && cookie.expiry > new Date()) + if(!cookie.hasExpired()) cookiesToSave.set(cookie.name, cookie); }); fs.writeFileSync(this.file, JSON.stringify([...cookiesToSave])); diff --git a/src/cookie.mjs b/src/cookie.mjs index e999550..975422a 100644 --- a/src/cookie.mjs +++ b/src/cookie.mjs @@ -90,8 +90,11 @@ export default class Cookie { serialize() { return this.name + "=" + this.value; } + hasExpired() { + return this.expiry && this.expiry < new Date(); + } isValidForRequest(url) { - if(this.expiry && this.expiry < new Date()) + if(this.hasExpired()) return false; const parsedURL = urlParser.parse(url); if(parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:") diff --git a/src/index.mjs b/src/index.mjs index e52076d..6d4db33 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -2,54 +2,52 @@ import fetch from "node-fetch"; import CookieJar from "./cookie-jar"; import Cookie from "./cookie"; -export default { - fetch: async (url, options, cookieJars) => { - let cookies = ""; - if(Array.isArray(cookieJars) && cookieJars.every(c => c instanceof CookieJar)) { - cookieJars.forEach(jar => { - if(!jar.flags.includes("r")) - return; - jar.forEach(c => { - if(c.isValidForRequest(url)) - cookies += c.serialize() + "; "; - }); - }); - } - else if(cookieJars instanceof CookieJar && cookieJars.flags.includes("r")) { - cookieJars.forEach(c => { +async function cookieFetch(cookieJars, url, options) { + let cookies = ""; + if(Array.isArray(cookieJars) && cookieJars.every(c => c instanceof CookieJar)) { + cookieJars.forEach(jar => { + if(!jar.flags.includes("r")) + return; + jar.forEach(c => { if(c.isValidForRequest(url)) cookies += c.serialize() + "; "; }); + }); + } + else if(cookieJars instanceof CookieJar && cookieJars.flags.includes("r")) { + cookieJars.forEach(c => { + if(c.isValidForRequest(url)) + cookies += c.serialize() + "; "; + }); + } + else + throw new TypeError("Third paramter is neither a cookie jar nor an array of cookie jars!"); + if(cookies.length !== 0) { + if(!options) { + options = { + headers: {} + }; } - else - throw new TypeError("Third paramter is neither a cookie jar nor an array of cookie jars!"); - if(cookies.length !== 0) { - if(!options) { - options = { - headers: {} - }; - } - if(!options.headers) - options.headers = {}; - options.headers.cookie = cookies.slice(0, -2); + else if(!options.headers) + options.headers = {}; + options.headers.cookie = cookies.slice(0, -2); + } + const result = await fetch(url, options); + // i cannot use headers.get() here because it joins the cookies to a string + cookies = result.headers[Object.getOwnPropertySymbols(result.headers)[0]]["set-cookie"]; + if(cookies) { + if(Array.isArray(cookieJars)) { + cookieJars.forEach(jar => { + if(!jar.flags.includes("w")) + return; + cookies.forEach(c => jar.addCookie(c, url)); + }); } - const result = await fetch(url, options); - // i cannot use headers.get() here because it joins the cookies to a string - cookies = result.headers[Object.getOwnPropertySymbols(result.headers)[0]]["set-cookie"]; - if(cookies) { - if(Array.isArray(cookieJars)) { - cookieJars.forEach(jar => { - if(!jar.flags.includes("w")) - return; - cookies.forEach(c => jar.addCookie(c, url)); - }); - } - else if(cookieJars.flags.includes("w")) { - cookies.forEach(c => cookieJars.addCookie(c, url)); - } + else if(cookieJars.flags.includes("w")) { + cookies.forEach(c => cookieJars.addCookie(c, url)); } - return result; - }, - CookieJar: CookieJar, - Cookie: Cookie -}; + } + return result; +} + +export {cookieFetch as fetch, CookieJar, Cookie};