fexios
Version:
Fetch based HTTP client with similar API to axios for browser and Node.js
279 lines (275 loc) • 7.96 kB
JavaScript
'use strict';
/**
* Cookie Jar
*
* @author dragon-fish <dragon-fish@qq.com>
* @license MIT
*/
class CookieJar {
cookies = /* @__PURE__ */ new Map();
/**
* Set a cookie
*/
setCookie(cookie, domain, path) {
const effectiveDomain = cookie.domain ?? domain;
const effectivePath = cookie.path ?? path ?? "/";
const hostOnly = cookie.domain === void 0 && domain !== void 0;
const key = this.getCookieKey(cookie.name, effectiveDomain, effectivePath);
const cookieWithTime = {
...cookie,
domain: effectiveDomain,
path: effectivePath,
_hostOnly: cookie._hostOnly ?? hostOnly,
_createdAt: cookie.maxAge !== void 0 ? /* @__PURE__ */ new Date() : cookie._createdAt
};
this.cookies.set(key, cookieWithTime);
}
/**
* Get a cookie
*/
getCookie(name, domain, path) {
const exactKey = this.getCookieKey(name, domain, path);
let cookie = this.cookies.get(exactKey);
if (cookie && !this.isCookieExpired(cookie)) {
return cookie;
}
for (const storedCookie of this.cookies.values()) {
if (storedCookie.name === name && this.isCookieMatch(storedCookie, domain, path) && !this.isCookieExpired(storedCookie)) {
return storedCookie;
}
}
return void 0;
}
/**
* Get all matching cookies
*/
getCookies(domain, path) {
const result = [];
for (const cookie of this.cookies.values()) {
if (this.isCookieMatch(cookie, domain, path) && !this.isCookieExpired(cookie)) {
result.push(cookie);
}
}
return result;
}
/**
* Delete a cookie
*/
deleteCookie(name, domain, path) {
const key = this.getCookieKey(name, domain, path);
return this.cookies.delete(key);
}
/**
* Clear all cookies
*/
clear() {
this.cookies.clear();
}
/**
* Clean expired cookies
*/
cleanExpiredCookies() {
let cleanedCount = 0;
for (const [key, cookie] of this.cookies.entries()) {
if (this.isCookieExpired(cookie)) {
this.cookies.delete(key);
cleanedCount++;
}
}
return cleanedCount;
}
/**
* Get all cookies (including expired ones)
*/
getAllCookies(domain, path) {
const result = [];
for (const cookie of this.cookies.values()) {
if (this.isCookieMatch(cookie, domain, path)) {
result.push(cookie);
}
}
return result;
}
/**
* Parse cookies from Set-Cookie header
*/
parseSetCookieHeader(setCookieHeader, domain, path) {
const cookieStrings = this.splitSetCookieHeader(setCookieHeader);
for (const cookieStr of cookieStrings) {
const cookie = this.parseCookieString(cookieStr);
if (cookie) {
this.setCookie(cookie, domain, path);
}
}
}
/**
* Generate Cookie header string
*/
getCookieHeader(domain, path) {
const cookies = this.getCookies(domain, path);
return cookies.filter((cookie) => !this.isCookieExpired(cookie)).map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
}
/**
* Get the unique key for a cookie
*/
getCookieKey(name, domain, path) {
return `${name}:${domain || "*"}:${path || "/"}`;
}
/**
* Check if cookie matches the given domain and path
*/
isCookieMatch(cookie, domain, path) {
if (domain) {
if (cookie.domain) {
if (cookie._hostOnly) {
if (cookie.domain !== domain) return false;
} else {
if (!this.isDomainMatch(cookie.domain, domain)) return false;
}
} else {
return false;
}
}
if (path && cookie.path && !this.isPathMatch(cookie.path, path)) {
return false;
}
return true;
}
/**
* Check if domain matches
*/
isDomainMatch(cookieDomain, requestDomain) {
const cd = cookieDomain.startsWith(".") ? cookieDomain.substring(1) : cookieDomain;
if (requestDomain === cd) {
return true;
}
return requestDomain.endsWith("." + cd);
}
/**
* Check if path matches
*/
isPathMatch(cookiePath, requestPath) {
if (cookiePath === requestPath) {
return true;
}
if (requestPath.startsWith(cookiePath)) {
return cookiePath.endsWith("/") || requestPath[cookiePath.length] === "/";
}
return false;
}
/**
* Check if cookie is expired
*/
isCookieExpired(cookie) {
const now = /* @__PURE__ */ new Date();
if (cookie.expires) {
return now > cookie.expires;
}
if (cookie.maxAge !== void 0 && cookie._createdAt) {
const expirationTime = new Date(
cookie._createdAt.getTime() + cookie.maxAge * 1e3
);
return now > expirationTime;
}
return false;
}
/**
* Parse cookie string
*/
parseCookieString(cookieStr) {
const parts = cookieStr.split(";").map((part) => part.trim());
if (parts.length === 0) return null;
const [nameValue] = parts;
const equalIndex = nameValue.indexOf("=");
if (equalIndex === -1) return null;
const name = nameValue.substring(0, equalIndex).trim();
const value = nameValue.substring(equalIndex + 1).trim();
const cookie = { name, value };
for (let i = 1; i < parts.length; i++) {
const part = parts[i];
const equalIndex2 = part.indexOf("=");
if (equalIndex2 === -1) {
const attr = part.toLowerCase();
if (attr === "secure") {
cookie.secure = true;
} else if (attr === "httponly") {
cookie.httpOnly = true;
}
} else {
const attrName = part.substring(0, equalIndex2).trim().toLowerCase();
const attrValue = part.substring(equalIndex2 + 1).trim();
switch (attrName) {
case "domain":
cookie.domain = attrValue;
break;
case "path":
cookie.path = attrValue;
break;
case "expires":
cookie.expires = new Date(attrValue);
break;
case "max-age":
cookie.maxAge = parseInt(attrValue, 10);
break;
case "samesite":
cookie.sameSite = attrValue;
break;
}
}
}
return cookie;
}
/**
* Safely split a possibly-combined Set-Cookie header string into individual cookie strings.
* Split on commas that are followed by the start of a new cookie-pair (name=),
* avoiding commas inside Expires attribute values.
*/
splitSetCookieHeader(header) {
if (!header) return [];
const parts = header.split(/,(?=\s*[^;=]+=)/);
return parts.map((p) => p.trim()).filter((p) => p.length > 0);
}
}
const pluginCookieJar = {
name: "fexios-plugin-cookie-jar",
install(app) {
const cookieJar = new CookieJar();
app.interceptors.request.use((ctx) => {
const url = new URL(ctx.url);
const cookieHeader = cookieJar.getCookieHeader(url.hostname, url.pathname);
if (cookieHeader) {
ctx.headers = {
...ctx.headers,
Cookie: cookieHeader
};
}
return ctx;
});
app.interceptors.response.use((ctx) => {
const url = new URL(ctx.url);
const headersAny = ctx.rawResponse?.headers;
const host = url.hostname;
const reqPath = url.pathname;
const getSetCookie = typeof headersAny?.getSetCookie === "function" ? headersAny.getSetCookie.bind(headersAny) : void 0;
if (getSetCookie) {
const list = getSetCookie();
if (Array.isArray(list) && list.length > 0) {
for (const sc of list) {
cookieJar.parseSetCookieHeader(sc, host, reqPath);
}
}
} else {
const setCookieHeader = ctx.rawResponse?.headers?.get("set-cookie");
if (setCookieHeader) {
cookieJar.parseSetCookieHeader(setCookieHeader, host, reqPath);
}
}
return ctx;
});
app.cookieJar = cookieJar;
return app;
}
};
exports.CookieJar = CookieJar;
exports.pluginCookieJar = pluginCookieJar;
//# sourceMappingURL=index.cjs.map