Merge branch 'master' into patch-zyb

This commit is contained in:
Liyas Thomas
2020-07-15 21:50:59 +05:30
committed by GitHub
28 changed files with 1175 additions and 549 deletions

View File

@@ -286,6 +286,11 @@ hr {
.info:not(.toasted) {
margin-left: 4px;
color: var(--fg-light-color);
.material-icons {
vertical-align: middle;
margin-right: 8px;
}
}
.bg-color {
@@ -535,11 +540,6 @@ ol {
padding: 0;
list-style-type: none;
&.response-headers {
display: inline-flex;
width: 50%;
}
ul,
ol {
margin: 0;

View File

@@ -62,7 +62,7 @@ TODO:
</div>
</div>
<p v-if="collections.length === 0" class="info">
Create new collection
<i class="material-icons">help_outline</i> Create new collection
</p>
<div class="virtual-list">
<ul>
@@ -76,9 +76,6 @@ TODO:
@edit-request="editRequest($event)"
/>
</li>
<li v-if="collections.length === 0">
<label>Collections are empty</label>
</li>
</ul>
</div>
<nuxt-link :to="localePath('doc')" :aria-label="$t('documentation')">

View File

@@ -35,14 +35,6 @@
</button>
</div>
</div>
<textarea
id="variableList"
readonly
v-textarea-auto-height="variableString"
v-model="variableString"
:placeholder="$t('add_one_variable')"
rows="1"
></textarea>
</li>
</ul>
<ul v-for="(variable, index) in this.editingEnvCopy.variables" :key="index">
@@ -114,13 +106,9 @@
</template>
<script>
import textareaAutoHeight from "~/directives/textareaAutoHeight"
import { fb } from "~/helpers/fb"
export default {
directives: {
textareaAutoHeight,
},
props: {
show: Boolean,
editingEnvironment: Object,

View File

@@ -25,7 +25,7 @@
</div>
</div>
<p v-if="environments.length === 0" class="info">
Create new environment
<i class="material-icons">help_outline</i> Create new environment
</p>
<div class="virtual-list">
<ul>
@@ -37,9 +37,6 @@
@select-environment="$emit('use-environment', environment)"
/>
</li>
<li v-if="environments.length === 0">
<label>Environments are empty</label>
</li>
</ul>
</div>
</pw-section>

View File

@@ -154,7 +154,7 @@
</li>
</ul>
<p v-if="history.length === 0" class="info">
{{ $t("history_empty") }}
<i class="material-icons">schedule</i> {{ $t("history_empty") }}
</p>
<div v-if="history.length !== 0">
<div class="flex-wrap" v-if="!isClearingHistory">
@@ -221,7 +221,7 @@
</div>
<div class="flex-wrap" v-else>
<label for="clear-history-button" class="info">
{{ $t("are_you_sure") }}
<i class="material-icons">help_outline</i> {{ $t("are_you_sure") }}
</label>
<div>
<button

View File

@@ -0,0 +1,44 @@
<template>
<div>
<tabs>
<tab
v-for="(lens, index) in validLenses"
:key="lens.lensName"
:id="lens.lensName"
:label="lens.lensName"
:selected="index === 0"
>
<component :is="lens.renderer" :response="response" />
</tab>
<tab
v-if="Object.keys(response.headers).length !== 0"
id="headers"
:label="`Headers \xA0 • \xA0 ${Object.keys(response.headers).length}`"
>
<headers :headers="response.headers" />
</tab>
</tabs>
</div>
</template>
<script>
import { getSuitableLenses, getLensRenderers } from "~/helpers/lenses/lenses"
export default {
components: {
tabs: () => import("../ui/tabs"),
tab: () => import("../ui/tab"),
headers: () => import("./headers"),
// Lens Renderers
...getLensRenderers(),
},
props: {
response: {},
},
computed: {
validLenses() {
return getSuitableLenses(this.response)
},
},
}
</script>

View File

@@ -0,0 +1,17 @@
<template>
<div>
<ul v-for="(value, key) in headers" :key="key">
<li>
<input :value="`${key} → ${value}`" :name="key" class="bg-color" readonly />
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
headers: {},
},
}
</script>

View File

@@ -0,0 +1,153 @@
<template>
<ul>
<li>
<div class="flex-wrap">
<label for="body">{{ $t("response") }}</label>
<div>
<button
class="icon"
@click="ToggleExpandResponse"
ref="ToggleExpandResponse"
v-if="response.body"
v-tooltip="{
content: !expandResponse ? $t('expand_response') : $t('collapse_response'),
}"
>
<i class="material-icons">
{{ !expandResponse ? "unfold_more" : "unfold_less" }}
</i>
</button>
<button
v-if="response.body"
class="icon"
@click.prevent="togglePreview"
v-tooltip="{
content: previewEnabled ? $t('hide_preview') : $t('preview_html'),
}"
>
<i class="material-icons">
{{ !previewEnabled ? "visibility" : "visibility_off" }}
</i>
</button>
<button
class="icon"
@click="downloadResponse"
ref="downloadResponse"
v-if="response.body"
v-tooltip="$t('download_file')"
>
<i class="material-icons">save_alt</i>
</button>
<button
class="icon"
@click="copyResponse"
ref="copyResponse"
v-if="response.body"
v-tooltip="$t('copy_response')"
>
<i class="material-icons">content_copy</i>
</button>
</div>
</div>
<div id="response-details-wrapper">
<Editor
:value="responseBodyText"
:lang="'html'"
:options="{
maxLines: responseBodyMaxLines,
minLines: '16',
fontSize: '16px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
useWorker: false,
}"
/>
<iframe
:class="{ hidden: !previewEnabled }"
class="covers-response"
ref="previewFrame"
src="about:blank"
></iframe>
</div>
</li>
</ul>
</template>
<script>
import AceEditor from "../../ui/ace-editor"
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
export default {
components: {
Editor: AceEditor,
},
mixins: [TextContentRendererMixin],
props: {
response: {},
},
data() {
return {
expandResponse: false,
responseBodyMaxLines: 16,
doneButton: '<i class="material-icons">done</i>',
downloadButton: '<i class="material-icons">save_alt</i>',
copyButton: '<i class="material-icons">content_copy</i>',
previewEnabled: false,
}
},
methods: {
ToggleExpandResponse() {
this.expandResponse = !this.expandResponse
this.responseBodyMaxLines = this.responseBodyMaxLines == Infinity ? 16 : Infinity
},
downloadResponse() {
const dataToWrite = this.responseBodyText
const file = new Blob([dataToWrite], { type: "text/html" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO get uri from meta
a.download = `response on ${Date()}`.replace(/\./g, "[dot]")
document.body.appendChild(a)
a.click()
this.$refs.downloadResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("download_started"), {
icon: "done",
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
this.$refs.downloadResponse.innerHTML = this.downloadButton
}, 1000)
},
copyResponse() {
this.$refs.copyResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("copied_to_clipboard"), {
icon: "done",
})
const aux = document.createElement("textarea")
const copy = this.responseBodyText
aux.innerText = copy
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
document.body.removeChild(aux)
setTimeout(() => (this.$refs.copyResponse.innerHTML = this.copyButton), 1000)
},
togglePreview() {
this.previewEnabled = !this.previewEnabled
if (this.previewEnabled) {
if (this.$refs.previewFrame.getAttribute("data-previewing-url") === this.url) return
// Use DOMParser to parse document HTML.
const previewDocument = new DOMParser().parseFromString(this.responseBodyText, "text/html")
// 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)
}
},
},
}
</script>

View File

@@ -0,0 +1,102 @@
<template>
<ul>
<li>
<div class="flex-wrap">
<label for="body">{{ $t("response") }}</label>
<div>
<button
class="icon"
@click="downloadResponse"
ref="downloadResponse"
v-if="response.body"
v-tooltip="$t('download_file')"
>
<i class="material-icons">save_alt</i>
</button>
</div>
</div>
<div id="response-details-wrapper">
<img class="response-image" :src="imageSource" />
</div>
</li>
</ul>
</template>
<style scoped lang="scss">
.response-image {
max-width: 100%;
}
</style>
<script>
export default {
props: {
response: {},
},
data() {
return {
imageSource: "",
doneButton: '<i class="material-icons">done</i>',
downloadButton: '<i class="material-icons">save_alt</i>',
}
},
computed: {
responseType() {
return (this.response.headers["content-type"] || "").split(";")[0].toLowerCase()
},
},
watch: {
response: {
immediate: true,
handler(newValue) {
this.imageSource = ""
const buf = this.response.body
const bytes = new Uint8Array(buf)
const blob = new Blob([bytes.buffer])
const reader = new FileReader()
reader.onload = (e) => {
this.imageSource = e.target.result
}
reader.readAsDataURL(blob)
},
},
},
mounted() {
this.imageSource = ""
const buf = this.response.body
const bytes = new Uint8Array(buf)
const blob = new Blob([bytes.buffer])
const reader = new FileReader()
reader.onload = (e) => {
this.imageSource = e.target.result
}
reader.readAsDataURL(blob)
},
methods: {
downloadResponse() {
const dataToWrite = this.response.body
const file = new Blob([dataToWrite], { type: this.responseType })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO get uri from meta
a.download = `response on ${Date()}`.replace(/\./g, "[dot]")
document.body.appendChild(a)
a.click()
this.$refs.downloadResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("download_started"), {
icon: "done",
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
this.$refs.downloadResponse.innerHTML = this.downloadButton
}, 1000)
},
},
}
</script>

