Merge pull request #915 from AndrewBastin/feat/cancel-button

Cancellable Requests
This commit is contained in:
Andrew Bastin
2020-05-30 22:09:59 -04:00
committed by GitHub
5 changed files with 159 additions and 81 deletions

View File

@@ -1,5 +1,16 @@
import AxiosStrategy from "./strategies/AxiosStrategy" import AxiosStrategy, { cancelRunningAxiosRequest } from "./strategies/AxiosStrategy"
import ExtensionStrategy, { hasExtensionInstalled } from "./strategies/ExtensionStrategy" import ExtensionStrategy, {
cancelRunningExtensionRequest,
hasExtensionInstalled,
} from "./strategies/ExtensionStrategy"
export const cancelRunningRequest = (store) => {
if (isExtensionsAllowed(store) && hasExtensionInstalled()) {
cancelRunningExtensionRequest()
} else {
cancelRunningAxiosRequest()
}
}
const isExtensionsAllowed = ({ state }) => const isExtensionsAllowed = ({ state }) =>
typeof state.postwoman.settings.EXTENSIONS_ENABLED === "undefined" || typeof state.postwoman.settings.EXTENSIONS_ENABLED === "undefined" ||
@@ -13,7 +24,5 @@ const runAppropriateStrategy = (req, store) => {
return AxiosStrategy(req, store) return AxiosStrategy(req, store)
} }
const sendNetworkRequest = (req, store) => export const sendNetworkRequest = (req, store) =>
runAppropriateStrategy(req, store).finally(() => window.$nuxt.$loading.finish()) runAppropriateStrategy(req, store).finally(() => window.$nuxt.$loading.finish())
export { sendNetworkRequest }

View File

