diff --git a/README.md b/README.md index 22e8d1b..3fb5767 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,29 @@ A class that stores cookies. #### 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. +- `cookie` A [Cookie](#class-cookie) instance to add to the cookie jar. Alternatively this can also be a string, for example a serialized cookie 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)`. +#### addFromFile(file) +Reads a cookie jar from the disk and adds the contained cookies. + +#### domains() +Returns an array of the domains currently stored cookies for. + +#### *iterValidForRequest(domain, url) +Returns an iterator over all cookies valid for a request to `domain` and `url`. + +#### *iterValid() +Returns an iterator over all valid (non-expired) cookies. + +#### *iterAll() +Returns an iterator over all cookies currently stored. + +#### *iter(domain) +Returns an iterator over all cookies for a specific domain. + +#### deleteExpired() +Removes all expired cookies from the jar. #### save() Saves the cookie jar to disk. Only non-expired cookies are saved. diff --git a/package-lock.json b/package-lock.json index cca04f8..afb6c75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "node-fetch-cookies", - "version": "1.0.6", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ba9014e..a7cb486 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-fetch-cookies", - "version": "1.0.6", + "version": "1.1.0", "description": "node-fetch wrapper that adds support for cookie-jars", "main": "src/index.mjs", "engines": { diff --git a/src/cookie-jar.mjs b/src/cookie-jar.mjs index c842d2f..e990031 100644 --- a/src/cookie-jar.mjs +++ b/src/cookie-jar.mjs @@ -10,36 +10,63 @@ export default class CookieJar { throw new TypeError("First parameter is not a string!"); if(this.file && typeof this.file !== "string") throw new TypeError("Second parameter is not a string!"); + if(this.file && fs.existsSync(this.file)) + this.addFromFile(this.file); + else + this.cookies = new Map(); if(Array.isArray(cookies)) { if(!cookies.every(c => c instanceof Cookie)) throw new TypeError("Third parameter is not an array of cookies!"); else - cookies.forEach(cookie => this.cookies.set(cookie.name, cookie)); + cookies.forEach(cookie => this.addCookie(cookie)); } else if(cookies instanceof Cookie) - this.cookies.set(cookies.name, cookies); + this.addCookie(cookies); else if(cookies) throw new TypeError("Third parameter is neither an array nor a cookie!"); - if(this.file && this.cookies.size === 0 && this.file.length !== 0 && fs.existsSync(this.file)) - this.cookies = new Map(JSON.parse(fs.readFileSync(this.file)).map(([k, v]) => [k, Cookie.fromObject(v)])); } addCookie(c, fromURL) { if(typeof c === "string") c = new Cookie(c, fromURL); - this.cookies.set(c.name, c); + if(!(c instanceof Cookie)) + throw new TypeError("First parameter is neither a string nor a cookie!"); + if(!this.cookies.get(c.domain)) + this.cookies.set(c.domain, new Map()); + this.cookies.get(c.domain).set(c.name, c); } - forEach(callback) { - this.cookies.forEach(callback); + addFromFile(file) { + JSON.parse(fs.readFileSync(this.file)).forEach(c => this.addCookie(Cookie.fromObject(c))); + } + domains() { + return [...this.cookies.keys()]; + } + *iterValidForRequest(domain, url) { + for(const cookie of this.iter(domain)) + if(cookie.isValidForRequest(url)) + yield cookie; + } + *iterValid() { + for(const cookie of this.iterAll()) + if(!cookie.hasExpired()) + yield cookie; + } + *iterAll() { + for(const domain of this.domains()) + yield* this.iter(domain); + } + *iter(domain) { + for(const cookie of (this.cookies.get(domain) || []).values()) + yield cookie; + } + deleteExpired() { + const filteredCookies = [...this.iterValid()]; + this.cookies = new Map(); + filteredCookies.forEach(c => this.addCookie(c)); } save() { if(typeof this.file !== "string") throw new Error("No file has been specified for this cookie jar!"); // only save cookies that haven't expired - let cookiesToSave = new Map(); - this.forEach(cookie => { - if(!cookie.hasExpired()) - cookiesToSave.set(cookie.name, cookie); - }); - fs.writeFileSync(this.file, JSON.stringify([...cookiesToSave])); + fs.writeFileSync(this.file, JSON.stringify([...this.iterValid()])); } }; diff --git a/src/cookie.mjs b/src/cookie.mjs index 8f3b83d..bc4e31b 100644 --- a/src/cookie.mjs +++ b/src/cookie.mjs @@ -18,18 +18,27 @@ const validatePath = (cookiePath, requestPath) => { return (requestPath + "/").startsWith(cookiePath + "/"); }; +const splitN = (str, sep, n) => { + const splitted = str.split(sep); + if(n < splitted.length - 1) { + splitted[n] = splitted.slice(n).join(sep); + splitted.splice(n + 1); + } + return splitted; +}; + export default class Cookie { constructor(str, url) { if(typeof str !== "string") throw new TypeError("Input not a string"); const splitted = str.split("; "); - [this.name, this.value] = splitted[0].split("="); + [this.name, this.value] = splitN(splitted[0], "=", 1); if(this.value.startsWith("\"") && this.value.endsWith("\"")) this.value = this.value.slice(1, -1); for(let i = 1; i < splitted.length; i++) { - let [k, v] = splitted[i].split("="); + let [k, v] = splitN(splitted[i], "=", 1); k = k.toLowerCase(); if(v) { if(k === "expires") { @@ -40,7 +49,7 @@ export default class Cookie { throw new TypeError("Invalid value for Expires \"" + v + "\"!"); } else if(k === "max-age") { - const seconds = parseInt(v); + const seconds = ~~+v; if(seconds.toString() !== v) throw new TypeError("Invalid value for Max-Age \"" + v + "\"!"); this.expiry = new Date(); diff --git a/src/index.mjs b/src/index.mjs index 3eff5a1..c339f0c 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1,26 +1,32 @@ import fetch from "node-fetch"; import CookieJar from "./cookie-jar"; import Cookie from "./cookie"; +import urlParser from "url"; async function cookieFetch(cookieJars, url, options) { let cookies = ""; + const domains = + urlParser + .parse(url) + .hostname + .split(".") + .map((_, i, a) => a.slice(i).join(".")) + .slice(0, -1); + const addValidFromJar = jar => + domains + .map(d => [...jar.iterValidForRequest(d, url)]) + .reduce((a, b) => [...a, ...b]) + .forEach(c => cookies += c.serialize() + "; "); if(cookieJars) { 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() + "; "; + addValidFromJar(jar); }); } + else if(cookieJars instanceof CookieJar && cookieJars.flags.includes("r")) + addValidFromJar(cookieJars); else throw new TypeError("First paramter is neither a cookie jar nor an array of cookie jars!"); }