mirror of
https://github.com/Kong/insomnia
synced 2024-11-12 17:26:32 +00:00
297 lines
8.3 KiB
JavaScript
297 lines
8.3 KiB
JavaScript
// @flow
|
|
import * as React from 'react';
|
|
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
|
import autobind from 'autobind-decorator';
|
|
import * as toughCookie from 'tough-cookie';
|
|
import * as models from '../../../models';
|
|
import clone from 'clone';
|
|
import { DEBOUNCE_MILLIS } from '../../../common/constants';
|
|
import Modal from '../base/modal';
|
|
import ModalBody from '../base/modal-body';
|
|
import ModalHeader from '../base/modal-header';
|
|
import ModalFooter from '../base/modal-footer';
|
|
import OneLineEditor from '../codemirror/one-line-editor';
|
|
import { cookieToString } from 'insomnia-cookies';
|
|
import type { Cookie, CookieJar } from '../../../models/cookie-jar';
|
|
import type { Workspace } from '../../../models/workspace';
|
|
|
|
type Props = {
|
|
handleRender: Function,
|
|
handleGetRenderContext: Function,
|
|
nunjucksPowerUserMode: boolean,
|
|
isVariableUncovered: boolean,
|
|
workspace: Workspace,
|
|
cookieJar: CookieJar,
|
|
};
|
|
|
|
type State = {
|
|
cookie: Cookie | null,
|
|
rawValue: string,
|
|
};
|
|
|
|
@autobind
|
|
class CookieModifyModal extends React.PureComponent<Props, State> {
|
|
modal: Modal | null;
|
|
_rawTimeout: TimeoutID;
|
|
_cookieUpdateTimeout: TimeoutID;
|
|
|
|
constructor(props: any) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
cookie: null,
|
|
rawValue: '',
|
|
};
|
|
}
|
|
|
|
_setModalRef(n: Modal | null) {
|
|
this.modal = n;
|
|
}
|
|
|
|
async show(cookie: Cookie) {
|
|
// Dunno why this is sent as an array
|
|
cookie = cookie[0] || cookie;
|
|
|
|
const { cookieJar } = this.props;
|
|
const oldCookie = cookieJar.cookies.find(c => c.id === cookie.id);
|
|
|
|
if (!oldCookie) {
|
|
// Cookie not found in jar
|
|
return;
|
|
}
|
|
|
|
this.setState({ cookie });
|
|
|
|
this.modal && this.modal.show();
|
|
}
|
|
|
|
hide() {
|
|
this.modal && this.modal.hide();
|
|
}
|
|
|
|
static async _saveChanges(cookieJar: CookieJar) {
|
|
await models.cookieJar.update(cookieJar);
|
|
}
|
|
|
|
_handleChangeRawValue(e: SyntheticEvent<HTMLInputElement>) {
|
|
const value = e.currentTarget.value;
|
|
|
|
clearTimeout(this._rawTimeout);
|
|
this._rawTimeout = setTimeout(async () => {
|
|
const oldCookie = this.state.cookie;
|
|
let cookie;
|
|
try {
|
|
// NOTE: Perform toJSON so we have a plain JS object instead of Cookie instance
|
|
cookie = toughCookie.Cookie.parse(value).toJSON();
|
|
} catch (err) {
|
|
console.warn(`Failed to parse cookie string "${value}"`, err);
|
|
return;
|
|
}
|
|
|
|
if (!this.state.cookie || !oldCookie) {
|
|
return;
|
|
}
|
|
|
|
// Make sure cookie has an id
|
|
cookie.id = oldCookie.id;
|
|
|
|
await this._handleCookieUpdate(cookie);
|
|
}, DEBOUNCE_MILLIS * 2);
|
|
}
|
|
|
|
async _handleCookieUpdate(newCookie: Cookie) {
|
|
const oldCookie = this.state.cookie;
|
|
if (!oldCookie) {
|
|
// We don't have a cookie to edit
|
|
return;
|
|
}
|
|
|
|
const cookie = clone(newCookie);
|
|
|
|
// Sanitize expires field
|
|
const expires = new Date(cookie.expires || '').getTime();
|
|
if (isNaN(expires)) {
|
|
delete cookie.expires;
|
|
} else {
|
|
cookie.expires = expires;
|
|
}
|
|
|
|
// Clone so we don't modify the original
|
|
const cookieJar = clone(this.props.cookieJar);
|
|
|
|
const { cookies } = cookieJar;
|
|
const index = cookies.findIndex(c => c.id === cookie.id);
|
|
|
|
if (index < 0) {
|
|
console.warn(`Could not find cookie with id=${cookie.id} to edit`);
|
|
return;
|
|
}
|
|
|
|
cookieJar.cookies = [...cookies.slice(0, index), cookie, ...cookies.slice(index + 1)];
|
|
|
|
this.setState({ cookie });
|
|
|
|
await CookieModifyModal._saveChanges(cookieJar);
|
|
|
|
return cookie;
|
|
}
|
|
|
|
_handleChange(field: string, eventOrValue: string | Event) {
|
|
const { cookie } = this.state;
|
|
|
|
let value;
|
|
|
|
if (typeof eventOrValue === 'string') {
|
|
value = eventOrValue.trim();
|
|
} else if (eventOrValue.target instanceof HTMLInputElement) {
|
|
if (eventOrValue.target.type === 'checkbox') {
|
|
value = eventOrValue.target.checked;
|
|
} else {
|
|
value = eventOrValue.target.value.trim();
|
|
}
|
|
} else {
|
|
// Should never happen
|
|
}
|
|
|
|
const newCookie = Object.assign({}, cookie, { [field]: value });
|
|
|
|
clearTimeout(this._cookieUpdateTimeout);
|
|
this._cookieUpdateTimeout = setTimeout(async () => {
|
|
await this._handleCookieUpdate(newCookie);
|
|
this.setState({ cookie: newCookie });
|
|
}, DEBOUNCE_MILLIS * 2);
|
|
}
|
|
|
|
static _capitalize(str: string) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
|
|
_getRawCookieString() {
|
|
const { cookie } = this.state;
|
|
|
|
if (!cookie) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
return cookieToString(toughCookie.Cookie.fromJSON(JSON.stringify(cookie)));
|
|
} catch (err) {
|
|
console.warn('Failed to parse cookie string', err);
|
|
return '';
|
|
}
|
|
}
|
|
|
|
_renderInputField(field: string, error: string | null = null) {
|
|
const { cookie } = this.state;
|
|
const {
|
|
handleRender,
|
|
handleGetRenderContext,
|
|
nunjucksPowerUserMode,
|
|
isVariableUncovered,
|
|
} = this.props;
|
|
|
|
if (!cookie) {
|
|
return null;
|
|
}
|
|
|
|
const val = (cookie[field] || '').toString();
|
|
|
|
return (
|
|
<div className="form-control form-control--outlined">
|
|
<label>
|
|
{CookieModifyModal._capitalize(field)} <span className="danger">{error}</span>
|
|
<OneLineEditor
|
|
render={handleRender}
|
|
getRenderContext={handleGetRenderContext}
|
|
nunjucksPowerUserMode={nunjucksPowerUserMode}
|
|
isVariableUncovered={isVariableUncovered}
|
|
defaultValue={val || ''}
|
|
onChange={value => this._handleChange(field, value)}
|
|
/>
|
|
</label>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const { cookieJar } = this.props;
|
|
const { cookie } = this.state;
|
|
const checkFields = ['secure', 'httpOnly'];
|
|
|
|
return (
|
|
<Modal ref={this._setModalRef} {...this.props}>
|
|
<ModalHeader>Edit Cookie</ModalHeader>
|
|
<ModalBody className="cookie-modify">
|
|
{cookieJar &&
|
|
cookie && (
|
|
<Tabs>
|
|
<TabList>
|
|
<Tab tabIndex="-1">
|
|
<button>Friendly</button>
|
|
</Tab>
|
|
<Tab tabIndex="-1">
|
|
<button>Raw</button>
|
|
</Tab>
|
|
</TabList>
|
|
<TabPanel>
|
|
<div className="pad">
|
|
<div className="form-row">
|
|
{this._renderInputField('key')}
|
|
{this._renderInputField('value')}
|
|
</div>
|
|
<div className="form-row">
|
|
{this._renderInputField('domain')}
|
|
{this._renderInputField('path')}
|
|
</div>
|
|
{this._renderInputField(
|
|
'expires',
|
|
isNaN(new Date(cookie.expires || 0).getTime()) ? 'Invalid Date' : null,
|
|
)}
|
|
</div>
|
|
<div className="pad no-pad-top cookie-modify__checkboxes row-around txt-lg">
|
|
{checkFields.map((field, i) => {
|
|
const checked = !!cookie[field];
|
|
|
|
return (
|
|
<label key={i}>
|
|
{CookieModifyModal._capitalize(field)}
|
|
<input
|
|
className="space-left"
|
|
type="checkbox"
|
|
name={field}
|
|
defaultChecked={checked || false}
|
|
onChange={e => this._handleChange(field, e)}
|
|
/>
|
|
</label>
|
|
);
|
|
})}
|
|
</div>
|
|
</TabPanel>
|
|
<TabPanel className="react-tabs__tab-panel pad">
|
|
<div className="form-control form-control--outlined">
|
|
<label>
|
|
Raw Cookie String
|
|
<input
|
|
type="text"
|
|
onChange={this._handleChangeRawValue}
|
|
defaultValue={this._getRawCookieString()}
|
|
/>
|
|
</label>
|
|
</div>
|
|
</TabPanel>
|
|
</Tabs>
|
|
)}
|
|
</ModalBody>
|
|
<ModalFooter>
|
|
<button className="btn" onClick={this.hide}>
|
|
Done
|
|
</button>
|
|
</ModalFooter>
|
|
</Modal>
|
|
);
|
|
}
|
|
}
|
|
|
|
// export CookieModifyModal;
|
|
export default CookieModifyModal;
|