@@ -1,39 +1,69 @@
import axios from "axios" import axios from "axios"
let cancelSource = axios.CancelToken.source()
export const cancelRunningAxiosRequest = () => {
cancelSource.cancel()
// Create a new cancel token
cancelSource = axios.CancelToken.source()
}
const axiosWithProxy = async (req, { state }) => { const axiosWithProxy = async (req, { state }) => {
const { data } = await axios.post( try {
state.postwoman.settings.PROXY_URL || "https://postwoman.apollosoftware.xyz/", const { data } = await axios.post(
req state.postwoman.settings.PROXY_URL || "https://postwoman.apollosoftware.xyz/",
) req,
return data {
cancelToken: cancelSource.token,
}
)
return data
} catch (e) {
// Check if the throw is due to a cancellation
if (axios.isCancel(e)) {
throw "cancellation"
} else {
throw e
}
}
} }
const axiosWithoutProxy = async (req, _store) => { const axiosWithoutProxy = async (req, _store) => {
const res = await axios({ try {
...req, const res = await axios({
transformResponse: [ ...req,
(data, headers) => { cancelToken: cancelSource.token,
// If the response has a JSON content type, try parsing it transformResponse: [
if ( (data, headers) => {
headers["content-type"] && // If the response has a JSON content type, try parsing it
(headers["content-type"].startsWith("application/json") || if (
headers["content-type"].startsWith("application/vnd.api+json") || headers["content-type"] &&
headers["content-type"].startsWith("application/hal+json")) (headers["content-type"].startsWith("application/json") ||
) { headers["content-type"].startsWith("application/vnd.api+json") ||
try { headers["content-type"].startsWith("application/hal+json"))
const jsonData = JSON.parse(data) ) {
return jsonData try {
} catch (e) { const jsonData = JSON.parse(data)
return data return jsonData
} catch (e) {
return data
}
} }
}
// Else return the string itself without any transformations // Else return the string itself without any transformations
return data return data
}, },
], ],
}) })
return res return res
} catch (e) {
if (axios.isCancel(e)) {
throw "cancellation"
} else {
throw e
}
}
} }
const axiosStrategy = (req, store) => { const axiosStrategy = (req, store) => {

View File

@@ -7,6 +7,12 @@ export const hasChromeExtensionInstalled = () =>
export const hasFirefoxExtensionInstalled = () => export const hasFirefoxExtensionInstalled = () =>
hasExtensionInstalled() && /Firefox/i.test(navigator.userAgent) hasExtensionInstalled() && /Firefox/i.test(navigator.userAgent)
export const cancelRunningExtensionRequest = () => {
if (hasExtensionInstalled() && window.__POSTWOMAN_EXTENSION_HOOK__.cancelRunningRequest) {
window.__POSTWOMAN_EXTENSION_HOOK__.cancelRunningRequest()
}
}
const extensionWithProxy = async (req, { state }) => { const extensionWithProxy = async (req, { state }) => {
const { data } = await window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest({ const { data } = await window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest({
method: "post", method: "post",

View File

@@ -187,6 +187,7 @@
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again", "json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
"prettify_body": "Prettify body", "prettify_body": "Prettify body",
"cancel": "Cancel", "cancel": "Cancel",
"cancelled": "Cancelled",
"save": "Save", "save": "Save",
"dismiss": "Dismiss", "dismiss": "Dismiss",
"are_you_sure": "Are you sure?", "are_you_sure": "Are you sure?",

View File

@@ -189,12 +189,25 @@
</li> </li>
<li class="shrink"> <li class="shrink">
<label class="hide-on-small-screen" for="send">&nbsp;</label> <label class="hide-on-small-screen" for="send">&nbsp;</label>
<button :disabled="!isValidURL" @click="sendRequest" id="send" ref="sendButton"> <button
v-if="!runningRequest"
:disabled="!isValidURL"
@click="sendRequest"
id="send"
ref="sendButton"
>
{{ $t("send") }} {{ $t("send") }}
<span> <span>
<i class="material-icons">send</i> <i class="material-icons">send</i>
</span> </span>
</button> </button>
<button v-else @click="cancelRequest" id="send" ref="sendButton">
{{ $t("cancel") }}
<span>
<i class="material-icons">clear</i>
</span>
</button>
</li> </li>
</ul> </ul>
<div class="blue"> <div class="blue">
@@ -1332,7 +1345,7 @@ import runTestScriptWithVariables from "../functions/postwomanTesting"
import parseTemplateString from "../functions/templating" import parseTemplateString from "../functions/templating"
import AceEditor from "../components/ui/ace-editor" import AceEditor from "../components/ui/ace-editor"
import { tokenRequest, oauthRedirect } from "../assets/js/oauth" import { tokenRequest, oauthRedirect } from "../assets/js/oauth"
import { sendNetworkRequest } from "../functions/network" import { cancelRunningRequest, sendNetworkRequest } from "../functions/network"
import { fb } from "../functions/fb" import { fb } from "../functions/fb"
import { getEditorLangForMimeType } from "~/functions/editorutils" import { getEditorLangForMimeType } from "~/functions/editorutils"
import { import {
@@ -1450,6 +1463,7 @@ export default {
files: [], files: [],
filenames: "", filenames: "",
navigatorShare: navigator.share, navigatorShare: navigator.share,
runningRequest: false,
settings: { settings: {
SCROLL_INTO_ENABLED: SCROLL_INTO_ENABLED:
@@ -2085,6 +2099,9 @@ export default {
} }
return await sendNetworkRequest(requestOptions, this.$store) return await sendNetworkRequest(requestOptions, this.$store)
}, },
cancelRequest() {
cancelRunningRequest(this.$store)
},
async sendRequest() { async sendRequest() {
this.$toast.clear() this.$toast.clear()
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("response") if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("response")
@@ -2154,12 +2171,16 @@ export default {
headers = headersObject headers = headersObject
try { try {
const startTime = Date.now() const startTime = Date.now()
this.runningRequest = true
const payload = await this.makeRequest( const payload = await this.makeRequest(
auth, auth,
headers, headers,
requestBody, requestBody,
this.showPreRequestScript && this.preRequestScript this.showPreRequestScript && this.preRequestScript
) )
this.runningRequest = false
const duration = Date.now() - startTime const duration = Date.now() - startTime
this.$toast.info(this.$t("finished_in", { duration }), { this.$toast.info(this.$t("finished_in", { duration }), {
icon: "done", icon: "done",
@@ -2202,55 +2223,66 @@ export default {
} }
})() })()
} catch (error) { } catch (error) {
console.log(error) this.runningRequest = false
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 = {
label: this.requestName,
status: this.response.status,
date: new Date().toLocaleDateString(),
time: new Date().toLocaleTimeString(),
method: this.method,
url: this.url,
path: this.path,
usesScripts: Boolean(this.preRequestScript),
preRequestScript: this.preRequestScript,
}
if ((this.preRequestScript && this.showPreRequestScript) || hasPathParams(this.params)) { // If the error is caused by cancellation, do nothing
let environmentVariables = getEnvironmentVariablesFromScript(this.preRequestScript) if (error === "cancellation") {
environmentVariables = addPathParamsToVariables(this.params, environmentVariables) this.response.status = this.$t("cancelled")
entry.path = parseTemplateString(entry.path, environmentVariables) this.response.body = this.$t("cancelled")
entry.url = parseTemplateString(entry.url, environmentVariables)
}
this.$refs.historyComponent.addEntry(entry)
if (fb.currentUser !== null) {
if (fb.currentSettings[2].value) {
fb.writeHistory(entry)
}
}
return
} else { } else {
this.response.status = error.message console.log(error)
this.response.body = `${error}. ${this.$t("check_console_details")}` if (error.response) {
this.$toast.error(`${error} ${this.$t("f12_details")}`, { this.response.headers = error.response.headers
icon: "error", this.response.status = error.response.status
}) this.response.body = error.response.data
if (!this.$store.state.postwoman.settings.PROXY_ENABLED) { // Addition of an entry to the history component.
this.$toast.info(this.$t("enable_proxy"), { const entry = {
icon: "help", label: this.requestName,
duration: 8000, status: this.response.status,
action: { date: new Date().toLocaleDateString(),
text: this.$t("yes"), time: new Date().toLocaleTimeString(),
onClick: (e, toastObject) => { method: this.method,
this.$router.push({ path: "/settings" }) url: this.url,
}, path: this.path,
}, usesScripts: Boolean(this.preRequestScript),
preRequestScript: this.preRequestScript,
}
if (
(this.preRequestScript && this.showPreRequestScript) ||
hasPathParams(this.params)
) {
let environmentVariables = getEnvironmentVariablesFromScript(this.preRequestScript)
environmentVariables = addPathParamsToVariables(this.params, environmentVariables)
entry.path = parseTemplateString(entry.path, environmentVariables)
entry.url = parseTemplateString(entry.url, environmentVariables)
}
this.$refs.historyComponent.addEntry(entry)
if (fb.currentUser !== null) {
if (fb.currentSettings[2].value) {
fb.writeHistory(entry)
}
}
return
} else {
this.response.status = error.message
this.response.body = `${error}. ${this.$t("check_console_details")}`
this.$toast.error(`${error} ${this.$t("f12_details")}`, {
icon: "error",
}) })
if (!this.$store.state.postwoman.settings.PROXY_ENABLED) {
this.$toast.info(this.$t("enable_proxy"), {
icon: "help",
duration: 8000,
action: {
text: this.$t("yes"),
onClick: (e, toastObject) => {
this.$router.push({ path: "/settings" })
},
},
})
}
} }
} }
} }