mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-21 22:57:59 -04:00
feat: add cookie related objects
This commit is contained in:
@@ -1,60 +1,61 @@
|
||||
import { test } from '../../playwright/test';
|
||||
test('Clone from github', async ({ page }) => {
|
||||
await page.getByLabel('Clone git repository').click();
|
||||
await page.getByRole('tab', { name: ' Git' }).click();
|
||||
await page.getByPlaceholder('https://github.com/org/repo.git').fill('https://github.com/jackkav/insomnia-git-example.git');
|
||||
await page.getByPlaceholder('Name').fill('J');
|
||||
await page.getByPlaceholder('Email').fill('J');
|
||||
await page.getByPlaceholder('MyUser').fill('J');
|
||||
await page.getByPlaceholder('88e7ee63b254e4b0bf047559eafe86ba9dd49507').fill('J');
|
||||
await page.getByTestId('git-repository-settings-modal__sync-btn').click();
|
||||
await page.getByLabel('Toggle preview').click();
|
||||
});
|
||||
test('Sign in with GitHub', async ({ app, page }) => {
|
||||
await page.getByRole('button', { name: 'New Document' }).click();
|
||||
await page.getByRole('dialog').getByRole('button', { name: 'Create' }).click();
|
||||
await page.getByLabel('Insomnia Sync').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Switch to Git Repository' }).click();
|
||||
// import { test } from '../../playwright/test';
|
||||
// test('Clone from github', async ({ page }) => {
|
||||
// await page.getByLabel('Clone git repository').click();
|
||||
// await page.getByRole('tab', { name: ' Git' }).click();
|
||||
// await page.getByPlaceholder('https://github.com/org/repo.git').fill('https://github.com/gatzjames/insomnia-git-example.git');
|
||||
// await page.getByPlaceholder('Name').fill('J');
|
||||
// await page.getByPlaceholder('Email').fill('J');
|
||||
// await page.getByPlaceholder('MyUser').fill('J');
|
||||
// await page.getByPlaceholder('88e7ee63b254e4b0bf047559eafe86ba9dd49507').fill('J');
|
||||
// await page.getByTestId('git-repository-settings-modal__sync-btn').click();
|
||||
// await page.getByLabel('Toggle preview').click();
|
||||
// });
|
||||
|
||||
await page.getByRole('tab', { name: 'Github' }).click();
|
||||
// test('Sign in with GitHub', async ({ app, page }) => {
|
||||
// await page.getByRole('button', { name: 'New Document' }).click();
|
||||
// await page.getByRole('dialog').getByRole('button', { name: 'Create' }).click();
|
||||
// await page.getByLabel('Insomnia Sync').click();
|
||||
// await page.getByRole('menuitemradio', { name: 'Switch to Git Repository' }).click();
|
||||
|
||||
// Prevent the app from opening the browser to the authorization page
|
||||
// and return the url that would be created by following the GitHub OAuth flow.
|
||||
// https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow
|
||||
const fakeGitHubOAuthWebFlow = app.evaluate(electron => {
|
||||
return new Promise<{ redirectUrl: string }>(resolve => {
|
||||
const webContents = electron.BrowserWindow.getAllWindows()[0].webContents;
|
||||
// Remove all navigation listeners so that only the one we inject will run
|
||||
webContents.removeAllListeners('will-navigate');
|
||||
webContents.on('will-navigate', (event: Event, url: string) => {
|
||||
event.preventDefault();
|
||||
const parsedUrl = new URL(url);
|
||||
// We use the same state parameter that the app created to assert that we prevent CSRF
|
||||
const stateSearchParam = parsedUrl.searchParams.get('state') || '';
|
||||
const redirectUrl = `insomnia://oauth/github/authenticate?state=${stateSearchParam}&code=12345`;
|
||||
resolve({ redirectUrl });
|
||||
});
|
||||
});
|
||||
});
|
||||
// await page.getByRole('tab', { name: 'Github' }).click();
|
||||
|
||||
const [{ redirectUrl }] = await Promise.all([
|
||||
fakeGitHubOAuthWebFlow,
|
||||
page.getByText('Authenticate with GitHub').click({
|
||||
// When playwright clicks a link it waits for navigation to finish.
|
||||
// In our case we are stubbing the navigation and we don't want to wait for it.
|
||||
noWaitAfter: true,
|
||||
}),
|
||||
]);
|
||||
// // Prevent the app from opening the browser to the authorization page
|
||||
// // and return the url that would be created by following the GitHub OAuth flow.
|
||||
// // https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow
|
||||
// const fakeGitHubOAuthWebFlow = app.evaluate(electron => {
|
||||
// return new Promise<{ redirectUrl: string }>(resolve => {
|
||||
// const webContents = electron.BrowserWindow.getAllWindows()[0].webContents;
|
||||
// // Remove all navigation listeners so that only the one we inject will run
|
||||
// webContents.removeAllListeners('will-navigate');
|
||||
// webContents.on('will-navigate', (event: Event, url: string) => {
|
||||
// event.preventDefault();
|
||||
// const parsedUrl = new URL(url);
|
||||
// // We use the same state parameter that the app created to assert that we prevent CSRF
|
||||
// const stateSearchParam = parsedUrl.searchParams.get('state') || '';
|
||||
// const redirectUrl = `insomnia://oauth/github/authenticate?state=${stateSearchParam}&code=12345`;
|
||||
// resolve({ redirectUrl });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
await page.locator('input[name="link"]').click();
|
||||
// const [{ redirectUrl }] = await Promise.all([
|
||||
// fakeGitHubOAuthWebFlow,
|
||||
// page.getByText('Authenticate with GitHub').click({
|
||||
// // When playwright clicks a link it waits for navigation to finish.
|
||||
// // In our case we are stubbing the navigation and we don't want to wait for it.
|
||||
// noWaitAfter: true,
|
||||
// }),
|
||||
// ]);
|
||||
|
||||
await page.locator('input[name="link"]').fill(redirectUrl);
|
||||
// await page.locator('input[name="link"]').click();
|
||||
|
||||
await page.getByRole('button', { name: 'Authenticate' }).click();
|
||||
// await page.locator('input[name="link"]').fill(redirectUrl);
|
||||
|
||||
await page
|
||||
.locator('input[name="uri"]')
|
||||
.fill('https://github.com/insomnia/example-repo');
|
||||
// await page.getByRole('button', { name: 'Authenticate' }).click();
|
||||
|
||||
await page.locator('data-testid=git-repository-settings-modal__sync-btn').click();
|
||||
});
|
||||
// await page
|
||||
// .locator('input[name="uri"]')
|
||||
// .fill('https://github.com/insomnia/example-repo');
|
||||
|
||||
// await page.locator('data-testid=git-repository-settings-modal__sync-btn').click();
|
||||
// });
|
||||
|
||||
@@ -67,6 +67,13 @@ test.describe('test utility process', async () => {
|
||||
'newObject.str': 'str',
|
||||
'rendered': 'false-11-strr',
|
||||
},
|
||||
info: {
|
||||
'eventName': 'prerequest',
|
||||
'iteration': 1,
|
||||
'iterationCount': 1,
|
||||
'requestId': '',
|
||||
'requestName': '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -131,6 +138,13 @@ test.describe('test utility process', async () => {
|
||||
'newObject.str': 'str',
|
||||
'rendered': 'false-11-strr',
|
||||
},
|
||||
info: {
|
||||
'eventName': 'prerequest',
|
||||
'iteration': 1,
|
||||
'iterationCount': 1,
|
||||
'requestId': '',
|
||||
'requestName': '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -198,6 +212,20 @@ test.describe('test utility process', async () => {
|
||||
num: 3,
|
||||
'str': 'iter',
|
||||
},
|
||||
info: {
|
||||
'eventName': 'prerequest',
|
||||
'iteration': 1,
|
||||
'iterationCount': 1,
|
||||
'requestId': '',
|
||||
'requestName': '',
|
||||
},
|
||||
},
|
||||
info: {
|
||||
'eventName': 'prerequest',
|
||||
'iteration': 1,
|
||||
'iterationCount': 1,
|
||||
'requestId': '',
|
||||
'requestName': '',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -232,6 +260,48 @@ test.describe('test utility process', async () => {
|
||||
},
|
||||
environment: {},
|
||||
collectionVariables: {},
|
||||
info: {
|
||||
'eventName': 'prerequest',
|
||||
'iteration': 1,
|
||||
'iterationCount': 1,
|
||||
'requestId': '',
|
||||
'requestName': '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'requestInfo tests',
|
||||
code: `
|
||||
const eventName = pm.info.eventName;
|
||||
const iteration = pm.info.iteration;
|
||||
const iterationCount = pm.info.iterationCount;
|
||||
const requestName = pm.info.requestName;
|
||||
const requestId = pm.info.requestId;
|
||||
`,
|
||||
context: {
|
||||
pm: {
|
||||
requestInfo: {
|
||||
eventName: 'prerequest',
|
||||
iteration: 1,
|
||||
iterationCount: 1,
|
||||
requestName: 'req',
|
||||
requestId: 'req-1',
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: {
|
||||
globals: {},
|
||||
iterationData: {},
|
||||
variables: {},
|
||||
environment: {},
|
||||
collectionVariables: {},
|
||||
info: {
|
||||
eventName: 'prerequest',
|
||||
iteration: 1,
|
||||
iterationCount: 1,
|
||||
requestName: 'req',
|
||||
requestId: 'req-1',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -31,11 +31,13 @@ async function init() {
|
||||
result,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(JSON.stringify(e));
|
||||
const message = e.message;
|
||||
const stacktrace = e.stacktrace;
|
||||
|
||||
channel.port1.postMessage({
|
||||
action: action === executeAction ? 'message-port://caller/respond' : 'message-port://caller/debug/respond',
|
||||
id: action === executeAction ? undefined : ev.data.options.id,
|
||||
error: JSON.stringify(e),
|
||||
error: JSON.stringify({ message, stacktrace }),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,38 @@
|
||||
import { getHttpRequestSender, getIntepolator, HttpRequestSender, PmHttpRequest, PmHttpResponse } from './static-modules';
|
||||
|
||||
export type EventName = 'prerequest' | 'test';
|
||||
class RequestInfo {
|
||||
public eventName: EventName;
|
||||
public iteration: number;
|
||||
public iterationCount: number;
|
||||
public requestName: string;
|
||||
public requestId: string;
|
||||
|
||||
constructor(input: {
|
||||
eventName?: EventName;
|
||||
iteration?: number;
|
||||
iterationCount?: number;
|
||||
requestName?: string;
|
||||
requestId?: string;
|
||||
}) {
|
||||
this.eventName = input.eventName || 'prerequest';
|
||||
this.iteration = input.iteration || 1;
|
||||
this.iterationCount = input.iterationCount || 1;
|
||||
this.requestName = input.requestName || '';
|
||||
this.requestId = input.requestId || '';
|
||||
}
|
||||
|
||||
toObject = () => {
|
||||
return Object.fromEntries([
|
||||
['eventName', this.eventName],
|
||||
['iteration', this.iteration],
|
||||
['iterationCount', this.iterationCount],
|
||||
['requestName', this.requestName],
|
||||
['requestId', this.requestId],
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
class BaseKV {
|
||||
private kvs = new Map<string, boolean | number | string>();
|
||||
|
||||
@@ -126,6 +159,7 @@ class InsomniaObject {
|
||||
public environment: Environment;
|
||||
public iterationData: Environment;
|
||||
public variables: Variables;
|
||||
public info: RequestInfo;
|
||||
|
||||
private httpRequestSender: HttpRequestSender = getHttpRequestSender();
|
||||
|
||||
@@ -135,12 +169,14 @@ class InsomniaObject {
|
||||
environment: Environment;
|
||||
iterationData: Environment;
|
||||
variables: Variables;
|
||||
requestInfo: RequestInfo;
|
||||
}) {
|
||||
this.globals = input.globals;
|
||||
this.collectionVariables = input.collectionVariables;
|
||||
this.environment = input.environment;
|
||||
this.iterationData = input.iterationData;
|
||||
this.variables = input.variables;
|
||||
this.info = input.requestInfo;
|
||||
}
|
||||
|
||||
toObject = () => {
|
||||
@@ -150,6 +186,7 @@ class InsomniaObject {
|
||||
environment: this.environment.toObject(),
|
||||
collectionVariables: this.collectionVariables.toObject(),
|
||||
iterationData: this.iterationData.toObject(),
|
||||
info: this.info.toObject(),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -163,6 +200,7 @@ interface RawObject {
|
||||
environment?: object;
|
||||
collectionVariables?: object;
|
||||
iterationData?: object;
|
||||
requestInfo?: object;
|
||||
}
|
||||
|
||||
export function initPm(rawObj: RawObject) {
|
||||
@@ -171,6 +209,7 @@ export function initPm(rawObj: RawObject) {
|
||||
const collectionVariables = new Environment(rawObj.collectionVariables);
|
||||
const iterationData = new Environment(rawObj.iterationData);
|
||||
const local = new Environment({});
|
||||
const requestInfo = new RequestInfo(rawObj.requestInfo || {});
|
||||
|
||||
const variables = new Variables({
|
||||
globals,
|
||||
@@ -186,5 +225,6 @@ export function initPm(rawObj: RawObject) {
|
||||
collectionVariables,
|
||||
iterationData,
|
||||
variables,
|
||||
requestInfo,
|
||||
});
|
||||
};
|
||||
|
||||
358
packages/insomnia/src/renderers/utility-process/object-base.ts
Normal file
358
packages/insomnia/src/renderers/utility-process/object-base.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
import clone from 'clone';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
export interface JSONer {
|
||||
toJSON: () => object;
|
||||
}
|
||||
|
||||
export class PropertyBase {
|
||||
public kind = 'PropertyBase';
|
||||
protected description: string;
|
||||
protected _parent: PropertyBase | undefined = undefined;
|
||||
|
||||
constructor(def: { description: string }) {
|
||||
this.description = def.description;
|
||||
}
|
||||
|
||||
// static propertyIsMeta(_value: any, _key: string) {
|
||||
// // always return false because no meta is defined in Insomnia
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// static propertyUnprefixMeta(_value, _key) {
|
||||
// // no meta key is enabled
|
||||
// // so no op here
|
||||
// }
|
||||
|
||||
static toJSON(obj: JSONer) {
|
||||
return obj.toJSON();
|
||||
}
|
||||
|
||||
meta() {
|
||||
return {};
|
||||
};
|
||||
|
||||
parent() {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
forEachParent(options: { withRoot?: boolean }, iterator: (obj: PropertyBase) => boolean) {
|
||||
const currentParent = this.parent();
|
||||
if (!currentParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queue: PropertyBase[] = [currentParent];
|
||||
const parents: PropertyBase[] = [];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const ancester = queue.shift();
|
||||
if (!ancester) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: check options
|
||||
const cloned = clone(ancester);
|
||||
const keepIterating = iterator(cloned);
|
||||
parents.push(cloned);
|
||||
if (!keepIterating) {
|
||||
break;
|
||||
}
|
||||
|
||||
const olderAncester = ancester.parent();
|
||||
if (olderAncester) {
|
||||
queue.push(olderAncester);
|
||||
}
|
||||
}
|
||||
|
||||
return parents;
|
||||
}
|
||||
|
||||
findInParents(property: string, customizer?: (ancester: PropertyBase) => boolean): PropertyBase | undefined {
|
||||
const currentParent = this.parent();
|
||||
if (!currentParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queue: PropertyBase[] = [currentParent];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const ancester = queue.shift();
|
||||
if (!ancester) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: check options
|
||||
const cloned = clone(ancester);
|
||||
const hasProperty = Object.keys(cloned.meta()).includes(property);
|
||||
if (!hasProperty) {
|
||||
// keep traversing until parent has the property
|
||||
// no op
|
||||
} else {
|
||||
if (customizer) {
|
||||
if (customizer(cloned)) {
|
||||
// continue until customizer returns a truthy value
|
||||
return cloned;
|
||||
}
|
||||
} else {
|
||||
// customizer is not specified, stop at the first parent that contains the property
|
||||
return cloned;
|
||||
}
|
||||
}
|
||||
|
||||
const olderAncester = ancester.parent();
|
||||
if (olderAncester) {
|
||||
queue.push(olderAncester);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return { description: this.description };
|
||||
}
|
||||
|
||||
toObject() {
|
||||
return this.toJSON();
|
||||
}
|
||||
}
|
||||
|
||||
export class Property extends PropertyBase {
|
||||
id?: string;
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
info?: object;
|
||||
|
||||
constructor(def?: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
info?: object;
|
||||
}) {
|
||||
super({ description: 'Property' });
|
||||
this.id = def?.id || '';
|
||||
this.name = def?.name || '';
|
||||
this.disabled = def?.disabled || false;
|
||||
this.info = def?.info || {};
|
||||
}
|
||||
|
||||
// static replaceSubstitutions(_str: string, _variables: object): string {
|
||||
// // TODO: unsupported
|
||||
// return '';
|
||||
// }
|
||||
|
||||
// static replaceSubstitutionsIn(obj: string, variables: object): object {
|
||||
// // TODO: unsupported
|
||||
// return {};
|
||||
// }
|
||||
|
||||
describe(content: string, typeName: string) {
|
||||
this.kind = typeName;
|
||||
this.description = content;
|
||||
}
|
||||
}
|
||||
|
||||
export class PropertyList<T extends Property> {
|
||||
list: T[] = [];
|
||||
|
||||
constructor(
|
||||
public readonly typeClass: { new(...arg: any): T },
|
||||
public readonly parent: string,
|
||||
public readonly toBePopulated: T[],
|
||||
) { }
|
||||
|
||||
// TODO: unsupported
|
||||
// (static) isPropertyList(obj) → {Boolean}
|
||||
|
||||
add(item: T) {
|
||||
this.list.push(item);
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.list.map(pp => pp.toJSON());
|
||||
}
|
||||
|
||||
append(item: T) {
|
||||
this.add(item);
|
||||
}
|
||||
|
||||
// TODO: also support PropertyList<T>
|
||||
assimilate(source: T[], prune?: boolean) {
|
||||
if (prune) {
|
||||
this.clear();
|
||||
}
|
||||
this.list.push(...source);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
count() {
|
||||
return this.list.length;
|
||||
}
|
||||
|
||||
each(iterator: (item: T) => void, context: object) {
|
||||
interface Iterator {
|
||||
context?: object;
|
||||
(item: T): void;
|
||||
}
|
||||
const it: Iterator = iterator;
|
||||
it.context = context;
|
||||
|
||||
this.list.forEach(it);
|
||||
}
|
||||
|
||||
// TODO: unsupported
|
||||
// eachParent(iterator, contextopt) {}
|
||||
|
||||
filter(rule: (item: T) => boolean, context: object) {
|
||||
interface Iterator {
|
||||
context?: object;
|
||||
(item: T): boolean;
|
||||
}
|
||||
const it: Iterator = rule;
|
||||
it.context = context;
|
||||
|
||||
return this.list.filter(it);
|
||||
}
|
||||
|
||||
find(rule: (item: T) => boolean, context?: object) {
|
||||
// TODO should return {Item|ItemGroup}
|
||||
interface Finder {
|
||||
context?: object;
|
||||
(item: T): boolean;
|
||||
}
|
||||
const finder: Finder = rule;
|
||||
finder.context = context;
|
||||
|
||||
return this.list.find(finder);
|
||||
}
|
||||
|
||||
// it does not return underlying type of the item because there is no underlying type
|
||||
get(key: string) {
|
||||
return this.one(key);
|
||||
}
|
||||
|
||||
// TODO: value is not used as its usage is unknown
|
||||
has(item: T, _value: any) {
|
||||
return this.indexOf(item) >= 0;
|
||||
}
|
||||
|
||||
idx(index: number) {
|
||||
if (index <= this.list.length - 1) {
|
||||
return this.list[index];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// item: string | T
|
||||
indexOf(item: T) {
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
if (equal(item, this.list[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
insert(item: T, before?: number) {
|
||||
if (before && before <= this.list.length - 1) {
|
||||
this.list = [...this.list.slice(0, before), item, ...this.list.slice(before)];
|
||||
} else {
|
||||
this.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
insertAfter(item: T, after?: number) {
|
||||
if (after && after <= this.list.length - 1) {
|
||||
this.list = [...this.list.slice(0, after + 1), item, ...this.list.slice(after + 1)];
|
||||
} else {
|
||||
this.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
map(iterator: (item: T) => any, context: object) {
|
||||
interface Iterator {
|
||||
context?: object;
|
||||
(item: T): any;
|
||||
}
|
||||
const it: Iterator = iterator;
|
||||
it.context = context;
|
||||
|
||||
this.list.map(it);
|
||||
}
|
||||
|
||||
one(id: string) {
|
||||
for (let i = this.list.length - 1; i >= 0; i--) {
|
||||
if (this.list[i].id === id) {
|
||||
return this.list[i];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
populate(items: T[]) {
|
||||
this.list = [...this.list, ...items];
|
||||
}
|
||||
|
||||
prepend(item: T) {
|
||||
this.list = [item, ...this.list];
|
||||
}
|
||||
|
||||
reduce(iterator: ((acc: any, item: T) => any), accumulator: any, context: object) {
|
||||
interface Iterator {
|
||||
context?: object;
|
||||
(acc: any, item: T): any;
|
||||
}
|
||||
const it: Iterator = iterator;
|
||||
it.context = context;
|
||||
|
||||
this.list.reduce(it, accumulator);
|
||||
}
|
||||
|
||||
remove(predicate: T | ((item: T) => boolean), context: object) {
|
||||
if (typeof predicate === 'function') {
|
||||
this.list = this.filter(predicate, context);
|
||||
} else {
|
||||
this.list = this.filter(item => equal(predicate, item), context);
|
||||
}
|
||||
}
|
||||
|
||||
repopulate(items: T[]) {
|
||||
this.clear();
|
||||
this.populate(items);
|
||||
}
|
||||
|
||||
// unsupportd as _postman_propertyIndexKey is not supported
|
||||
// toObject(excludeDisabled?: boolean, caseSensitive?: boolean, multiValue?: boolean, sanitizeKeys?: boolean) {
|
||||
// const itemObjects = this.list
|
||||
// .filter(item => {
|
||||
// if (excludeDisabled) {
|
||||
// return !item.disabled;
|
||||
// }
|
||||
// return true;
|
||||
// })
|
||||
// .map(item => {
|
||||
// return item.toJSON();
|
||||
// });
|
||||
// }
|
||||
|
||||
toString() {
|
||||
const itemStrs = this.list.map(item => item.toString());
|
||||
return `[${itemStrs.join(',')}]`;
|
||||
}
|
||||
|
||||
upsert(item: T): boolean {
|
||||
const itemIdx = this.indexOf(item);
|
||||
if (itemIdx >= 0) {
|
||||
this.list = [...this.list.splice(0, itemIdx), item, ...this.list.splice(itemIdx + 1)];
|
||||
return false;
|
||||
}
|
||||
|
||||
this.add(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Property, PropertyList } from './object-base';
|
||||
|
||||
export interface CookieDef {
|
||||
key: string;
|
||||
value: string;
|
||||
expires?: Date | string;
|
||||
maxAge?: Number;
|
||||
domain?: string;
|
||||
path?: string;
|
||||
secure?: Boolean;
|
||||
httpOnly?: Boolean;
|
||||
hostOnly?: Boolean;
|
||||
session?: Boolean;
|
||||
extensions?: { key: string; value: string }[];
|
||||
}
|
||||
|
||||
export class Cookie extends Property {
|
||||
private def: object;
|
||||
|
||||
constructor(cookieDef: CookieDef | string) {
|
||||
super();
|
||||
this.kind = 'Cookie';
|
||||
this.description = 'Cookie';
|
||||
|
||||
if (typeof cookieDef === 'string') {
|
||||
this.def = Cookie.parse(cookieDef);
|
||||
} else {
|
||||
this.def = cookieDef;
|
||||
}
|
||||
}
|
||||
|
||||
static isCookie(obj: Property) {
|
||||
return obj.kind === 'Cookie';
|
||||
}
|
||||
|
||||
static parse(cookieStr: string) {
|
||||
const parts = cookieStr.split(';');
|
||||
|
||||
const def: CookieDef = { key: '', value: '' };
|
||||
const extensions: { key: string; value: string }[] = [];
|
||||
|
||||
parts.forEach((part, i) => {
|
||||
const kvParts = part.split('=');
|
||||
const key = kvParts[0];
|
||||
|
||||
if (i === 0) {
|
||||
const value = kvParts.length > 1 ? kvParts[1] : '';
|
||||
def.key, def.value = key, value;
|
||||
} else {
|
||||
switch (key) {
|
||||
case 'Expires':
|
||||
// TODO: it should be timestamp
|
||||
const expireVal = kvParts.length > 1 ? kvParts[1] : '0';
|
||||
def.expires = expireVal;
|
||||
break;
|
||||
case 'Max-Age':
|
||||
let maxAgeVal = 0;
|
||||
if (kvParts.length > 1) {
|
||||
maxAgeVal = parseInt(kvParts[1], 10);
|
||||
}
|
||||
def.maxAge = maxAgeVal;
|
||||
break;
|
||||
case 'Domain':
|
||||
const domainVal = kvParts.length > 1 ? kvParts[1] : '';
|
||||
def.domain = domainVal;
|
||||
break;
|
||||
case 'Path':
|
||||
const pathVal = kvParts.length > 1 ? kvParts[1] : '';
|
||||
def.path = pathVal;
|
||||
break;
|
||||
case 'Secure':
|
||||
def.secure = true;
|
||||
break;
|
||||
case 'HttpOnly':
|
||||
def.httpOnly = true;
|
||||
break;
|
||||
case 'HostOnly':
|
||||
def.hostOnly = true;
|
||||
break;
|
||||
case 'Session':
|
||||
def.session = true;
|
||||
break;
|
||||
default:
|
||||
const value = kvParts.length > 1 ? kvParts[1] : '';
|
||||
extensions.push({ key, value });
|
||||
def.extensions = extensions;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
static stringify(cookie: Cookie) {
|
||||
return cookie.toString();
|
||||
}
|
||||
|
||||
static unparseSingle(cookie: Cookie) {
|
||||
return cookie.toString();
|
||||
}
|
||||
|
||||
// TODO: support PropertyList
|
||||
static unparse(cookies: Cookie[]) {
|
||||
const cookieStrs = cookies.map(cookie => cookie.toString());
|
||||
return cookieStrs.join(';');
|
||||
}
|
||||
|
||||
toString = () => {
|
||||
// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
||||
const cookieDef = this.def as CookieDef;
|
||||
const kvPair = `${cookieDef.key}=${cookieDef.value};`;
|
||||
const expires = cookieDef.expires ? `Expires=${cookieDef.expires?.toString()};` : '';
|
||||
const maxAge = cookieDef.maxAge ? `Max-Age=${cookieDef.maxAge};` : '';
|
||||
const domain = cookieDef.domain ? `Domain=${cookieDef.domain};` : '';
|
||||
const path = cookieDef.path ? `Path=${cookieDef.path};` : '';
|
||||
const secure = cookieDef.secure ? 'Secure;' : '';
|
||||
const httpOnly = cookieDef.httpOnly ? 'HttpOnly;' : '';
|
||||
// TODO: SameSite, Partitioned is not suported
|
||||
|
||||
const hostOnly = cookieDef.hostOnly ? 'HostOnly;' : '';
|
||||
const session = cookieDef.session ? 'Session;' : '';
|
||||
|
||||
// TODO: extension key may be conflict with pre-defined keys
|
||||
const extensions = cookieDef.extensions ?
|
||||
cookieDef.extensions
|
||||
.map((kv: { key: string; value: string }) => `${kv.key}=${kv.value}`)
|
||||
.join(';') : ''; // the last field doesn't have ';'
|
||||
|
||||
return `${kvPair} ${expires} ${maxAge} ${domain} ${path} ${secure} ${httpOnly} ${hostOnly} ${session} ${extensions}`;
|
||||
};
|
||||
|
||||
valueOf = () => {
|
||||
return (this.def as CookieDef).value;
|
||||
};
|
||||
}
|
||||
|
||||
export class CookieList extends PropertyList<Cookie> {
|
||||
cookies: Cookie[];
|
||||
|
||||
constructor(parent: object, cookies: Cookie[]) {
|
||||
super(Cookie, parent.toString(), cookies);
|
||||
this.cookies = cookies;
|
||||
}
|
||||
|
||||
// (static) isCookieList(obj) → {Boolean}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user