mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 14:49:53 +00:00
2d9e2da52e
* feat: move script sdk to one workspace and update importing paths * test: add jest config for enabling tests * fix: failed to build package because of missing .node loader
352 lines
12 KiB
TypeScript
352 lines
12 KiB
TypeScript
import { Cookie as InsomniaCookie, CookieJar as InsomniaCookieJar } from 'insomnia/src/models/cookie-jar';
|
|
import { Cookie as ToughCookie } from 'tough-cookie';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
import { Property, PropertyList } from './properties';
|
|
|
|
export interface CookieOptions {
|
|
id?: string;
|
|
key: string;
|
|
value: string;
|
|
expires?: Date | string;
|
|
maxAge?: string | 'Infinity' | '-Infinity';
|
|
domain?: string;
|
|
path?: string;
|
|
secure?: boolean;
|
|
httpOnly?: boolean;
|
|
hostOnly?: boolean;
|
|
session?: boolean;
|
|
extensions?: { key: string; value: string }[];
|
|
}
|
|
|
|
export class Cookie extends Property {
|
|
readonly _kind: string = 'Cookie';
|
|
protected cookie: ToughCookie;
|
|
private extensions?: { key: string; value: string }[];
|
|
|
|
constructor(cookieDef: CookieOptions | string) {
|
|
super();
|
|
|
|
if (typeof cookieDef === 'string') {
|
|
const cookieDefParsed = Cookie.parse(cookieDef);
|
|
if (!cookieDefParsed) {
|
|
throw Error('failed to parse cookie, the cookie string seems invalid');
|
|
}
|
|
cookieDef = cookieDefParsed;
|
|
}
|
|
|
|
const def = { ...cookieDef };
|
|
this.extensions = def.extensions ? [...def.extensions] : [];
|
|
def.extensions = [];
|
|
|
|
const cookie = ToughCookie.fromJSON(def);
|
|
if (!cookie) {
|
|
throw Error('failed to parse cookie, the cookie string seems invalid');
|
|
}
|
|
|
|
this.id = cookieDef.id || '';
|
|
this.cookie = cookie;
|
|
}
|
|
|
|
static _index = 'key';
|
|
|
|
static isCookie(obj: Property) {
|
|
return '_kind' in obj && obj._kind === 'Cookie';
|
|
}
|
|
|
|
static parse(cookieStr: string) {
|
|
const cookieObj = ToughCookie.parse(cookieStr);
|
|
if (!cookieObj) {
|
|
throw Error('failed to parse cookie, the cookie string seems invalid');
|
|
}
|
|
|
|
const hostOnly = cookieObj.extensions?.includes('HostOnly') || false;
|
|
const session = cookieObj.extensions?.includes('Session') || false;
|
|
if (hostOnly) {
|
|
cookieObj.extensions = cookieObj.extensions?.filter(item => item !== 'HostOnly') || [];
|
|
}
|
|
if (session) {
|
|
cookieObj.extensions = cookieObj.extensions?.filter(item => item !== 'Session') || [];
|
|
}
|
|
|
|
// Tough Cookies extensions works well with string[], but not {key: string; value: string}[]
|
|
const extensions = cookieObj.extensions?.map((entry: string | { key: string; value: string }) => {
|
|
if (typeof entry === 'string') {
|
|
const equalPos = entry.indexOf('=');
|
|
if (equalPos > 0) {
|
|
return { key: entry.slice(0, equalPos), value: entry.slice(equalPos + 1) };
|
|
}
|
|
return { key: entry, value: 'true' };
|
|
} else if (
|
|
'key' in entry &&
|
|
'value' in entry &&
|
|
typeof entry.key === 'string' &&
|
|
typeof entry.value === 'string'
|
|
) {
|
|
return { key: entry.key, value: entry.value };
|
|
} else {
|
|
throw Error('failed to create cookie, extension must be: { key: string; value: string }[]');
|
|
}
|
|
|
|
});
|
|
|
|
return {
|
|
key: cookieObj.key,
|
|
value: cookieObj.value,
|
|
expires: cookieObj.expires || undefined,
|
|
maxAge: `${cookieObj.maxAge}` || undefined,
|
|
domain: cookieObj.domain || undefined,
|
|
path: cookieObj.path || undefined,
|
|
secure: cookieObj.secure || false,
|
|
httpOnly: cookieObj.httpOnly || false,
|
|
hostOnly,
|
|
session,
|
|
extensions: extensions,
|
|
};
|
|
}
|
|
|
|
static stringify(cookie: Cookie) {
|
|
return cookie.toString();
|
|
}
|
|
|
|
static unparseSingle(cookieOpt: CookieOptions) {
|
|
const cookie = new Cookie(cookieOpt);
|
|
if (!cookie) {
|
|
throw Error('failed to unparse cookie, the cookie options seems invalid');
|
|
}
|
|
return cookie.toString();
|
|
}
|
|
|
|
static unparse(cookies: Cookie[]) {
|
|
const cookieStrs = cookies.map(cookie => cookie.toString());
|
|
return cookieStrs.join('; ');
|
|
}
|
|
|
|
toString = () => {
|
|
const hostOnlyPart = this.cookie.hostOnly ? '; HostOnly' : '';
|
|
const sessionPart = this.cookie.extensions?.includes('session') ? '; Session' : '';
|
|
const extensionPart = this.extensions && this.extensions.length > 0 ?
|
|
'; ' + this.extensions.map(ext => `${ext.key}=${ext.value}`).join(';') :
|
|
'';
|
|
|
|
return this.cookie.toString() + hostOnlyPart + sessionPart + extensionPart;
|
|
};
|
|
|
|
valueOf = () => {
|
|
return this.cookie.toJSON().value;
|
|
};
|
|
|
|
get key() {
|
|
return this.cookie.toJSON().key;
|
|
};
|
|
|
|
toJSON = () => {
|
|
return {
|
|
id: this.id,
|
|
key: this.cookie.key,
|
|
value: this.cookie.value,
|
|
expires: this.cookie.expires === 'Infinity' ? undefined : this.cookie.expires,
|
|
maxAge: this.cookie.maxAge,
|
|
domain: this.cookie.domain,
|
|
path: this.cookie.path,
|
|
secure: this.cookie.secure,
|
|
httpOnly: this.cookie.httpOnly,
|
|
hostOnly: this.cookie.hostOnly,
|
|
session: this.cookie.extensions?.includes('session'),
|
|
extensions: this.extensions,
|
|
};
|
|
};
|
|
}
|
|
|
|
export class CookieList extends PropertyList<Cookie> {
|
|
_kind: string = 'CookieList';
|
|
|
|
constructor(cookies: Cookie[]) {
|
|
super(
|
|
Cookie,
|
|
undefined,
|
|
cookies,
|
|
);
|
|
}
|
|
|
|
static isCookieList(obj: object) {
|
|
return '_kind' in obj && obj._kind === 'CookieList';
|
|
}
|
|
}
|
|
|
|
export class CookieObject extends CookieList {
|
|
private cookieJar: CookieJar;
|
|
|
|
constructor(cookieJar: InsomniaCookieJar | null) {
|
|
const cookies = cookieJar
|
|
? cookieJar.cookies.map((cookie: InsomniaCookie): Cookie => {
|
|
let expires: string | Date = '';
|
|
if (cookie.expires) {
|
|
if (typeof cookie.expires === 'number') {
|
|
expires = new Date(cookie.expires);
|
|
} else {
|
|
expires = cookie.expires;
|
|
}
|
|
}
|
|
|
|
return new Cookie({
|
|
id: cookie.id,
|
|
key: cookie.key,
|
|
value: cookie.value,
|
|
expires: expires,
|
|
maxAge: undefined, // not supported in Insomnia
|
|
domain: cookie.domain,
|
|
path: cookie.path,
|
|
secure: cookie.secure,
|
|
httpOnly: cookie.httpOnly,
|
|
hostOnly: cookie.hostOnly,
|
|
session: undefined, // not supported in Insomnia
|
|
extensions: undefined, // TODO: its format from Insomnia is unknown
|
|
});
|
|
})
|
|
: [];
|
|
|
|
super(cookies);
|
|
const scriptCookieJar = cookieJar ? new CookieJar(cookieJar.name, cookies) : new CookieJar('', []);
|
|
this.cookieJar = scriptCookieJar;
|
|
this.typeClass = Cookie;
|
|
}
|
|
|
|
jar() {
|
|
return this.cookieJar;
|
|
}
|
|
}
|
|
|
|
export class CookieJar {
|
|
// CookieJar from tough-cookie can not be used, as it will failed in comparing context location and cookies' domain
|
|
// as it reads location from the browser window, it is "localhost"
|
|
private jar: Map<string, Map<string, Cookie>>; // Map<domain, Map<cookieKey, cookieObject>>
|
|
private jarName: string;
|
|
|
|
constructor(jarName: string, cookies?: Cookie[]) {
|
|
this.jarName = jarName;
|
|
this.jar = new Map();
|
|
|
|
if (cookies) {
|
|
cookies.forEach(cookie => {
|
|
const properties = cookie.toJSON();
|
|
if (!properties.domain) {
|
|
throw Error(`domain is not specified for the cookie ${cookie.key}`);
|
|
}
|
|
|
|
const domainCookies = this.jar.get(properties.domain) || new Map();
|
|
this.jar.set(properties.domain, domainCookies.set(properties.key, cookie));
|
|
});
|
|
}
|
|
}
|
|
|
|
set(url: string, key: string, value: string | CookieOptions, cb: (error?: Error, cookie?: Cookie) => void) {
|
|
const domainCookies = this.jar.get(url) || new Map();
|
|
if (typeof value === 'string') {
|
|
const domainCookie = new Cookie({
|
|
key: key,
|
|
value: value,
|
|
domain: url,
|
|
});
|
|
this.jar.set(url, domainCookies.set(key, domainCookie));
|
|
cb(undefined, domainCookie);
|
|
} else {
|
|
const domainCookie = new Cookie(value);
|
|
this.jar.set(url, domainCookies.set(key, domainCookie));
|
|
cb(undefined, domainCookie);
|
|
}
|
|
}
|
|
|
|
// TODO: create a better method for setting cookie, or overload the above method
|
|
// set(
|
|
// url: string,
|
|
// info: { name: string; value: string; httpOnly: boolean },
|
|
// cb: (error?: Error, cookie?: Cookie) => void,
|
|
// ) {
|
|
// try {
|
|
// const cookie = new ToughCookie({ key: info.name, value: info.value, httpOnly: info.httpOnly });
|
|
// this.jar.setCookieSync(cookie, url, { http: info.httpOnly });
|
|
// cb(undefined, new Cookie({ key: info.name, value: info.value, httpOnly: info.httpOnly }));
|
|
// } catch (e) {
|
|
// cb(e, undefined);
|
|
// }
|
|
// }
|
|
|
|
get(url: string, name: string, cb: (error?: Error, cookie?: Cookie) => void) {
|
|
const domainCookies = this.jar.get(url) || new Map();
|
|
cb(undefined, domainCookies.get(name));
|
|
}
|
|
|
|
getAll(url: string, cb: (error?: Error, cookies?: Cookie[]) => void) {
|
|
const domainCookies = this.jar.get(url) || new Map();
|
|
cb(
|
|
undefined,
|
|
Array.from(domainCookies.values()),
|
|
);
|
|
}
|
|
|
|
unset(url: string, name: string, cb: (error?: Error | null) => void) {
|
|
const domainCookies = this.jar.get(url);
|
|
if (!domainCookies) {
|
|
cb(undefined);
|
|
} else {
|
|
domainCookies.delete(name);
|
|
cb(undefined);
|
|
}
|
|
}
|
|
|
|
clear(url: string, cb: (error?: Error | null) => void) {
|
|
this.jar.delete(url);
|
|
cb(undefined);
|
|
}
|
|
|
|
toInsomniaCookieJar() {
|
|
const cookies = new Array<Partial<InsomniaCookie>>();
|
|
Array.from(this.jar.values())
|
|
.forEach((domainCookies: Map<string, Cookie>) => {
|
|
Array.from(domainCookies.values()).forEach(cookie => {
|
|
const cookieObj = cookie.toJSON();
|
|
cookies.push({
|
|
id: cookieObj.id,
|
|
key: cookieObj.key,
|
|
value: cookieObj.value,
|
|
expires: cookieObj.expires,
|
|
domain: cookieObj.domain || undefined,
|
|
path: cookieObj.path || undefined,
|
|
secure: cookieObj.secure,
|
|
httpOnly: cookieObj.httpOnly,
|
|
extensions: cookieObj.extensions || undefined,
|
|
creation: undefined,
|
|
creationIndex: undefined,
|
|
hostOnly: cookieObj.hostOnly || undefined,
|
|
pathIsDefault: undefined,
|
|
lastAccessed: undefined,
|
|
});
|
|
});
|
|
});
|
|
|
|
return {
|
|
name: this.jarName,
|
|
cookies,
|
|
};
|
|
}
|
|
}
|
|
|
|
export function mergeCookieJar(
|
|
originalCookieJar: InsomniaCookieJar,
|
|
updatedCookieJar: { name: string; cookies: Partial<InsomniaCookie>[] },
|
|
): InsomniaCookieJar {
|
|
const cookiesWithId = updatedCookieJar.cookies.map((cookie): InsomniaCookie => {
|
|
if (!cookie.id) {
|
|
// this follows the genration apporach in the `cookie-list.tsx`
|
|
cookie.id = uuidv4();
|
|
}
|
|
return cookie as InsomniaCookie;
|
|
});
|
|
|
|
return {
|
|
...originalCookieJar,
|
|
cookies: cookiesWithId,
|
|
};
|
|
}
|