View File

@@ -0,0 +1,149 @@
<template>
<ul>
<li>
<div class="flex-wrap">
<label for="body">{{ $t("response") }}</label>
<div>
<button
class="icon"
@click="ToggleExpandResponse"
ref="ToggleExpandResponse"
v-if="response.body"
v-tooltip="{
content: !expandResponse ? $t('expand_response') : $t('collapse_response'),
}"
>
<i class="material-icons">
{{ !expandResponse ? "unfold_more" : "unfold_less" }}
</i>
</button>
<button
class="icon"
@click="downloadResponse"
ref="downloadResponse"
v-if="response.body && canDownloadResponse"
v-tooltip="$t('download_file')"
>
<i class="material-icons">save_alt</i>
</button>
<button
class="icon"
@click="copyResponse"
ref="copyResponse"
v-if="response.body"
v-tooltip="$t('copy_response')"
>
<i class="material-icons">content_copy</i>
</button>
</div>
</div>
<div class="valid-warning" v-if="jsonInvalid">
<p class="info"><i class="material-icons">error_outline</i> Invalid JSON</p>
</div>
<div id="response-details-wrapper">
<Editor
:value="jsonBodyText"
:lang="'json'"
:options="{
maxLines: responseBodyMaxLines,
minLines: '16',
fontSize: '16px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
useWorker: false,
}"
/>
</div>
</li>
</ul>
</template>
<script>
import AceEditor from "../../ui/ace-editor"
import { isJSONContentType } from "~/helpers/utils/contenttypes"
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
export default {
components: {
Editor: AceEditor,
},
mixins: [TextContentRendererMixin],
props: {
response: {},
},
data() {
return {
expandResponse: false,
jsonInvalid: false,
responseBodyMaxLines: 16,
doneButton: '<i class="material-icons">done</i>',
downloadButton: '<i class="material-icons">save_alt</i>',
copyButton: '<i class="material-icons">content_copy</i>',
}
},
computed: {
jsonBodyText() {
try {
this.jsonInvalid = false
return JSON.stringify(JSON.parse(this.responseBodyText), null, 2)
} catch (e) {
// Most probs invalid JSON was returned, so drop prettification (should we warn ?)
this.jsonInvalid = true
return this.responseBodyText
}
},
responseType() {
return (this.response.headers["content-type"] || "").split(";")[0].toLowerCase()
},
canDownloadResponse() {
return (
this.response &&
this.response.headers &&
this.response.headers["content-type"] &&
isJSONContentType(this.response.headers["content-type"])
)
},
},
methods: {
ToggleExpandResponse() {
this.expandResponse = !this.expandResponse
this.responseBodyMaxLines = this.responseBodyMaxLines == Infinity ? 16 : Infinity
},
downloadResponse() {
const dataToWrite = this.responseBodyText
const file = new Blob([dataToWrite], { type: this.responseType })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO get uri from meta
a.download = `response on ${Date()}`.replace(/\./g, "[dot]")
document.body.appendChild(a)
a.click()
this.$refs.downloadResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("download_started"), {
icon: "done",
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
this.$refs.downloadResponse.innerHTML = this.downloadButton
}, 1000)
},
copyResponse() {
this.$refs.copyResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("copied_to_clipboard"), {
icon: "done",
})
const aux = document.createElement("textarea")
const copy = this.responseBodyText
aux.innerText = copy
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
document.body.removeChild(aux)
setTimeout(() => (this.$refs.copyResponse.innerHTML = this.copyButton), 1000)
},
},
}
</script>

