merge latest node-fetch-cookies version
This commit is contained in:
@ -1,30 +1,46 @@
|
||||
import Cookie from "./cookie.mjs";
|
||||
import {promises as fs} from "fs";
|
||||
import url from "url";
|
||||
import Cookie from "./cookie.mjs";
|
||||
import {paramError, CookieParseError} from "./errors.mjs";
|
||||
|
||||
export default class CookieJar {
|
||||
constructor() {
|
||||
constructor(file, flags = "rw", cookies) {
|
||||
this.flags = flags;
|
||||
this.file = file;
|
||||
this.cookies = new Map();
|
||||
if(typeof this.flags !== "string")
|
||||
throw paramError("First", "flags", "new CookieJar()", "string");
|
||||
if(this.file && typeof this.file !== "string")
|
||||
throw paramError("Second", "file", "new CookieJar()", "string");
|
||||
if(Array.isArray(cookies)) {
|
||||
if(!cookies.every(c => c instanceof Cookie))
|
||||
throw paramError("Third", "cookies", "new CookieJar()", "[Cookie]");
|
||||
cookies.forEach(cookie => this.addCookie(cookie));
|
||||
}
|
||||
else if(cookies instanceof Cookie)
|
||||
this.addCookie(cookies);
|
||||
else if(cookies)
|
||||
throw paramError("Third", "cookies", "new CookieJar()", ["[Cookie]", "Cookie"]);
|
||||
}
|
||||
addCookie(c, fromURL) {
|
||||
if(typeof c === "string") {
|
||||
addCookie(cookie, fromURL) {
|
||||
if(typeof cookie === "string") {
|
||||
try {
|
||||
c = new Cookie(c, fromURL);
|
||||
cookie = new Cookie(cookie, fromURL);
|
||||
}
|
||||
catch(error) {
|
||||
if(error.name === "CookieParseError") {
|
||||
console.warn("Ignored cookie: " + c);
|
||||
if(error instanceof CookieParseError) {
|
||||
console.warn("Ignored cookie: " + cookie);
|
||||
console.warn("Reason: " + error.message);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
throw error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
else 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);
|
||||
else if(!(cookie instanceof Cookie))
|
||||
throw paramError("First", "cookie", "CookieJar.addCookie()", ["string", "Cookie"]);
|
||||
if(!this.cookies.get(cookie.domain))
|
||||
this.cookies.set(cookie.domain, new Map());
|
||||
this.cookies.get(cookie.domain).set(cookie.name, cookie);
|
||||
return true;
|
||||
}
|
||||
domains() {
|
||||
@ -44,7 +60,7 @@ export default class CookieJar {
|
||||
yield* this.cookiesDomain(domain);
|
||||
}
|
||||
*cookiesValidForRequest(requestURL) {
|
||||
const namesYielded = [],
|
||||
const namesYielded = new Set(),
|
||||
domains = url
|
||||
.parse(requestURL)
|
||||
.hostname
|
||||
@ -54,8 +70,8 @@ export default class CookieJar {
|
||||
for(const domain of domains) {
|
||||
for(const cookie of this.cookiesDomain(domain)) {
|
||||
if(cookie.isValidForRequest(requestURL)
|
||||
&& namesYielded.every(name => name !== cookie.name)) {
|
||||
namesYielded.push(cookie.name);
|
||||
&& !namesYielded.has(cookie.name)) {
|
||||
namesYielded.add(cookie.name);
|
||||
yield cookie;
|
||||
}
|
||||
}
|
||||
@ -66,4 +82,15 @@ export default class CookieJar {
|
||||
this.cookies = new Map();
|
||||
validCookies.forEach(c => this.addCookie(c));
|
||||
}
|
||||
async load(file = this.file) {
|
||||
if(typeof file !== "string")
|
||||
throw new Error("No file has been specified for this cookie jar!");
|
||||
JSON.parse(await fs.readFile(file)).forEach(c => this.addCookie(Cookie.fromObject(c)));
|
||||
}
|
||||
async save(file = this.file) {
|
||||
if(typeof file !== "string")
|
||||
throw new Error("No file has been specified for this cookie jar!");
|
||||
// only save cookies that haven't expired
|
||||
await fs.writeFile(this.file, JSON.stringify([...this.cookiesValid(false)]));
|
||||
}
|
||||
};
|
||||
|
@ -1,11 +1,5 @@
|
||||
import url from "url";
|
||||
|
||||
class CookieParseError extends Error {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.name = "CookieParseError";
|
||||
}
|
||||
}
|
||||
import {paramError, CookieParseError} from "./errors.mjs";
|
||||
|
||||
const validateHostname = (cookieHostname, requestHostname, subdomains) => {
|
||||
cookieHostname = cookieHostname.toLowerCase();
|
||||
@ -37,12 +31,17 @@ const splitN = (str, sep, n) => {
|
||||
export default class Cookie {
|
||||
constructor(str, requestURL) {
|
||||
if(typeof str !== "string")
|
||||
throw new TypeError("First parameter is not a string!");
|
||||
throw paramError("First", "str", "new Cookie()", "string");
|
||||
if(typeof requestURL !== "string")
|
||||
throw paramError("Second", "requestURL", "new Cookie()", "string");
|
||||
|
||||
// check if url is valid
|
||||
new url.URL(requestURL);
|
||||
|
||||
const splitted = str.split("; ");
|
||||
[this.name, this.value] = splitN(splitted[0], "=", 1);
|
||||
if(!this.name)
|
||||
throw new CookieParseError("Invalid cookie name \"" + this.name + "\"");
|
||||
throw new CookieParseError("Invalid cookie name \"" + this.name + "\"!");
|
||||
if(this.value.startsWith("\"") && this.value.endsWith("\""))
|
||||
this.value = this.value.slice(1, -1);
|
||||
|
||||
@ -56,7 +55,8 @@ export default class Cookie {
|
||||
if(this.expiry) // max-age has precedence over expires
|
||||
continue;
|
||||
if(!/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2}[ -](?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[ -]\d{2,4} \d{2}:\d{2}:\d{2} GMT$/.test(v)
|
||||
|| (this.expiry = new Date(v)) === "Invalid Date")
|
||||
|| (this.expiry = new Date(v)).toString() === "Invalid Date"
|
||||
|| this.expiry.getTime() < 0)
|
||||
throw new CookieParseError("Invalid value for Expires \"" + v + "\"!");
|
||||
}
|
||||
else if(k === "max-age") {
|
||||
@ -93,7 +93,7 @@ export default class Cookie {
|
||||
|
||||
if(this.name.toLowerCase().startsWith("__secure-") && (!this.secure || parsedURL.protocol !== "https:"))
|
||||
throw new CookieParseError("Cookie has \"__Secure-\" prefix but \"Secure\" isn't set or the cookie is not set via https!");
|
||||
if(this.name.toLowerCase().startsWith("__host-") && (!this.secure || parsedURL.protocol !== "https:" || this.domain || (this.path && this.path !== "/")))
|
||||
if(this.name.toLowerCase().startsWith("__host-") && (!this.secure || parsedURL.protocol !== "https:" || this.domain || this.path !== "/"))
|
||||
throw new CookieParseError("Cookie has \"__Host-\" prefix but \"Secure\" isn't set, the cookie is not set via https, \"Domain\" is set or \"Path\" is not equal to \"/\"!");
|
||||
|
||||
// assign defaults
|
||||
|
12
src/errors.mjs
Normal file
12
src/errors.mjs
Normal file
@ -0,0 +1,12 @@
|
||||
export class CookieParseError extends Error {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.name = "CookieParseError";
|
||||
}
|
||||
};
|
||||
|
||||
export function paramError(position, paramName, functionName, validTypes) {
|
||||
validTypes = [validTypes].flat().map(t => "\"" + t + "\"");
|
||||
validTypes = validTypes.slice(0, -1).join(", ") + (validTypes.length > 1 ? " or " : "") + validTypes.slice(-1);
|
||||
return new TypeError(`${position} parameter "${paramName}" passed to "${functionName}" is not of type ${validTypes}!`);
|
||||
};
|
@ -1,13 +1,12 @@
|
||||
import fetch from "flumm-fetch";
|
||||
import _fetch from "flumm-fetch";
|
||||
import CookieJar from "./cookie-jar.mjs";
|
||||
import Cookie from "./cookie.mjs";
|
||||
|
||||
const cookieJar = new CookieJar();
|
||||
|
||||
export default async function cookieFetch(url, options) {
|
||||
export default async function fetch(url, options) {
|
||||
let cookies = "";
|
||||
[...cookieJar.cookiesValidForRequest(url)]
|
||||
.filter((v, i, a) => a.slice(0, i).every(c => c.name !== v.name)) // filter cookies with duplicate names
|
||||
.forEach(c => cookies += c.serialize() + "; ");
|
||||
|
||||
if(cookies) {
|
||||
@ -30,3 +29,4 @@ export default async function cookieFetch(url, options) {
|
||||
}
|
||||
|
||||
export {cookieJar, CookieJar, Cookie};
|
||||
|
||||
|
Reference in New Issue
Block a user