diff --git a/components/app/Announcement.vue b/components/app/Announcement.vue index 74a6ccde4..b2e9cf97b 100644 --- a/components/app/Announcement.vue +++ b/components/app/Announcement.vue @@ -1,44 +1,59 @@ diff --git a/components/collections/ImportExport.vue b/components/collections/ImportExport.vue index ec7302a2b..d5aa665c7 100644 --- a/components/collections/ImportExport.vue +++ b/components/collections/ImportExport.vue @@ -418,45 +418,45 @@ export default { }) }, parsePostmanCollection({ info, name, item }) { - const postwomanCollection = { + const hoppscotchCollection = { name: "", folders: [], requests: [], } - postwomanCollection.name = info ? info.name : name + hoppscotchCollection.name = info ? info.name : name if (item && item.length > 0) { for (const collectionItem of item) { if (collectionItem.request) { if ( Object.prototype.hasOwnProperty.call( - postwomanCollection, + hoppscotchCollection, "folders" ) ) { - postwomanCollection.name = info ? info.name : name - postwomanCollection.requests.push( + hoppscotchCollection.name = info ? info.name : name + hoppscotchCollection.requests.push( this.parsePostmanRequest(collectionItem) ) } else { - postwomanCollection.name = name || "" - postwomanCollection.requests.push( + hoppscotchCollection.name = name || "" + hoppscotchCollection.requests.push( this.parsePostmanRequest(collectionItem) ) } } else if (this.hasFolder(collectionItem)) { - postwomanCollection.folders.push( + hoppscotchCollection.folders.push( this.parsePostmanCollection(collectionItem) ) } else { - postwomanCollection.requests.push( + hoppscotchCollection.requests.push( this.parsePostmanRequest(collectionItem) ) } } } - return postwomanCollection + return hoppscotchCollection }, parsePostmanRequest({ name, request }) { const pwRequest = { diff --git a/components/collections/graphql/ImportExport.vue b/components/collections/graphql/ImportExport.vue index deb94a50b..1f0457473 100644 --- a/components/collections/graphql/ImportExport.vue +++ b/components/collections/graphql/ImportExport.vue @@ -271,45 +271,45 @@ export default { }) }, parsePostmanCollection({ info, name, item }) { - const postwomanCollection = { + const hoppscotchCollection = { name: "", folders: [], requests: [], } - postwomanCollection.name = info ? info.name : name + hoppscotchCollection.name = info ? info.name : name if (item && item.length > 0) { for (const collectionItem of item) { if (collectionItem.request) { if ( Object.prototype.hasOwnProperty.call( - postwomanCollection, + hoppscotchCollection, "folders" ) ) { - postwomanCollection.name = info ? info.name : name - postwomanCollection.requests.push( + hoppscotchCollection.name = info ? info.name : name + hoppscotchCollection.requests.push( this.parsePostmanRequest(collectionItem) ) } else { - postwomanCollection.name = name || "" - postwomanCollection.requests.push( + hoppscotchCollection.name = name || "" + hoppscotchCollection.requests.push( this.parsePostmanRequest(collectionItem) ) } } else if (this.hasFolder(collectionItem)) { - postwomanCollection.folders.push( + hoppscotchCollection.folders.push( this.parsePostmanCollection(collectionItem) ) } else { - postwomanCollection.requests.push( + hoppscotchCollection.requests.push( this.parsePostmanRequest(collectionItem) ) } } } - return postwomanCollection + return hoppscotchCollection }, parsePostmanRequest({ name, request }) { const pwRequest = { diff --git a/components/environments/ImportExport.vue b/components/environments/ImportExport.vue index 032db0776..ead710cd8 100644 --- a/components/environments/ImportExport.vue +++ b/components/environments/ImportExport.vue @@ -204,13 +204,13 @@ export default { ) { this.importFromPostman(importFileObj) } else { - this.importFromPostwoman(importFileObj) + this.importFromHoppscotch(importFileObj) } } reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0]) this.$refs.inputChooseFileToImportFrom.value = "" }, - importFromPostwoman(environments) { + importFromHoppscotch(environments) { appendEnvironments(environments) this.fileImported() }, @@ -220,7 +220,7 @@ export default { environment.variables.push({ key, value }) ) const environments = [environment] - this.importFromPostwoman(environments) + this.importFromHoppscotch(environments) }, exportJSON() { let text = this.environmentJson diff --git a/components/history/index.vue b/components/history/index.vue index 5bc9b0e05..e916ecc7a 100644 --- a/components/history/index.vue +++ b/components/history/index.vue @@ -92,6 +92,7 @@ import { deleteGraphqlHistoryEntry, deleteRESTHistoryEntry, } from "~/newstore/history" +import { setRESTRequest } from "~/newstore/RESTSession" export default { props: { @@ -133,7 +134,7 @@ export default { }) }, useHistory(entry) { - this.$emit("useHistory", entry) + setRESTRequest(entry) }, deleteHistory(entry) { if (this.page === "rest") deleteRESTHistoryEntry(entry) diff --git a/components/history/rest/Card.vue b/components/history/rest/Card.vue index 785d5052e..d0553f80f 100644 --- a/components/history/rest/Card.vue +++ b/components/history/rest/Card.vue @@ -36,7 +36,7 @@ @click="$emit('use-entry')" > - {{ `${entry.url}${entry.path}` }} + {{ `${entry.endpoint}` }} 0 - ? `${this.$t("duration")}: ${duration}ms` - : this.$t("no_duration") + if (this.entry.meta.responseDuration) { + const responseDuration = this.entry.meta.responseDuration + return responseDuration > 0 + ? `${this.$t("duration")}: ${responseDuration}ms` + : this.$t("no_duration") + } else return this.$t("no_duration") }, entryStatus() { - const foundStatusGroup = findStatusGroup(this.entry.status) + const foundStatusGroup = findStatusGroup(this.entry.statusCode) return ( foundStatusGroup || { className: "", diff --git a/components/http/Headers.vue b/components/http/Headers.vue index c6f7c6370..2b1dbc8e2 100644 --- a/components/http/Headers.vue +++ b/components/http/Headers.vue @@ -49,9 +49,9 @@ :spellcheck="false" :value="header.key" autofocus - @change=" + @input=" updateHeader(index, { - key: $event.target.value, + key: $event, value: header.value, active: header.active, }) diff --git a/components/http/Response.vue b/components/http/Response.vue index 8b7ee49c2..40b09f3a2 100644 --- a/components/http/Response.vue +++ b/components/http/Response.vue @@ -1,6 +1,6 @@ diff --git a/components/http/ResponseMeta.vue b/components/http/ResponseMeta.vue index 64fb2638a..a260e4884 100644 --- a/components/http/ResponseMeta.vue +++ b/components/http/ResponseMeta.vue @@ -1,33 +1,39 @@ diff --git a/components/realtime/Mqtt.vue b/components/realtime/Mqtt.vue index afec01f2b..b6fce79c6 100644 --- a/components/realtime/Mqtt.vue +++ b/components/realtime/Mqtt.vue @@ -194,7 +194,7 @@ export default { this.client = new Paho.Client( parseUrl.hostname, parseUrl.port !== "" ? Number(parseUrl.port) : 8081, - "postwoman" + "hoppscotch" ) this.client.connect({ onSuccess: this.onConnectionSuccess, diff --git a/helpers/network.ts b/helpers/network.ts index 2a43d1c0e..7952e3ecf 100644 --- a/helpers/network.ts +++ b/helpers/network.ts @@ -51,7 +51,10 @@ export const sendNetworkRequest = (req: any) => export function createRESTNetworkRequestStream( req: EffectiveHoppRESTRequest ): Observable { - const response = new BehaviorSubject({ type: "loading" }) + const response = new BehaviorSubject({ + type: "loading", + req, + }) const headers = req.effectiveFinalHeaders.reduce((acc, { key, value }) => { return Object.assign(acc, { [key]: value }) @@ -60,32 +63,73 @@ export function createRESTNetworkRequestStream( const timeStart = Date.now() runAppropriateStrategy({ + method: req.method as any, url: req.effectiveFinalURL, headers, - }).then((res: any) => { - const timeEnd = Date.now() - - const contentLength = res.headers["content-length"] - ? parseInt(res.headers["content-length"]) - : (res.data as ArrayBuffer).byteLength - - const resObj: HoppRESTResponse = { - type: "success", - statusCode: res.status, - body: res.data, - headers: Object.keys(res.headers).map((x) => ({ - key: x, - value: res.headers[x], - })), - meta: { - responseSize: contentLength, - responseDuration: timeEnd - timeStart, - }, - } - response.next(resObj) - - response.complete() }) + .then((res: any) => { + const timeEnd = Date.now() + + const contentLength = res.headers["content-length"] + ? parseInt(res.headers["content-length"]) + : (res.data as ArrayBuffer).byteLength + + const resObj: HoppRESTResponse = { + type: "success", + statusCode: res.status, + body: res.data, + headers: Object.keys(res.headers).map((x) => ({ + key: x, + value: res.headers[x], + })), + meta: { + responseSize: contentLength, + responseDuration: timeEnd - timeStart, + }, + req, + } + response.next(resObj) + + response.complete() + }) + .catch((err) => { + if (err.response) { + const timeEnd = Date.now() + + const contentLength = err.response.headers["content-length"] + ? parseInt(err.response.headers["content-length"]) + : (err.response.data as ArrayBuffer).byteLength + + const resObj: HoppRESTResponse = { + type: "fail", + body: err.response.data, + headers: Object.keys(err.response.headers).map((x) => ({ + key: x, + value: err.response.headers[x], + })), + meta: { + responseDuration: timeEnd - timeStart, + responseSize: contentLength, + }, + req, + statusCode: err.response.status, + } + + response.next(resObj) + + response.complete() + } else { + const resObj: HoppRESTResponse = { + type: "network_fail", + error: err, + req, + } + + response.next(resObj) + + response.complete() + } + }) return response } diff --git a/helpers/types/HoppRESTRequest.ts b/helpers/types/HoppRESTRequest.ts index cc7148005..9555df4e4 100644 --- a/helpers/types/HoppRESTRequest.ts +++ b/helpers/types/HoppRESTRequest.ts @@ -1,3 +1,5 @@ +export const RESTReqSchemaVersion = "1" + export type HoppRESTParam = { key: string value: string @@ -11,8 +13,44 @@ export type HoppRESTHeader = { } export interface HoppRESTRequest { + v: string + method: string endpoint: string params: HoppRESTParam[] headers: HoppRESTHeader[] } + +export function isHoppRESTRequest(x: any): x is HoppRESTRequest { + return x && typeof x === "object" && "v" in x +} + +export function translateToNewRequest(x: any): HoppRESTRequest { + if (isHoppRESTRequest(x)) { + return x + } else { + // Old format + const endpoint: string = `${x.url}${x.path}` + + const headers: HoppRESTHeader[] = x.headers + + // Remove old keys from params + const params: HoppRESTParam[] = (x.params as any[]).map( + ({ key, value, active }) => ({ + key, + value, + active, + }) + ) + + const method = x.method + + return { + endpoint, + headers, + params, + method, + v: RESTReqSchemaVersion, + } + } +} diff --git a/helpers/types/HoppRESTResponse.ts b/helpers/types/HoppRESTResponse.ts index a6f8ec951..90a66a443 100644 --- a/helpers/types/HoppRESTResponse.ts +++ b/helpers/types/HoppRESTResponse.ts @@ -1,14 +1,25 @@ +import { HoppRESTRequest } from "./HoppRESTRequest" + export type HoppRESTResponse = - | { type: "loading" } + | { type: "loading"; req: HoppRESTRequest } | { type: "fail" headers: { key: string; value: string }[] body: ArrayBuffer statusCode: number + + meta: { + responseSize: number // in bytes + responseDuration: number // in millis + } + + req: HoppRESTRequest } | { type: "network_fail" error: Error + + req: HoppRESTRequest } | { type: "success" @@ -19,4 +30,6 @@ export type HoppRESTResponse = responseSize: number // in bytes responseDuration: number // in millis } + + req: HoppRESTRequest } diff --git a/newstore/RESTSession.ts b/newstore/RESTSession.ts index 34963c0bd..e9b4a46c8 100644 --- a/newstore/RESTSession.ts +++ b/newstore/RESTSession.ts @@ -1,9 +1,10 @@ -import { pluck, distinctUntilChanged, map } from "rxjs/operators" +import { pluck, distinctUntilChanged, map, filter } from "rxjs/operators" import DispatchingStore, { defineDispatchers } from "./DispatchingStore" import { HoppRESTHeader, HoppRESTParam, HoppRESTRequest, + RESTReqSchemaVersion, } from "~/helpers/types/HoppRESTRequest" import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse" @@ -115,6 +116,7 @@ type RESTSession = { const defaultRESTSession: RESTSession = { request: { + v: RESTReqSchemaVersion, endpoint: "https://httpbin.org/get", params: [], headers: [], @@ -124,6 +126,11 @@ const defaultRESTSession: RESTSession = { } const dispatchers = defineDispatchers({ + setRequest(_: RESTSession, { req }: { req: HoppRESTRequest }) { + return { + request: req, + } + }, setEndpoint(curr: RESTSession, { newEndpoint }: { newEndpoint: string }) { const paramsInNewURL = getParamsInURL(newEndpoint) const updatedParams = recalculateParams( @@ -297,6 +304,15 @@ const dispatchers = defineDispatchers({ const restSessionStore = new DispatchingStore(defaultRESTSession, dispatchers) +export function setRESTRequest(req: HoppRESTRequest) { + restSessionStore.dispatch({ + dispatcher: "setRequest", + payload: { + req, + }, + }) +} + export function setRESTEndpoint(newEndpoint: string) { restSessionStore.dispatch({ dispatcher: "setEndpoint", @@ -438,3 +454,10 @@ export const restResponse$ = restSessionStore.subject$.pipe( pluck("response"), distinctUntilChanged() ) + +export const completedRESTResponse$ = restResponse$.pipe( + filter( + (res) => + res !== null && res.type !== "loading" && res.type !== "network_fail" + ) +) diff --git a/newstore/history.ts b/newstore/history.ts index 3a9f5705a..29a560ef5 100644 --- a/newstore/history.ts +++ b/newstore/history.ts @@ -1,6 +1,7 @@ import eq from "lodash/eq" import { pluck } from "rxjs/operators" import DispatchingStore, { defineDispatchers } from "./DispatchingStore" +import { completedRESTResponse$ } from "./RESTSession" export const defaultRESTHistoryState = { state: [] as any[], @@ -136,3 +137,18 @@ export function toggleGraphqlHistoryEntryStar(entry: any) { payload: { entry }, }) } + +// Listen to completed responses to add to history +completedRESTResponse$.subscribe((res) => { + if (res !== null) { + if (res.type === "loading" || res.type === "network_fail") return + + addRESTHistoryEntry({ + ...res.req, + type: res.type, + meta: res.meta, + statusCode: res.statusCode, + star: false, + }) + } +}) diff --git a/pages/index.vue b/pages/index.vue index cc2ac74d3..d6cd2cf00 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -462,11 +462,7 @@