View File

@@ -0,0 +1,134 @@
<template>
<ul>
<li>
<div class="flex-wrap">
<label for="body">{{ $t("response") }}</label>
<div>
<button
class="icon"
@click="ToggleExpandResponse"
ref="ToggleExpandResponse"
v-if="response.body"
v-tooltip="{
content: !expandResponse ? $t('expand_response') : $t('collapse_response'),
}"
>
<i class="material-icons">
{{ !expandResponse ? "unfold_more" : "unfold_less" }}
</i>
</button>
<button
class="icon"
@click="downloadResponse"
ref="downloadResponse"
v-if="response.body && canDownloadResponse"
v-tooltip="$t('download_file')"
>
<i class="material-icons">save_alt</i>
</button>
<button
class="icon"
@click="copyResponse"
ref="copyResponse"
v-if="response.body"
v-tooltip="$t('copy_response')"
>
<i class="material-icons">content_copy</i>
</button>
</div>
</div>
<div id="response-details-wrapper">
<Editor
:value="responseBodyText"
:lang="'plain_text'"
:options="{
maxLines: responseBodyMaxLines,
minLines: '16',
fontSize: '16px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
useWorker: false,
}"
/>
</div>
</li>
</ul>
</template>
<script>
import AceEditor from "../../ui/ace-editor"
import { isJSONContentType } from "~/helpers/utils/contenttypes"
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
export default {
components: {
Editor: AceEditor,
},
mixins: [TextContentRendererMixin],
props: {
response: {},
},
data() {
return {
expandResponse: false,
responseBodyMaxLines: 16,
doneButton: '<i class="material-icons">done</i>',
downloadButton: '<i class="material-icons">save_alt</i>',
copyButton: '<i class="material-icons">content_copy</i>',
}
},
computed: {
responseType() {
return (this.response.headers["content-type"] || "").split(";")[0].toLowerCase()
},
canDownloadResponse() {
return (
this.response &&
this.response.headers &&
this.response.headers["content-type"] &&
isJSONContentType(this.response.headers["content-type"])
)
},
},
methods: {
ToggleExpandResponse() {
this.expandResponse = !this.expandResponse
this.responseBodyMaxLines = this.responseBodyMaxLines == Infinity ? 16 : Infinity
},
downloadResponse() {
const dataToWrite = this.responseBodyText
const file = new Blob([dataToWrite], { type: this.responseType })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO get uri from meta
a.download = `response on ${Date()}`.replace(/\./g, "[dot]")
document.body.appendChild(a)
a.click()
this.$refs.downloadResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("download_started"), {
icon: "done",
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
this.$refs.downloadResponse.innerHTML = this.downloadButton
}, 1000)
},
copyResponse() {
this.$refs.copyResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("copied_to_clipboard"), {
icon: "done",
})
const aux = document.createElement("textarea")
const copy = this.responseBodyText
aux.innerText = copy
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
document.body.removeChild(aux)
setTimeout(() => (this.$refs.copyResponse.innerHTML = this.copyButton), 1000)
},
},
}
</script>

View File

@@ -0,0 +1,125 @@
<template>
<ul>
<li>
<div class="flex-wrap">
<label for="body">{{ $t("response") }}</label>
<div>
<button
class="icon"
@click="ToggleExpandResponse"
ref="ToggleExpandResponse"
v-if="response.body"
v-tooltip="{
content: !expandResponse ? $t('expand_response') : $t('collapse_response'),
}"
>
<i class="material-icons">
{{ !expandResponse ? "unfold_more" : "unfold_less" }}
</i>
</button>
<button
class="icon"
@click="downloadResponse"
ref="downloadResponse"
v-if="response.body"
v-tooltip="$t('download_file')"
>
<i class="material-icons">save_alt</i>
</button>
<button
class="icon"
@click="copyResponse"
ref="copyResponse"
v-if="response.body"
v-tooltip="$t('copy_response')"
>
<i class="material-icons">content_copy</i>
</button>
</div>
</div>
<div id="response-details-wrapper">
<Editor
:value="responseBodyText"
:lang="'xml'"
:options="{
maxLines: responseBodyMaxLines,
minLines: '16',
fontSize: '16px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
useWorker: false,
}"
/>
</div>
</li>
</ul>
</template>
<script>
import AceEditor from "../../ui/ace-editor"
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
export default {
components: {
Editor: AceEditor,
},
mixins: [TextContentRendererMixin],
props: {
response: {},
},
data() {
return {
expandResponse: false,
responseBodyMaxLines: 16,
doneButton: '<i class="material-icons">done</i>',
downloadButton: '<i class="material-icons">save_alt</i>',
copyButton: '<i class="material-icons">content_copy</i>',
}
},
computed: {
responseType() {
return (this.response.headers["content-type"] || "").split(";")[0].toLowerCase()
},
},
methods: {
ToggleExpandResponse() {
this.expandResponse = !this.expandResponse
this.responseBodyMaxLines = this.responseBodyMaxLines == Infinity ? 16 : Infinity
},
downloadResponse() {
const dataToWrite = this.responseBodyText
const file = new Blob([dataToWrite], { type: this.responseType })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO get uri from meta
a.download = `response on ${Date()}`.replace(/\./g, "[dot]")
document.body.appendChild(a)
a.click()
this.$refs.downloadResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("download_started"), {
icon: "done",
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
this.$refs.downloadResponse.innerHTML = this.downloadButton
}, 1000)
},
copyResponse() {
this.$refs.copyResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("copied_to_clipboard"), {
icon: "done",
})
const aux = document.createElement("textarea")
const copy = this.responseBodyText
aux.innerText = copy
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
document.body.removeChild(aux)
setTimeout(() => (this.$refs.copyResponse.innerHTML = this.copyButton), 1000)
},
},
}
</script>

View File

