flumm-fetch-cookies/src/cookie-jar.mjs
2019-11-25 23:14:34 +01:00

97 lines
3.6 KiB
JavaScript

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(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(cookie, fromURL) {
if(typeof cookie === "string") {
try {
cookie = new Cookie(cookie, fromURL);
}
catch(error) {
if(error instanceof CookieParseError) {
console.warn("Ignored cookie: " + cookie);
console.warn("Reason: " + error.message);
return false;
}
throw error;
}
}
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() {
return this.cookies.keys();
}
*cookiesDomain(domain) {
for(const cookie of (this.cookies.get(domain) || []).values())
yield cookie;
}
*cookiesValid(withSession) {
for(const cookie of this.cookiesAll())
if(!cookie.hasExpired(!withSession))
yield cookie;
}
*cookiesAll() {
for(const domain of this.domains())
yield* this.cookiesDomain(domain);
}
*cookiesValidForRequest(requestURL) {
const namesYielded = new Set(),
domains = url
.parse(requestURL)
.hostname
.split(".")
.map((_, i, a) => a.slice(i).join("."))
.slice(0, -1);
for(const domain of domains) {
for(const cookie of this.cookiesDomain(domain)) {
if(cookie.isValidForRequest(requestURL)
&& !namesYielded.has(cookie.name)) {
namesYielded.add(cookie.name);
yield cookie;
}
}
}
}
deleteExpired(sessionEnded) {
const validCookies = [...this.cookiesValid(!sessionEnded)];
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)]));
}
};