123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929 |
- <template>
- <div class="page">
- <pw-section class="yellow" label="Import" ref="import">
- <ul>
- <li>
- <button id="show-modal" @click="showModal = true">Import cURL</button>
- </li>
- </ul>
- <import-modal v-if="showModal" @close="showModal = false">
- <div slot="header">
- <ul>
- <li>
- <div class="flex-wrap">
- <h3 class="title">Import cURL</h3>
- <div>
- <button class="icon" @click="toggleModal">
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
- <path d="M23 20.168l-8.185-8.187 8.185-8.174-2.832-2.807-8.182 8.179-8.176-8.179-2.81 2.81 8.186 8.196-8.186 8.184 2.81 2.81 8.203-8.192 8.18 8.192z"/>
- </svg>
- </button>
- </div>
- </div>
- </li>
- </ul>
- </div>
- <div slot="body">
- <ul>
- <li>
- <textarea id="import-text" autofocus rows="8"></textarea>
- </li>
- </ul>
- </div>
- <div slot="footer">
- <ul>
- <li>
- <button @click="handleImport">Import</button>
- </li>
- </ul>
- </div>
- </import-modal>
- </pw-section>
- <pw-section class="blue" label="Request" ref="request">
- <ul>
- <li>
- <label for="method">Method</label>
- <select id="method" v-model="method">
- <option>GET</option>
- <option>HEAD</option>
- <option>POST</option>
- <option>PUT</option>
- <option>DELETE</option>
- <option>OPTIONS</option>
- <option>PATCH</option>
- </select>
- </li>
- <li>
- <label for="url">URL</label>
- <input :class="{ error: !isValidURL }" @keyup.enter="isValidURL ? sendRequest() : null" id="url" type="url" v-model="url">
- </li>
- <li>
- <label for="path">Path</label>
- <input @keyup.enter="isValidURL ? sendRequest() : null" id="path" v-model="path">
- </li>
- <div class="show-on-small-screen">
- <li>
- <label class="hide-on-small-screen" for="copyRequest"> </label>
- <button class="icon" @click="copyRequest" id="copyRequest" ref="copyRequest" :disabled="!isValidURL">
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
- <path d="M22 6v16h-16v-16h16zm2-2h-20v20h20v-20zm-24 17v-21h21v2h-19v19h-2z" />
- </svg>
- <span>Share URL</span>
- </button>
- </li>
- <li>
- <label class="hide-on-small-screen" for="code"> </label>
- <button class="icon" id="code" name="code" v-on:click="isHidden = !isHidden" :disabled="!isValidURL">
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" v-if="isHidden">
- <path d="M12.015 7c4.751 0 8.063 3.012 9.504 4.636-1.401 1.837-4.713 5.364-9.504 5.364-4.42 0-7.93-3.536-9.478-5.407 1.493-1.647 4.817-4.593 9.478-4.593zm0-2c-7.569 0-12.015 6.551-12.015 6.551s4.835 7.449 12.015 7.449c7.733 0 11.985-7.449 11.985-7.449s-4.291-6.551-11.985-6.551zm-.015 5c1.103 0 2 .897 2 2s-.897 2-2 2-2-.897-2-2 .897-2 2-2zm0-2c-2.209 0-4 1.792-4 4 0 2.209 1.791 4 4 4s4-1.791 4-4c0-2.208-1.791-4-4-4z" />
- </svg>
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" v-if="!isHidden">
- <path d="M19.604 2.562l-3.346 3.137c-1.27-.428-2.686-.699-4.243-.699-7.569 0-12.015 6.551-12.015 6.551s1.928 2.951 5.146 5.138l-2.911 2.909 1.414 1.414 17.37-17.035-1.415-1.415zm-6.016 5.779c-3.288-1.453-6.681 1.908-5.265 5.206l-1.726 1.707c-1.814-1.16-3.225-2.65-4.06-3.66 1.493-1.648 4.817-4.594 9.478-4.594.927 0 1.796.119 2.61.315l-1.037 1.026zm-2.883 7.431l5.09-4.993c1.017 3.111-2.003 6.067-5.09 4.993zm13.295-4.221s-4.252 7.449-11.985 7.449c-1.379 0-2.662-.291-3.851-.737l1.614-1.583c.715.193 1.458.32 2.237.32 4.791 0 8.104-3.527 9.504-5.364-.729-.822-1.956-1.99-3.587-2.952l1.489-1.46c2.982 1.9 4.579 4.327 4.579 4.327z" />
- </svg>
- <span>{{ isHidden ? 'Show Code' : 'Hide Code' }}</span>
- </button>
- </li>
- </div>
- <li>
- <label class="hide-on-small-screen" for="action"> </label>
- <button :disabled="!isValidURL" @click="sendRequest" class="show" id="action" name="action" ref="sendButton">
- Send <span id="hidden-message">Again</span>
- <span>
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
- <path d="M5 3l3.057-3 11.943 12-11.943 12-3.057-3 9-9z"/>
- </svg>
- </span>
- </button>
- </li>
- </ul>
- </pw-section>
- <pw-section class="blue" label="Request Code" ref="requestCode" v-if="!isHidden">
- <ul>
- <li>
- <label for="requestType">Request Type</label>
- <select name="requestType" v-model="requestType">
- <option>JavaScript XHR</option>
- <option>Fetch</option>
- <option>cURL</option>
- </select>
- </li>
- </ul>
- <ul>
- <li>
- <div class="flex-wrap">
- <label for="generatedCode">Generated Code</label>
- <div>
- <button class="icon" @click="copyRequestCode" name="copyRequestCode" ref="copyRequestCode">
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
- <path d="M22 6v16h-16v-16h16zm2-2h-20v20h20v-20zm-24 17v-21h21v2h-19v19h-2z" />
- </svg>
- <span>Copy</span>
- </button>
- </div>
- </div>
- <textarea ref="generatedCode" name="generatedCode" rows="16" v-model="requestCode"></textarea>
- </li>
- </ul>
- </pw-section>
- <pw-section class="blue" label="Request Body" v-if="method === 'POST' || method === 'PUT' || method === 'PATCH'">
- <ul>
- <li>
- <autocomplete :source="validContentTypes" :spellcheck="false" v-model="contentType">Content Type
- </autocomplete>
- <span>
- <pw-toggle :on="rawInput" @change="rawInput = !rawInput">
- Raw input {{ rawInput ? "enabled" : "disabled" }}
- </pw-toggle>
- </span>
- </li>
- </ul>
- <div v-if="!rawInput">
- <ol v-for="(param, index) in bodyParams" :key="index">
- <li>
- <label :for="'bparam'+index">Key {{index + 1}}</label>
- <input :name="'bparam'+index" v-model="param.key" @keyup.prevent="setRouteQueryState" autofocus>
- </li>
- <li>
- <label :for="'bvalue'+index">Value {{index + 1}}</label>
- <input :name="'bvalue'+index" v-model="param.value" @keyup.prevent="setRouteQueryState">
- </li>
- <div>
- <li>
- <label class="hide-on-small-screen" for="request"> </label>
- <button class="icon" @click="removeRequestBodyParam(index)" name="request">
- <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd">
- <path d="M5.662 23l-5.369-5.365c-.195-.195-.293-.45-.293-.707 0-.256.098-.512.293-.707l14.929-14.928c.195-.194.451-.293.707-.293.255 0 .512.099.707.293l7.071 7.073c.196.195.293.451.293.708 0 .256-.097.511-.293.707l-11.216 11.219h5.514v2h-12.343zm3.657-2l-5.486-5.486-1.419 1.414 4.076 4.072h2.829zm6.605-17.581l-10.677 10.68 5.658 5.659 10.676-10.682-5.657-5.657z" />
- </svg>
- </button>
- </li>
- </div>
- </ol>
- <ul>
- <li>
- <label for="addrequest">Action</label>
- <button @click="addRequestBodyParam" name="addrequest">Add New</button>
- </li>
- </ul>
- <ul>
- <li>
- <label for="request">Parameter List</label>
- <textarea name="request" readonly v-textarea-auto-height="rawRequestBody" v-model="rawRequestBody" placeholder="(add at least one parameter)" rows="1"></textarea>
- </li>
- </ul>
- </div>
- <div v-else>
- <textarea @keydown="formatRawParams" rows="16" v-model="rawParams" v-textarea-auto-height="rawParams"></textarea>
- </div>
- </pw-section>
- <pw-section class="purple" id="response" label="Response" ref="response">
- <ul>
- <li>
- <label for="status">status</label>
- <input :class="statusCategory ? statusCategory.className : ''" :value="response.status || '(waiting to send request)'" name="status" readonly type="text">
- </li>
- </ul>
- <ul v-for="(value, key) in response.headers" :key="key">
- <li>
- <label for="value">{{key}}</label>
- <input :value="value" name="value" readonly>
- </li>
- </ul>
- <ul v-if="response.body">
- <li>
- <div class="flex-wrap">
- <label for="body">response</label>
- <div>
- <button class="icon" @click="copyResponse" name="copyResponse" ref="copyResponse" v-if="response.body">
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
- <path d="M22 6v16h-16v-16h16zm2-2h-20v20h20v-20zm-24 17v-21h21v2h-19v19h-2z" />
- </svg>
- <span>Copy</span>
- </button>
- </div>
- </div>
- <div id="response-details-wrapper">
- <pre><code ref="responseBody" name="body" rows="16" placeholder="(waiting to send request)">{{response.body}}</code></pre>
- <iframe :class="{hidden: !previewEnabled}" class="covers-response" ref="previewFrame" src="about:blank"></iframe>
- </div>
- <div class="align-right" v-if="response.body && responseType === 'text/html'">
- <button class="icon" @click.prevent="togglePreview">
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" v-if="!previewEnabled">
- <path d="M12.015 7c4.751 0 8.063 3.012 9.504 4.636-1.401 1.837-4.713 5.364-9.504 5.364-4.42 0-7.93-3.536-9.478-5.407 1.493-1.647 4.817-4.593 9.478-4.593zm0-2c-7.569 0-12.015 6.551-12.015 6.551s4.835 7.449 12.015 7.449c7.733 0 11.985-7.449 11.985-7.449s-4.291-6.551-11.985-6.551zm-.015 5c1.103 0 2 .897 2 2s-.897 2-2 2-2-.897-2-2 .897-2 2-2zm0-2c-2.209 0-4 1.792-4 4 0 2.209 1.791 4 4 4s4-1.791 4-4c0-2.208-1.791-4-4-4z" />
- </svg>
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" v-if="previewEnabled">
- <path d="M19.604 2.562l-3.346 3.137c-1.27-.428-2.686-.699-4.243-.699-7.569 0-12.015 6.551-12.015 6.551s1.928 2.951 5.146 5.138l-2.911 2.909 1.414 1.414 17.37-17.035-1.415-1.415zm-6.016 5.779c-3.288-1.453-6.681 1.908-5.265 5.206l-1.726 1.707c-1.814-1.16-3.225-2.65-4.06-3.66 1.493-1.648 4.817-4.594 9.478-4.594.927 0 1.796.119 2.61.315l-1.037 1.026zm-2.883 7.431l5.09-4.993c1.017 3.111-2.003 6.067-5.09 4.993zm13.295-4.221s-4.252 7.449-11.985 7.449c-1.379 0-2.662-.291-3.851-.737l1.614-1.583c.715.193 1.458.32 2.237.32 4.791 0 8.104-3.527 9.504-5.364-.729-.822-1.956-1.99-3.587-2.952l1.489-1.46c2.982 1.9 4.579 4.327 4.579 4.327z" />
- </svg>
- <span>{{ previewEnabled ? 'Hide Preview' : 'Preview HTML' }}</span>
- </button>
- </div>
- </li>
- </ul>
- </pw-section>
- <pw-section class="green" collapsed label="Authentication">
- <ul>
- <li>
- <label for="auth">Authentication Type</label>
- <select v-model="auth">
- <option>None</option>
- <option>Basic</option>
- <option>Bearer Token</option>
- </select>
- </li>
- </ul>
- <ul v-if="auth === 'Basic'">
- <li>
- <label for="http_basic_user">User</label>
- <input v-model="httpUser">
- </li>
- <li>
- <label for="http_basic_passwd">Password</label>
- <input type="password" v-model="httpPassword">
- </li>
- </ul>
- <ul v-if="auth === 'Bearer Token'">
- <li>
- <label for="bearer_token">Token</label>
- <input v-model="bearerToken">
- </li>
- </ul>
- </pw-section>
- <pw-section class="orange" collapsed label="Headers">
- <ol v-for="(header, index) in headers" :key="index">
- <li>
- <label :for="'header'+index">Header {{index + 1}}</label>
- <input :name="'header'+index" v-model="header.key" @keyup.prevent="setRouteQueryState" autofocus>
- </li>
- <li>
- <label :for="'value'+index">Value {{index + 1}}</label>
- <input :name="'value'+index" v-model="header.value" @keyup.prevent="setRouteQueryState">
- </li>
- <div>
- <li>
- <label class="hide-on-small-screen" for="header"> </label>
- <button class="icon" @click="removeRequestHeader(index)" name="header">
- <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd">
- <path d="M5.662 23l-5.369-5.365c-.195-.195-.293-.45-.293-.707 0-.256.098-.512.293-.707l14.929-14.928c.195-.194.451-.293.707-.293.255 0 .512.099.707.293l7.071 7.073c.196.195.293.451.293.708 0 .256-.097.511-.293.707l-11.216 11.219h5.514v2h-12.343zm3.657-2l-5.486-5.486-1.419 1.414 4.076 4.072h2.829zm6.605-17.581l-10.677 10.68 5.658 5.659 10.676-10.682-5.657-5.657z" />
- </svg>
- </button>
- </li>
- </div>
- </ol>
- <ul>
- <li>
- <button @click="addRequestHeader" name="add">Add New</button>
- </li>
- </ul>
- <ul>
- <li>
- <label for="request">Header List</label>
- <textarea name="request" readonly v-textarea-auto-height="headerString" v-model="headerString" placeholder="(add at least one header)" rows="1"></textarea>
- </li>
- </ul>
- </pw-section>
- <pw-section class="pink" collapsed label="Parameters">
- <ol v-for="(param, index) in params" :key="index">
- <li>
- <label :for="'param'+index">Parameter {{index + 1}}</label>
- <input :name="'param'+index" v-model="param.key" autofocus>
- </li>
- <li>
- <label :for="'value'+index">Value {{index + 1}}</label>
- <input :name="'value'+index" v-model="param.value">
- </li>
- <div>
- <li>
- <label class="hide-on-small-screen" for="param"> </label>
- <button class="icon" @click="removeRequestParam(index)" name="param">
- <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd">
- <path d="M5.662 23l-5.369-5.365c-.195-.195-.293-.45-.293-.707 0-.256.098-.512.293-.707l14.929-14.928c.195-.194.451-.293.707-.293.255 0 .512.099.707.293l7.071 7.073c.196.195.293.451.293.708 0 .256-.097.511-.293.707l-11.216 11.219h5.514v2h-12.343zm3.657-2l-5.486-5.486-1.419 1.414 4.076 4.072h2.829zm6.605-17.581l-10.677 10.68 5.658 5.659 10.676-10.682-5.657-5.657z" />
- </svg>
- </button>
- </li>
- </div>
- </ol>
- <ul>
- <li>
- <button @click="addRequestParam" name="add">Add New</button>
- </li>
- </ul>
- <ul>
- <li>
- <label for="request">Parameter List</label>
- <textarea name="request" readonly v-textarea-auto-height="queryString" v-model="queryString" placeholder="(add at least one parameter)" rows="1"></textarea>
- </li>
- </ul>
- </pw-section>
- <history @useHistory="handleUseHistory" ref="historyComponent" />
- </div>
- </template>
- <script>
- import autocomplete from '../components/autocomplete';
- import history from "../components/history";
- import section from "../components/section";
- import textareaAutoHeight from "../directives/textareaAutoHeight";
- import toggle from "../components/toggle";
- import import_modal from "../components/modal";
- import parseCurlCommand from '../assets/js/curlparser.js';
- import hljs from 'highlight.js';
- import 'highlight.js/styles/dracula.css';
- const statusCategories = [{
- name: 'informational',
- statusCodeRegex: new RegExp(/[1][0-9]+/),
- className: 'info-response'
- },
- {
- name: 'successful',
- statusCodeRegex: new RegExp(/[2][0-9]+/),
- className: 'success-response'
- },
- {
- name: 'redirection',
- statusCodeRegex: new RegExp(/[3][0-9]+/),
- className: 'redir-response'
- },
- {
- name: 'client error',
- statusCodeRegex: new RegExp(/[4][0-9]+/),
- className: 'cl-error-response'
- },
- {
- name: 'server error',
- statusCodeRegex: new RegExp(/[5][0-9]+/),
- className: 'sv-error-response'
- },
- {
- // this object is a catch-all for when no other objects match and should always be last
- name: 'unknown',
- statusCodeRegex: new RegExp(/.*/),
- className: 'missing-data-response'
- }
- ];
- const parseHeaders = xhr => {
- const headers = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);
- const headerMap = {};
- headers.forEach(line => {
- const parts = line.split(': ');
- const header = parts.shift().toLowerCase();
- const value = parts.join(': ');
- headerMap[header] = value
- });
- return headerMap
- };
- export const findStatusGroup = responseStatus => statusCategories.find(status => status.statusCodeRegex.test(responseStatus));
- export default {
- directives: {
- textareaAutoHeight
- },
- components: {
- 'pw-section': section,
- 'pw-toggle': toggle,
- 'import-modal': import_modal,
- history,
- autocomplete,
- },
- data() {
- return {
- showModal: false,
- copyButton: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path d="M22 6v16h-16v-16h16zm2-2h-20v20h20v-20zm-24 17v-21h21v2h-19v19h-2z" /></svg>',
- copiedButton: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path d="M22 2v20h-20v-20h20zm2-2h-24v24h24v-24zm-5.541 8.409l-1.422-1.409-7.021 7.183-3.08-2.937-1.395 1.435 4.5 4.319 8.418-8.591z"/></svg>',
- method: 'GET',
- url: 'https://reqres.in',
- auth: 'None',
- path: '/api/users',
- httpUser: '',
- httpPassword: '',
- bearerToken: '',
- headers: [],
- params: [],
- bodyParams: [],
- rawParams: '',
- rawInput: false,
- contentType: 'application/json',
- requestType: 'JavaScript XHR',
- isHidden: true,
- response: {
- status: '',
- headers: '',
- body: ''
- },
- previewEnabled: false,
- /**
- * These are content types that can be automatically
- * serialized by postwoman.
- */
- knownContentTypes: [
- 'application/json',
- 'application/x-www-form-urlencoded'
- ],
- /**
- * These are a list of Content Types known to Postwoman.
- */
- validContentTypes: [
- 'application/json',
- 'application/hal+json',
- 'application/xml',
- 'application/x-www-form-urlencoded',
- 'text/html',
- 'text/plain'
- ]
- }
- },
- watch: {
- contentType(val) {
- this.rawInput = !this.knownContentTypes.includes(val);
- },
- rawInput (status) {
- if (status && this.rawParams === '') this.rawParams = '{}'
- else this.setRouteQueryState()
- },
- 'response.body': function (val) {
- var responseText = document.querySelector("div#response-details-wrapper pre code") != null ? document.querySelector("div#response-details-wrapper pre code") : null;
- if (responseText) {
- if (document.querySelector('.hljs') !== null && responseText.innerHTML.indexOf('<span class="hljs') !== -1) {
- responseText.removeAttribute("class");
- responseText.innerHTML = null;
- responseText.innerText = this.response.body;
- } else if (responseText && this.response.body != "(waiting to send request)" && this.response.body != "Loading..." && this.response.body != "See JavaScript console (F12) for details.") {
- responseText.innerText = this.responseType == 'application/json' ? JSON.stringify(this.response.body, null, 2) : this.response.body;
- hljs.highlightBlock(document.querySelector("div#response-details-wrapper pre code"));
- } else {
- responseText.innerText = this.response.body
- }
- }
- }
- },
- computed: {
- statusCategory() {
- return findStatusGroup(this.response.status);
- },
- isValidURL() {
- const protocol = '^(https?:\\/\\/)?';
- const validIP = new RegExp(protocol + "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
- const validHostname = new RegExp(protocol + "(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$");
- return validIP.test(this.url) || validHostname.test(this.url);
- },
- hasRequestBody() {
- return ['POST', 'PUT', 'PATCH'].includes(this.method);
- },
- rawRequestBody() {
- const {
- bodyParams
- } = this
- if (this.contentType === 'application/json') {
- try {
- const obj = JSON.parse(`{${bodyParams.filter(({key}) => !!key).map(({key, value}) => `
- "${key}": "${value}"
- `).join()}}`)
- return JSON.stringify(obj)
- } catch (ex) {
- return 'invalid'
- }
- } else {
- return bodyParams
- .filter(({
- key
- }) => !!key)
- .map(({
- key,
- value
- }) => `${key}=${encodeURIComponent(value)}`).join('&')
- }
- },
- headerString() {
- const result = this.headers
- .filter(({
- key
- }) => !!key)
- .map(({
- key,
- value
- }) => `${key}: ${value}`).join(',\n')
- return result == '' ? '' : `${result}`
- },
- queryString() {
- const result = this.params
- .filter(({
- key
- }) => !!key)
- .map(({
- key,
- value
- }) => `${key}=${encodeURIComponent(value)}`).join('&')
- return result === '' ? '' : `?${result}`
- },
- responseType() {
- return (this.response.headers['content-type'] || '').split(';')[0].toLowerCase();
- },
- requestCode() {
- if (this.requestType == 'JavaScript XHR') {
- var requestString = []
- requestString.push('const xhr = new XMLHttpRequest()');
- const user = this.auth === 'Basic' ? this.httpUser : null
- const pswd = this.auth === 'Basic' ? this.httpPassword : null
- requestString.push('xhr.open(' + this.method + ', ' + this.url + this.path + this.queryString + ', true, ' + user + ', ' + pswd + ')');
- if (this.auth === 'Bearer Token') {
- requestString.push("xhr.setRequestHeader('Authorization', 'Bearer ' + " + this.bearerToken + ")");
- }
- if (this.headers) {
- this.headers.forEach(function(element) {
- requestString.push('xhr.setRequestHeader(' + element.key + ', ' + element.value + ')');
- })
- }
- if (this.method === 'POST' || this.method === 'PUT') {
- const requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
- requestString.push("xhr.setRequestHeader('Content-Length', " + requestBody.length + ")")
- requestString.push("xhr.setRequestHeader('Content-Type', `" + this.contentType + "; charset=utf-8`)")
- requestString.push("xhr.send(" + requestBody + ")")
- } else {
- requestString.push('xhr.send()')
- }
- return requestString.join('\n');
- } else if (this.requestType == 'Fetch') {
- var requestString = [];
- var headers = [];
- requestString.push('fetch(' + this.url + this.path + this.queryString + ', {\n')
- requestString.push(' method: "' + this.method + '",\n')
- if (this.auth === 'Basic') {
- var basic = this.httpUser + ':' + this.httpPassword;
- headers.push(' "Authorization": "Basic ' + window.btoa(unescape(encodeURIComponent(basic))) + ',\n')
- } else if (this.auth === 'Bearer Token') {
- headers.push(' "Authorization": "Bearer Token ' + this.bearerToken + ',\n')
- }
- if (this.method === 'POST' || this.method === 'PUT') {
- const requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
- requestString.push(' body: ' + requestBody + ',\n')
- headers.push(' "Content-Length": ' + requestBody.length + ',\n')
- headers.push(' "Content-Type": "' + this.contentType + '; charset=utf-8",\n')
- }
- if (this.headers) {
- this.headers.forEach(function(element) {
- headers.push(' "' + element.key + '": "' + element.value + '",\n');
- })
- }
- headers = headers.join('').slice(0, -3);
- requestString.push(' headers: {\n' + headers + '\n },\n')
- requestString.push(' credentials: "same-origin"\n')
- requestString.push(')}).then(function(response) {\n')
- requestString.push(' response.status\n')
- requestString.push(' response.statusText\n')
- requestString.push(' response.headers\n')
- requestString.push(' response.url\n\n')
- requestString.push(' return response.text()\n')
- requestString.push(')}, function(error) {\n')
- requestString.push(' error.message\n')
- requestString.push(')}')
- return requestString.join('');
- } else if (this.requestType == 'cURL') {
- var requestString = [];
- requestString.push('curl -X ' + this.method + ' \\\n')
- requestString.push(" '" + this.url + this.path + this.queryString + "' \\\n")
- if (this.auth === 'Basic') {
- var basic = this.httpUser + ':' + this.httpPassword;
- requestString.push(" -H 'Authorization: Basic " + window.btoa(unescape(encodeURIComponent(basic))) + "' \\\n")
- } else if (this.auth === 'Bearer Token') {
- requestString.push(" -H 'Authorization: Bearer Token " + this.bearerToken + "' \\\n")
- }
- if (this.headers) {
- this.headers.forEach(function(element) {
- requestString.push(" -H '" + element.key + ": " + element.value + "' \\\n");
- })
- }
- if (this.method === 'POST' || this.method === 'PUT') {
- const requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
- requestString.push(" -H 'Content-Length: " + requestBody.length + "' \\\n")
- requestString.push(" -H 'Content-Type: " + this.contentType + "; charset=utf-8' \\\n")
- requestString.push(" -d '" + requestBody + "' \\\n")
- }
- return requestString.join('').slice(0, -4);
- }
- }
- },
- methods: {
- handleUseHistory({
- method,
- url,
- path
- }) {
- this.method = method;
- this.url = url;
- this.path = path;
- this.$refs.request.$el.scrollIntoView({
- behavior: 'smooth'
- });
- },
- async sendRequest() {
- if (!this.isValidURL) {
- alert('Please check the formatting of the URL.');
- return;
- }
- // Start showing the loading bar as soon as possible.
- // The nuxt axios module will hide it when the request is made.
- this.$nuxt.$loading.start();
- if (this.$refs.response.$el.classList.contains('hidden')) {
- this.$refs.response.$el.classList.toggle('hidden')
- }
- this.$refs.response.$el.scrollIntoView({
- behavior: 'smooth'
- });
- this.previewEnabled = false;
- this.response.status = 'Fetching...';
- this.response.body = 'Loading...';
- const auth = this.auth === 'Basic' ? {
- username: this.httpUser,
- password: this.httpPassword
- } : null;
- let headers = {};
- // If the request has a request body, we want to ensure Content-Length and
- // Content-Type are sent.
- let requestBody;
- if (this.hasRequestBody) {
- requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
- Object.assign(headers, {
- //'Content-Length': requestBody.length,
- 'Content-Type': `${this.contentType}; charset=utf-8`
- });
- }
- // If the request uses a token for auth, we want to make sure it's sent here.
- if (this.auth === 'Bearer Token') headers['Authorization'] = `Bearer ${this.bearerToken}`;
- headers = Object.assign(
- // Clone the app headers object first, we don't want to
- // mutate it with the request headers added by default.
- Object.assign({}, this.headers),
- // We make our temporary headers object the source so
- // that you can override the added headers if you
- // specify them.
- headers
- );
- try {
- const payload = await this.$axios({
- method: this.method,
- url: this.url + this.path + this.queryString,
- auth,
- headers,
- data: requestBody ? requestBody.toString() : null
- });
- (() => {
- const status = this.response.status = payload.status;
- const headers = this.response.headers = payload.headers;
- // We don't need to bother parsing JSON, axios already handles it for us!
- const body = this.response.body = payload.data;
- const date = new Date().toLocaleDateString();
- const time = new Date().toLocaleTimeString();
- // Addition of an entry to the history component.
- const entry = {
- status,
- date,
- time,
- method: this.method,
- url: this.url,
- path: this.path
- };
- this.$refs.historyComponent.addEntry(entry);
- })();
- } catch (error) {
- if (error.response) {
- this.response.headers = error.response.headers;
- this.response.status = error.response.status;
- this.response.body = error.response.data;
- // Addition of an entry to the history component.
- const entry = {
- status: this.response.status,
- date: new Date().toLocaleDateString(),
- time: new Date().toLocaleTimeString(),
- method: this.method,
- url: this.url,
- path: this.path
- };
- this.$refs.historyComponent.addEntry(entry);
- return;
- }
- this.response.status = error.message;
- this.response.body = "See JavaScript console (F12) for details.";
- }
- },
- addRequestHeader() {
- this.headers.push({
- key: '',
- value: ''
- });
- return false
- },
- removeRequestHeader(index) {
- this.headers.splice(index, 1)
- },
- addRequestParam() {
- this.params.push({
- key: '',
- value: ''
- })
- return false
- },
- removeRequestParam(index) {
- this.params.splice(index, 1)
- },
- addRequestBodyParam() {
- this.bodyParams.push({
- key: '',
- value: ''
- })
- return false
- },
- removeRequestBodyParam(index) {
- this.bodyParams.splice(index, 1)
- },
- formatRawParams(event) {
- if ((event.which !== 13 && event.which !== 9)) {
- return;
- }
- const textBody = event.target.value;
- const textBeforeCursor = textBody.substring(0, event.target.selectionStart);
- const textAfterCursor = textBody.substring(event.target.selectionEnd);
- if (event.which === 13) {
- event.preventDefault();
- const oldSelectionStart = event.target.selectionStart;
- const lastLine = textBeforeCursor.split('\n').slice(-1)[0];
- const rightPadding = lastLine.match(/([\s\t]*).*/)[1] || "";
- event.target.value = textBeforeCursor + '\n' + rightPadding + textAfterCursor;
- setTimeout(() => event.target.selectionStart = event.target.selectionEnd = oldSelectionStart + rightPadding.length + 1, 1);
- } else if (event.which === 9) {
- event.preventDefault();
- const oldSelectionStart = event.target.selectionStart;
- event.target.value = textBeforeCursor + '\xa0\xa0' + textAfterCursor;
- event.target.selectionStart = event.target.selectionEnd = oldSelectionStart + 2;
- return false;
- }
- },
- copyRequest() {
- if (navigator.share) {
- let time = new Date().toLocaleTimeString();
- let date = new Date().toLocaleDateString();
- navigator.share({
- text: `Postwoman • API request builder at ${time} on ${date}`,
- url: window.location.href
- }).then(() => {
- // console.log('Thanks for sharing!');
- })
- .catch(console.error);
- } else {
- this.$refs.copyRequest.innerHTML = this.copiedButton + '<span>Copied</span>';
- var dummy = document.createElement('input');
- document.body.appendChild(dummy);
- dummy.value = window.location.href;
- dummy.select();
- document.execCommand('copy');
- document.body.removeChild(dummy);
- setTimeout(() => this.$refs.copyRequest.innerHTML = this.copyButton + '<span>Share URL</span>', 1500)
- }
- },
- copyRequestCode() {
- this.$refs.copyRequestCode.innerHTML = this.copiedButton + '<span>Copied</span>';
- this.$refs.generatedCode.select();
- document.execCommand("copy");
- setTimeout(() => this.$refs.copyRequestCode.innerHTML = this.copyButton + '<span>Copy</span>', 1500)
- },
- copyResponse() {
- this.$refs.copyResponse.innerHTML = this.copiedButton + '<span>Copied</span>';
- // Creates a textarea element
- var aux = document.createElement("textarea");
- var copy = this.responseType == 'application/json' ? JSON.stringify(this.response.body) : this.response.body;
- // Adds response body to the new textarea
- aux.innerText = copy;
- // Append the textarea to the body
- document.body.appendChild(aux);
- // Highlight the content
- aux.select();
- document.execCommand('copy');
- // Remove the input from the body
- document.body.removeChild(aux);
- setTimeout(() => this.$refs.copyResponse.innerHTML = this.copyButton + '<span>Copy</span>', 1500)
- },
- togglePreview() {
- this.previewEnabled = !this.previewEnabled;
- if (this.previewEnabled) {
- // If you want to add 'preview' support for other response types,
- // just add them here.
- if (this.responseType === "text/html") {
- // If the preview already has that URL loaded, let's not bother re-loading it all.
- if (this.$refs.previewFrame.getAttribute('data-previewing-url') === this.url)
- return;
- // Use DOMParser to parse document HTML.
- const previewDocument = new DOMParser().parseFromString(this.response.body, this.responseType);
- // Inject <base href="..."> tag to head, to fix relative CSS/HTML paths.
- previewDocument.head.innerHTML = `<base href="${this.url}">` + previewDocument.head.innerHTML;
- // Finally, set the iframe source to the resulting HTML.
- this.$refs.previewFrame.srcdoc = previewDocument.documentElement.outerHTML;
- this.$refs.previewFrame.setAttribute('data-previewing-url', this.url);
- }
- }
- },
- setRouteQueryState() {
- const flat = key => this[key] !== '' ? `${key}=${this[key]}&` : ''
- const deep = key => {
- const haveItems = [...this[key]].length
- if (haveItems && this[key]['value'] !== '') {
- return `${key}=${JSON.stringify(this[key])}&`
- } else return ''
- }
- let flats = ['method', 'url', 'path', 'auth', 'httpUser', 'httpPassword', 'bearerToken', 'contentType'].map(item => flat(item))
- let deeps = ['headers', 'params'].map(item => deep(item))
- let bodyParams = this.rawInput ? [flat('rawParams')] : [deep('bodyParams')];
- this.$router.replace('/?' + flats.concat(deeps, bodyParams).join('').slice(0, -1))
- },
- setRouteQueries(queries) {
- if (typeof(queries) !== 'object') throw new Error('Route query parameters must be a Object')
- for (const key in queries) {
- if (key === 'headers' || key === 'params' || key === 'bodyParams') this[key] = JSON.parse(queries[key])
- if (key === 'rawParams') {
- this.rawInput = true
- this.rawParams = queries['rawParams']
- } else if (typeof(this[key]) === 'string') this[key] = queries[key]
- }
- },
- observeRequestButton() {
- const requestElement = this.$refs.request.$el;
- const sendButtonElement = this.$refs.sendButton;
- const observer = new IntersectionObserver((entries, observer) => {
- entries.forEach(entry => {
- sendButtonElement.classList.toggle('show');
- });
- }, {
- threshold: 1
- });
- observer.observe(requestElement);
- },
- handleImport () {
- let textarea = document.getElementById("import-text")
- let text = textarea.value;
- try {
- let parsedCurl = parseCurlCommand(text);
- this.url = parsedCurl.url.replace(/"/g,"").replace(/'/g,"");
- this.url = this.url[this.url.length -1] == '/' ? this.url.slice(0, -1): this.url;
- this.path = "";
- this.headers
- this.showModal = false;
- this.headers = [];
- for (const key of Object.keys(parsedCurl.headers)) {
- this.headers.push({
- key: key,
- value: parsedCurl.headers[key]
- })
- }
- this.method = parsedCurl.method.toUpperCase();
- } catch (error) {
- this.showModal = false;
- }
- },
- toggleModal() {
- this.showModal = !this.showModal;
- }
- },
- mounted() {
- this.observeRequestButton();
- },
- created() {
- if (Object.keys(this.$route.query).length) this.setRouteQueries(this.$route.query);
- this.$watch(vm => [
- vm.method,
- vm.url,
- vm.auth,
- vm.path,
- vm.httpUser,
- vm.httpPassword,
- vm.bearerToken,
- vm.headers,
- vm.params,
- vm.bodyParams,
- vm.contentType,
- vm.rawParams
- ], val => {
- this.setRouteQueryState()
- })
- }
- }
- </script>
|