@@ -0,0 +1,10 @@
export default {
props: {
response: {},
},
computed: {
responseBodyText() {
return new TextDecoder("utf-8").decode(this.response.body)
},
},
}

View File

@@ -1,8 +0,0 @@
export default {
name: "textareaAutoHeight",
update({ scrollHeight, clientHeight, style }) {
if (scrollHeight !== clientHeight) {
style.minHeight = `${scrollHeight}px`
}
},
}

View File

@@ -0,0 +1,8 @@
const htmlLens = {
lensName: "HTML",
supportedContentTypes: ["text/html"],
renderer: "htmlres",
rendererImport: () => import("~/components/lenses/renderers/HTMLLensRenderer"),
}
export default htmlLens

View File

@@ -0,0 +1,16 @@
const imageLens = {
lensName: "Image",
supportedContentTypes: [
"image/gif",
"image/jpeg",
"image/png",
"image/bmp",
"image/svg+xml",
"image/x-icon",
"image/vnd.microsoft.icon",
],
renderer: "imageres",
rendererImport: () => import("~/components/lenses/renderers/ImageLensRenderer"),
}
export default imageLens

View File

@@ -0,0 +1,8 @@
const jsonLens = {
lensName: "JSON",
supportedContentTypes: ["application/json", "application/hal+json", "application/vnd.api+json"],
renderer: "json",
rendererImport: () => import("~/components/lenses/renderers/JSONLensRenderer"),
}
export default jsonLens

37
helpers/lenses/lenses.js Normal file
View File

@@ -0,0 +1,37 @@
import jsonLens from "./jsonLens"
import rawLens from "./rawLens"
import imageLens from "./imageLens"
import htmlLens from "./htmlLens"
import xmlLens from "./xmlLens"
const lenses = [jsonLens, imageLens, htmlLens, xmlLens, rawLens]
export function getSuitableLenses(response) {
const result = []
if (response && response.headers && response.headers["content-type"]) {
const properContentType = response.headers["content-type"].split(";")[0]
for (const lens of lenses) {
if (
lens.supportedContentTypes === null ||
lens.supportedContentTypes.includes(properContentType)
) {
result.push(lens)
}
}
} else {
// We don't know the content type, so lets just add rawLens
result.push(rawLens)
}
return result
}
export function getLensRenderers() {
const response = {}
for (const lens of lenses) {
response[lens.renderer] = lens.rendererImport
}
return response
}

View File

@@ -0,0 +1,8 @@
const rawLens = {
lensName: "Raw",
supportedContentTypes: null,
renderer: "raw",
rendererImport: () => import("~/components/lenses/renderers/RawLensRenderer"),
}
export default rawLens

View File

@@ -0,0 +1,8 @@
const xmlLens = {
lensName: "XML",
supportedContentTypes: ["application/xml", "image/svg+xml", "text/xml", "application/rss+xml"],
renderer: "xmlres",
rendererImport: () => import("~/components/lenses/renderers/XMLLensRenderer"),
}
export default xmlLens

View File

@@ -1,4 +1,5 @@
import axios from "axios"
import { decodeB64StringToArrayBuffer } from "../utils/b64"
let cancelSource = axios.CancelToken.source()
@@ -13,11 +14,19 @@ const axiosWithProxy = async (req, { state }) => {
try {
const { data } = await axios.post(
state.postwoman.settings.PROXY_URL || "https://postwoman.apollosoftware.xyz/",
req,
{
...req,
wantsBinary: true,
},
{
cancelToken: cancelSource.token,
}
)
if (data.isBinary) {
data.data = decodeB64StringToArrayBuffer(data.data)
}
return data
} catch (e) {
// Check if the throw is due to a cancellation
@@ -34,28 +43,9 @@ const axiosWithoutProxy = async (req, _store) => {
const res = await axios({
...req,
cancelToken: cancelSource.token,
transformResponse: [
(data, headers) => {
// If the response has a JSON content type, try parsing it
if (
headers["content-type"] &&
(headers["content-type"].startsWith("application/json") ||
headers["content-type"].startsWith("application/vnd.api+json") ||
headers["content-type"].startsWith("application/hal+json"))
) {
try {
const jsonData = JSON.parse(data)
return jsonData
} catch (e) {
return data
}
}
// Else return the string itself without any transformations
return data
},
],
responseType: "arraybuffer",
})
return res
} catch (e) {
if (axios.isCancel(e)) {

View File

@@ -1,3 +1,5 @@
import { decodeB64StringToArrayBuffer } from "../utils/b64"
export const hasExtensionInstalled = () =>
typeof window.__POSTWOMAN_EXTENSION_HOOK__ !== "undefined"
@@ -17,13 +19,24 @@ const extensionWithProxy = async (req, { state }) => {
const { data } = await window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest({
method: "post",
url: state.postwoman.settings.PROXY_URL || "https://postwoman.apollosoftware.xyz/",
data: req,
data: {
...req,
wantsBinary: true,
},
})
if (data.isBinary) {
data.data = decodeB64StringToArrayBuffer(data.data)
}
return data
}
const extensionWithoutProxy = async (req, _store) => {
const res = await window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest(req)
const res = await window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest({
...req,
wantsBinary: true,
})
return res
}

31
helpers/utils/b64.js Normal file
View File

@@ -0,0 +1,31 @@
export const decodeB64StringToArrayBuffer = (input) => {
const bytes = Math.floor((input.length / 4) * 3)
const ab = new ArrayBuffer(bytes)
const uarray = new Uint8Array(ab)
const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
let chr1, chr2, chr3
let enc1, enc2, enc3, enc4
let j = 0
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "")
for (let i = 0; i < bytes; i += 3) {
//get the 3 octets in 4 ASCII chars
enc1 = keyStr.indexOf(input.charAt(j++))
enc2 = keyStr.indexOf(input.charAt(j++))
enc3 = keyStr.indexOf(input.charAt(j++))
enc4 = keyStr.indexOf(input.charAt(j++))
chr1 = (enc1 << 2) | (enc2 >> 4)
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2)
chr3 = ((enc3 & 3) << 6) | enc4
uarray[i] = chr1
if (enc3 != 64) uarray[i + 1] = chr2
if (enc4 != 64) uarray[i + 2] = chr3
}
return ab
}

View File

@@ -285,5 +285,7 @@
"mqtt_topic_title": "Publish / Subscribe topic",
"mqtt_publish": "Publish",
"mqtt_subscribe": "Subscribe",
"mqtt_unsubscribe": "Unsubscribe"
"mqtt_unsubscribe": "Unsubscribe",
"pre_request_script": "Pre-request Script",
"tests": "Tests"
}

View File

@@ -27,7 +27,7 @@
</pw-section>
<pw-section class="orange" :label="$t('headers')" ref="headers">
<ul>
<ul v-if="headers.length !== 0">
<li>
<div class="flex-wrap">
<label for="headerList">{{ $t("header_list") }}</label>
@@ -37,14 +37,6 @@
</button>
</div>
</div>
<textarea
id="headerList"
readonly
v-textarea-auto-height="headerString"
v-model="headerString"
:placeholder="$t('add_one_header')"
rows="1"
></textarea>
</li>
</ul>
<ul v-for="(header, index) in headers" :key="`${header.value}_${index}`">
@@ -339,7 +331,6 @@
<script>
import axios from "axios"
import * as gql from "graphql"
import textareaAutoHeight from "~/directives/textareaAutoHeight"
import { commonHeaders } from "~/helpers/headers"
import AceEditor from "~/components/ui/ace-editor"
import QueryEditor from "~/components/graphql/queryeditor"
@@ -347,9 +338,6 @@ import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { sendNetworkRequest } from "~/helpers/network"
export default {
directives: {
textareaAutoHeight,
},
components: {
"pw-section": () => import("~/components/layout/section"),
"gql-field": () => import("~/components/graphql/field"),

View File

@@ -2,39 +2,6 @@
<div class="page">
<div class="content">
<div class="page-columns inner-left">
<pw-section v-if="showPreRequestScript" class="orange" label="Pre-Request" ref="preRequest">
<ul>
<li>
<div class="flex-wrap">
<label for="generatedCode">{{ $t("javascript_code") }}</label>
<div>
<a
href="https://github.com/liyasthomas/postwoman/wiki/Pre-Request-Scripts"
target="_blank"
rel="noopener"
>
<button class="icon" v-tooltip="$t('wiki')">
<i class="material-icons">help_outline</i>
</button>
</a>
</div>
</div>
<Editor
v-model="preRequestScript"
:lang="'javascript'"
:options="{
maxLines: '16',
minLines: '8',
fontSize: '16px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
/>
</li>
</ul>
</pw-section>
<pw-section class="blue" :label="$t('request')" ref="request">
<ul>
<li class="shrink">
@@ -297,15 +264,18 @@
<div v-if="!rawInput">
<ul>
<li>
<label for="reqParamList">{{ $t("parameter_list") }}</label>
<textarea
id="reqParamList"
readonly
v-textarea-auto-height="rawRequestBody"
v-model="rawRequestBody"
:placeholder="$t('add_one_parameter')"
rows="1"
></textarea>
<div class="flex-wrap">
<label for="reqParamList">{{ $t("parameter_list") }}</label>
<div>
<button
class="icon"
@click="clearContent('bodyParams', $event)"
v-tooltip.bottom="$t('clear')"
>
<i class="material-icons">clear_all</i>
</button>
</div>
</div>
</li>
</ul>
<ul v-for="(param, index) in bodyParams" :key="index">
@@ -364,7 +334,18 @@
<div v-else>
<ul>
<li>
<label for="rawBody">{{ $t("raw_request_body") }}</label>
<div class="flex-wrap">
<label for="rawBody">{{ $t("raw_request_body") }}</label>
<div>
<button
class="icon"
@click="clearContent('rawParams', $event)"
v-tooltip.bottom="$t('clear')"
>
<i class="material-icons">clear_all</i>
</button>
</div>
</div>
<Editor
v-model="rawParams"
:lang="rawInputEditorLang"
@@ -402,40 +383,6 @@
>
<i class="material-icons">code</i>
</button>
<button
class="icon"
id="preRequestScriptButton"
v-tooltip.bottom="{
content: !showPreRequestScript
? $t('show_prerequest_script')
: $t('hide_prerequest_script'),
}"
@click="showPreRequestScript = !showPreRequestScript"
>
<i
class="material-icons"
:class="showPreRequestScript"
v-if="!showPreRequestScript"
>
playlist_add
</i>
<i class="material-icons" :class="showPreRequestScript" v-else>
close
</i>
</button>
<button
class="icon"
id="preRequestScriptButto"
v-tooltip.bottom="{
content: !testsEnabled ? 'Enable Tests' : 'Disable Tests',
}"
@click="testsEnabled = !testsEnabled"
>
<i class="material-icons" :class="testsEnabled" v-if="!testsEnabled">
playlist_add_check
</i>
<i class="material-icons" :class="testsEnabled" v-else>close</i>
</button>
</span>
<span>
<button
@@ -471,73 +418,105 @@
</div>
</pw-section>
<pw-section v-if="testsEnabled" class="orange" label="Tests" ref="postRequestTests">
<ul>
<li>
<div class="flex-wrap">
<label for="generatedCode">{{ $t("javascript_code") }}</label>
<div>
<a
href="https://github.com/liyasthomas/postwoman/wiki/Post-Requests-Tests"
target="_blank"
rel="noopener"
>
<button class="icon" v-tooltip="$t('wiki')">
<i class="material-icons">help_outline</i>
</button>
</a>
</div>
</div>
<Editor
v-model="testScript"
:lang="'javascript'"
:options="{
maxLines: '16',
minLines: '8',
fontSize: '16px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
/>
<div v-if="testReports">
<div class="flex-wrap">
<label>Test Reports</label>
<div>
<button
class="icon"
@click="clearContent('tests', $event)"
v-tooltip.bottom="$t('clear')"
>
<i class="material-icons">clear_all</i>
</button>
</div>
</div>
<div v-for="(testReport, index) in testReports" :key="index">
<div v-if="testReport.startBlock" class="info">
<h4>{{ testReport.startBlock }}</h4>
</div>
<p v-else-if="testReport.result" class="flex-wrap info">
<span :class="testReport.styles.class">
<i class="material-icons">
{{ testReport.styles.icon }}
</i>
<span>&nbsp; {{ testReport.result }}</span>
<span v-if="testReport.message">
<label>&nbsp; • &nbsp; {{ testReport.message }}</label>
</span>
</span>
</p>
<div v-else-if="testReport.endBlock"><hr /></div>
</div>
</div>
</li>
</ul>
</pw-section>
<section id="options">
<tabs>
<tab :id="'authentication'" :label="$t('authentication')" :selected="true">
<tab
:id="'params'"
:label="
$t('parameters') + `${params.length !== 0 ? ' \xA0 • \xA0 ' + params.length : ''}`
"
:selected="true"
>
<pw-section class="pink" label="Parameters" ref="parameters">
<ul v-if="params.length !== 0">
<li>
<div class="flex-wrap">
<label for="paramList">{{ $t("parameter_list") }}</label>
<div>
<button
class="icon"
@click="clearContent('parameters', $event)"
v-tooltip.bottom="$t('clear')"
>
<i class="material-icons">clear_all</i>
</button>
</div>
</div>
</li>
</ul>
<ul v-for="(param, index) in params" :key="index">
<li>
<input
:placeholder="$t('parameter_count', { count: index + 1 })"
:name="'param' + index"
:value="param.key"
@change="
$store.commit('setKeyParams', {
index,
value: $event.target.value,
})
"
autofocus
/>
</li>
<li>
<input
:placeholder="$t('value_count', { count: index + 1 })"
:name="'value' + index"
:value="param.value"
@change="
$store.commit('setValueParams', {
index,
value: $event.target.value,
})
"
/>
</li>
<li>
<span class="select-wrapper">
<select
:name="'type' + index"
@change="
$store.commit('setTypeParams', {
index,
value: $event.target.value,
})
"
>
<option value="query" :selected="param.type === 'query'">{{
$t("query")
}}</option>
<option value="path" :selected="param.type === 'path'">{{
$t("path")
}}</option>
</select>
</span>
</li>
<div>
<li>
<button
class="icon"
@click="removeRequestParam(index)"
v-tooltip.bottom="$t('delete')"
id="param"
>
<i class="material-icons">delete</i>
</button>
</li>
</div>
</ul>
<ul>
<li>
<button class="icon" @click="addRequestParam">
<i class="material-icons">add</i>
<span>{{ $t("add_new") }}</span>
</button>
</li>
</ul>
</pw-section>
</tab>
<tab :id="'authentication'" :label="$t('authentication')">
<pw-section class="cyan" :label="$t('authentication')" ref="authentication">
<ul>
<li>
@@ -741,9 +720,14 @@
</pw-section>
</tab>
<tab :id="'headers'" :label="$t('headers')">
<tab
:id="'headers'"
:label="
$t('headers') + `${headers.length !== 0 ? ' \xA0 • \xA0 ' + headers.length : ''}`
"
>
<pw-section class="orange" label="Headers" ref="headers">
<ul>
<ul v-if="headers.length !== 0">
<li>
<div class="flex-wrap">
<label for="headerList">{{ $t("header_list") }}</label>
@@ -757,14 +741,6 @@
</button>
</div>
</div>
<textarea
id="headerList"
readonly
v-textarea-auto-height="headerString"
v-model="headerString"
:placeholder="$t('add_one_header')"
rows="1"
></textarea>
</li>
</ul>
<ul v-for="(header, index) in headers" :key="`${header.value}_${index}`">
@@ -822,119 +798,123 @@
</pw-section>
</tab>
<tab :id="'params'" :label="$t('parameters')">
<pw-section class="pink" label="Parameters" ref="parameters">
<tab :id="'pre_request_script'" :label="$t('pre_request_script')">
<pw-section
v-if="showPreRequestScript"
class="orange"
:label="$t('pre_request_script')"
ref="preRequest"
>
<ul>
<li>
<div class="flex-wrap">
<label for="paramList">{{ $t("parameter_list") }}</label>
<label for="generatedCode">{{ $t("javascript_code") }}</label>
<div>
<button
class="icon"
@click="clearContent('parameters', $event)"
v-tooltip.bottom="$t('clear')"
<a
href="https://github.com/liyasthomas/postwoman/wiki/Pre-Request-Scripts"
target="_blank"
rel="noopener"
>
<i class="material-icons">clear_all</i>
</button>
<button class="icon" v-tooltip="$t('wiki')">
<i class="material-icons">help_outline</i>
</button>
</a>
</div>
</div>
<textarea
id="paramList"
readonly
v-textarea-auto-height="queryString"
v-model="queryString"
:placeholder="$t('add_one_parameter')"
rows="1"
></textarea>
</li>
</ul>
<ul v-for="(param, index) in params" :key="index">
<li>
<input
:placeholder="$t('parameter_count', { count: index + 1 })"
:name="'param' + index"
:value="param.key"
@change="
$store.commit('setKeyParams', {
index,
value: $event.target.value,
})
"
autofocus
<Editor
v-model="preRequestScript"
:lang="'javascript'"
:options="{
maxLines: '16',
minLines: '8',
fontSize: '16px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
/>
</li>
<li>
<input
:placeholder="$t('value_count', { count: index + 1 })"
:name="'value' + index"
:value="param.value"
@change="
$store.commit('setValueParams', {
index,
value: $event.target.value,
})
"
/>
</li>
<li>
<span class="select-wrapper">
<select
:name="'type' + index"
@change="
$store.commit('setTypeParams', {
index,
value: $event.target.value,
})
"
>
<option value="query" :selected="param.type === 'query'">{{
$t("query")
}}</option>
<option value="path" :selected="param.type === 'path'">{{
$t("path")
}}</option>
</select>
</span>
</li>
<div>
<li>
<button
class="icon"
@click="removeRequestParam(index)"
v-tooltip.bottom="$t('delete')"
id="param"
>
<i class="material-icons">delete</i>
</button>
</li>
</div>
</ul>
</pw-section>
</tab>
<tab
:id="'tests'"
:label="
$t('tests') +
`${testReports.length !== 0 ? ' \xA0 • \xA0 ' + testReports.length : ''}`
"
>
<pw-section
v-if="testsEnabled"
class="orange"
:label="$t('tests')"
ref="postRequestTests"
>
<ul>
<li>
<button class="icon" @click="addRequestParam">
<i class="material-icons">add</i>
<span>{{ $t("add_new") }}</span>
</button>
<div class="flex-wrap">
<label for="generatedCode">{{ $t("javascript_code") }}</label>
<div>
<a
href="https://github.com/liyasthomas/postwoman/wiki/Post-Requests-Tests"
target="_blank"
rel="noopener"
>
<button class="icon" v-tooltip="$t('wiki')">
<i class="material-icons">help_outline</i>
</button>
</a>
</div>
</div>
<Editor
v-model="testScript"
:lang="'javascript'"
:options="{
maxLines: '16',
minLines: '8',
fontSize: '16px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
/>
<div v-if="testReports.length !== 0">
<div class="flex-wrap">
<label>Test Reports</label>
<div>
<button
class="icon"
@click="clearContent('tests', $event)"
v-tooltip.bottom="$t('clear')"
>
<i class="material-icons">clear_all</i>
</button>
</div>
</div>
<div v-for="(testReport, index) in testReports" :key="index">
<div v-if="testReport.startBlock" class="info">
<h4>{{ testReport.startBlock }}</h4>
</div>
<p v-else-if="testReport.result" class="flex-wrap info">
<span :class="testReport.styles.class">
<i class="material-icons">
{{ testReport.styles.icon }}
</i>
<span>&nbsp; {{ testReport.result }}</span>
<span v-if="testReport.message">
<label>&nbsp; • &nbsp; {{ testReport.message }}</label>
</span>
</span>
</p>
<div v-else-if="testReport.endBlock"><hr /></div>
</div>
</div>
</li>
</ul>
</pw-section>
</tab>
</tabs>
<!-- <div class="flex-wrap">
<span></span>
<button
class="icon hide-on-small-screen"
@click="activeSidebar = !activeSidebar"
v-tooltip="{
content: activeSidebar ? 'Hide Sidebar' : 'Show Sidebar'
}"
>
<i class="material-icons">
{{ activeSidebar ? "last_page" : "first_page" }}
</i>
</button>
</div> -->
</section>
<pw-section class="purple" id="response" :label="$t('response')" ref="response">
@@ -952,83 +932,9 @@
/>
</li>
</ul>
<ul v-if="response.body">
<li>
<div class="flex-wrap">
<label for="body">{{ $t("response") }}</label>
<div>
<button
class="icon"
@click="ToggleExpandResponse"
ref="ToggleExpandResponse"
v-if="response.body"
v-tooltip="{
content: !expandResponse ? $t('expand_response') : $t('collapse_response'),
}"
>
<i class="material-icons">
{{ !expandResponse ? "unfold_more" : "unfold_less" }}
</i>
</button>
<button
class="icon"
@click="downloadResponse"
ref="downloadResponse"
v-if="response.body && canDownloadResponse"
v-tooltip="$t('download_file')"
>
<i class="material-icons">save_alt</i>
</button>
<button
class="icon"
@click="copyResponse"
ref="copyResponse"
v-if="response.body"
v-tooltip="$t('copy_response')"
>
<i class="material-icons">content_copy</i>
</button>
</div>
</div>
<div id="response-details-wrapper">
<Editor
:value="responseBodyText"
:lang="responseBodyType"
:options="{
maxLines: responseBodyMaxLines,
minLines: '16',
fontSize: '16px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
useWorker: false,
}"
/>
<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">
<i class="material-icons">
{{ !previewEnabled ? "visibility" : "visibility_off" }}
</i>
<span>
{{ previewEnabled ? $t("hide_preview") : $t("preview_html") }}
</span>
</button>
</div>
</li>
</ul>
<ul v-for="(value, key) in response.headers" :key="key" class="response-headers">
<li>
<label :for="key">{{ key }}</label>
<input :id="key" :value="value" :name="key" readonly />
</li>
</ul>
<div v-if="response.body && response.body !== $t('loading')">
<response-renderer :response="response" />
</div>
</pw-section>
</div>
@@ -1343,7 +1249,6 @@ import section from "~/components/layout/section"
import url from "url"
import querystring from "querystring"
import { commonHeaders } from "~/helpers/headers"
import textareaAutoHeight from "~/directives/textareaAutoHeight"
import parseCurlCommand from "~/assets/js/curlparser.js"
import getEnvironmentVariablesFromScript from "~/helpers/preRequest"
import runTestScriptWithVariables from "~/helpers/postwomanTesting"
@@ -1408,9 +1313,6 @@ const parseHeaders = (xhr) => {
export const findStatusGroup = (responseStatus) =>
statusCategories.find(({ statusCodeRegex }) => statusCodeRegex.test(responseStatus))
export default {
directives: {
textareaAutoHeight,
},
components: {
"pw-section": section,
"pw-toggle": () => import("~/components/ui/toggle"),
@@ -1426,15 +1328,16 @@ export default {
login: () => import("~/components/firebase/login"),
tabs: () => import("~/components/ui/tabs"),
tab: () => import("~/components/ui/tab"),
"response-renderer": () => import("~/components/lenses/ResponseBodyRenderer"),
},
data() {
return {
showModal: false,
showPreRequestScript: false,
testsEnabled: false,
showPreRequestScript: true,
testsEnabled: true,
testScript: "// pw.expect('variable').toBe('value');",
preRequestScript: "// pw.env.set('variable', 'value');",
testReports: null,
testReports: [],
copyButton: '<i class="material-icons">content_copy</i>',
downloadButton: '<i class="material-icons">save_alt</i>',
doneButton: '<i class="material-icons">done</i>',
@@ -1445,9 +1348,7 @@ export default {
body: "",
},
validContentTypes: knownContentTypes,
previewEnabled: false,
paramsWatchEnabled: true,
expandResponse: false,
showTokenList: false,
showTokenRequest: false,
showTokenRequestList: false,
@@ -1455,9 +1356,6 @@ export default {
showRequestModal: false,
editRequest: {},
urlExcludes: {},
responseBodyText: "",
responseBodyType: "text",
responseBodyMaxLines: 16,
activeSidebar: true,
fb,
customMethod: false,
@@ -1513,27 +1411,6 @@ export default {
}
this.setRouteQueryState()
},
"response.body": function (val) {
if (
this.response.body === this.$t("waiting_send_req") ||
this.response.body === this.$t("loading")
) {
this.responseBodyText = this.response.body
this.responseBodyType = "text"
} else {
if (isJSONContentType(this.responseType)) {
this.responseBodyText = JSON.stringify(this.response.body, null, 2)
this.responseBodyType =
this.response.body.constructor.name === "Object" ? "json" : "json5"
} else if (this.responseType === "text/html") {
this.responseBodyText = this.response.body
this.responseBodyType = "html"
} else {
this.responseBodyText = this.response.body
this.responseBodyType = "text"
}
}
},
params: {
handler: function (newValue) {
if (!this.paramsWatchEnabled) {
@@ -1589,7 +1466,6 @@ export default {
this.showRequestModal = true
},
method() {
// this.$store.commit('setState', { 'value': ["POST", "PUT", "PATCH", "DELETE"].includes(this.method) ? 'application/json' : '', 'attribute': 'contentType' })
this.contentType = ["POST", "PUT", "PATCH", "DELETE"].includes(this.method)
? "application/json"
: ""
@@ -1609,14 +1485,6 @@ export default {
isJSONContentType(this.contentType)
)
},
canDownloadResponse() {
return (
this.response &&
this.response.headers &&
this.response.headers["content-type"] &&
isJSONContentType(this.response.headers["content-type"])
)
},
uri: {
get() {
return this.$store.state.request.uri ? this.$store.state.request.uri : this.url + this.path
@@ -2138,9 +2006,6 @@ export default {
// 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.previewEnabled = false
this.response.status = this.$t("fetching")
this.response.body = this.$t("loading")
@@ -2473,67 +2338,6 @@ export default {
document.execCommand("copy")
setTimeout(() => (this.$refs.copyRequestCode.innerHTML = this.copyButton), 1000)
},
ToggleExpandResponse() {
this.expandResponse = !this.expandResponse
this.responseBodyMaxLines = this.responseBodyMaxLines == Infinity ? 16 : Infinity
},
copyResponse() {
this.$refs.copyResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("copied_to_clipboard"), {
icon: "done",
})
const aux = document.createElement("textarea")
const copy = isJSONContentType(this.responseType)
? JSON.stringify(this.response.body, null, 2)
: this.response.body
aux.innerText = copy
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
document.body.removeChild(aux)
setTimeout(() => (this.$refs.copyResponse.innerHTML = this.copyButton), 1000)
},
downloadResponse() {
const dataToWrite = JSON.stringify(this.response.body, null, 2)
const file = new Blob([dataToWrite], { type: this.responseType })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
a.download = `${this.url + this.path} [${this.method}] on ${Date()}`.replace(/\./g, "[dot]")
document.body.appendChild(a)
a.click()
this.$refs.downloadResponse.innerHTML = this.doneButton
this.$toast.success(this.$t("download_started"), {
icon: "done",
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
this.$refs.downloadResponse.innerHTML = this.downloadButton
}, 1000)
},
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) => {
@@ -2629,6 +2433,15 @@ export default {
},
clearContent(name, { target }) {
switch (name) {
case "bodyParams":
this.bodyParams = []
break
case "rawParams":
this.rawParams = "{}"
break
case "parameters":
this.params = []
break
case "auth":
this.auth = "None"
this.httpUser = ""
@@ -2638,12 +2451,6 @@ export default {
this.tokens = []
this.tokenReqs = []
break
case "headers":
this.headers = []
break
case "parameters":
this.params = []
break
case "access_token":
this.accessTokenName = ""
this.oidcDiscoveryUrl = ""
@@ -2652,28 +2459,29 @@ export default {
this.clientId = ""
this.scope = ""
break
case "headers":
this.headers = []
break
case "tests":
this.testReports = []
break
case "tokens":
this.tokens = []
break
case "tokenReqs":
this.tokenReqs = []
case "tests":
this.testReports = null
break
default:
this.method = "GET"
this.url = "https://httpbin.org"
this.path = "/get"
this.uri = this.url + this.path
this.label = ""
this.bodyParams = []
this.rawParams = "{}"
this.files = []
this.params = []
this.auth = "None"
this.httpUser = ""
this.httpPassword = ""
this.bearerToken = ""
this.headers = []
this.params = []
this.bodyParams = []
this.rawParams = ""
this.showTokenRequest = false
this.tokens = []
this.tokenReqs = []
@@ -2683,7 +2491,8 @@ export default {
this.accessTokenUrl = ""
this.clientId = ""
this.scope = ""
this.files = []
this.headers = []
this.testReports = []
}
target.innerHTML = this.doneButton
this.$toast.info(this.$t("cleared"), {

View File

@@ -14,21 +14,21 @@ describe("Authentication", () => {
})
})
it("Enable username and password in URL with toggler", () => {
cy.visit("/", { retryOnStatusCodeFailure: true })
.get("#auth")
.select("Basic Auth")
.get('input[name="http_basic_user"]', { timeout: 500 })
.type("foo")
.get('input[name="http_basic_passwd"]', { timeout: 500 })
.type("bar")
.url()
.should("not.contain", "foo")
.should("not.contain", "bar")
.get(".toggle")
.click()
.url()
.should("contain", "foo")
.should("contain", "bar")
})
// it("Enable username and password in URL with toggler", () => {
// cy.visit("/", { retryOnStatusCodeFailure: true })
// .get("#auth")
// .select("Basic Auth")
// .get('input[name="http_basic_user"]', { timeout: 500 })
// .type("foo")
// .get('input[name="http_basic_passwd"]', { timeout: 500 })
// .type("bar")
// .url()
// .should("not.contain", "foo")
// .should("not.contain", "bar")
// .get(".toggle")
// .click()
// .url()
// .should("contain", "foo")
// .should("contain", "bar")
// })
})

View File

@@ -11,15 +11,15 @@
// })
// })
describe("Proxy enabled - external request", () => {
it("Enable proxy and make a GET request to Postwoman API", () => {
cy.enableProxy("/?url=https://postwoman.io&path=/.netlify/functions/api")
.get("#send")
.click()
.get("#response-details-wrapper", { timeout: 24000 })
.should("be.visible")
.should(($wrapper) => {
expect($wrapper).to.contain("Hello World")
})
})
})
// describe("Proxy enabled - external request", () => {
// it("Enable proxy and make a GET request to Postwoman API", () => {
// cy.enableProxy("/?url=https://postwoman.io&path=/.netlify/functions/api")
// .get("#send")
// .click()
// .get("#response-details-wrapper", { timeout: 24000 })
// .should("be.visible")
// .should(($wrapper) => {
// expect($wrapper).to.contain("Hello World")
// })
// })
// })