Revamp of the Settings State System along with TypeScript support (#1560)
* Add vue-rx, rxjs and lodash as dependencies * Added vue-rx plugin integration to nuxt config * Initial settings store implementation * Add babel plugin for private class properties to for Jest * Add DispatchingStore test spec * Initial settings code * Reactive Streams for fb current user and id token * Fix typo * Migrate index and graphql pages to the new store * Migrate network strategy to the new store * Fixed Section.vue errors * Fix getSettingSubject issue * Migrate fb settings reference in components to the new state system * Add typings for lodash as dev dependency * Load setting * Load initial sync setting values * Update proxy url * Add typescript support * Rewrite Settings store to TypeScript * Port Settings page to TypeScript as reference * Move all store migrations to a separate file * Delete test file for fb.js * Add ts-jest as dev dependency * Remove firebase-mock as dependency * Remove FRAME_COLORS_ENABLED settings value
This commit is contained in:
@@ -5,6 +5,9 @@ function isBabelLoader(caller) {
|
||||
module.exports = function (api) {
|
||||
if (api.env("test") && !api.caller(isBabelLoader)) {
|
||||
return {
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
],
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
|
||||
@@ -34,14 +34,15 @@ fieldset {
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import Vue from "vue"
|
||||
|
||||
export default Vue.extend({
|
||||
computed: {
|
||||
sectionString() {
|
||||
sectionString(): string {
|
||||
return `${this.$route.path.replace(/\/+$/, "")}/${this.label}`
|
||||
},
|
||||
},
|
||||
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
@@ -58,9 +59,9 @@ export default {
|
||||
// Save collapsed section into the collapsedSections array
|
||||
this.$store.commit("setCollapsedSection", this.sectionString)
|
||||
},
|
||||
isCollapsed(label) {
|
||||
isCollapsed(_label: string) {
|
||||
return this.$store.state.theme.collapsedSections.includes(this.sectionString) || false
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -48,16 +49,19 @@ export default {
|
||||
name: undefined,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
addNewCollection() {
|
||||
if (!this.$data.name) {
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -130,16 +131,19 @@ export default {
|
||||
confirmRemove: false,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
toggleShowChildren() {
|
||||
this.showChildren = !this.showChildren
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -50,16 +51,19 @@ export default {
|
||||
name: undefined,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
saveCollection() {
|
||||
if (!this.$data.name) {
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -51,16 +52,19 @@ export default {
|
||||
name: undefined,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
editFolder() {
|
||||
this.$store.commit("postwoman/editFolder", {
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -55,16 +56,19 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
saveRequest() {
|
||||
const requestUpdated = {
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
name: "folder",
|
||||
@@ -129,16 +130,19 @@ export default {
|
||||
confirmRemove: false,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
toggleShowChildren() {
|
||||
this.showChildren = !this.showChildren
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -121,6 +122,9 @@ export default {
|
||||
showJsonCode: false,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections")
|
||||
},
|
||||
props: {
|
||||
show: Boolean,
|
||||
},
|
||||
@@ -264,14 +268,12 @@ export default {
|
||||
this.fileImported()
|
||||
},
|
||||
syncToFBCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
fileImported() {
|
||||
this.$toast.info(this.$t("file_imported"), {
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -84,16 +85,19 @@ export default {
|
||||
confirmRemove: false,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
selectRequest() {
|
||||
this.$store.commit("postwoman/selectRequest", { request: this.request })
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -88,6 +89,11 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"requestData.collectionIndex": function resetFolderAndRequestIndex() {
|
||||
// if user has chosen some folder, than selected other collection, which doesn't have any folders
|
||||
@@ -144,14 +150,12 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
saveRequestAs() {
|
||||
const userDidntSpecifyCollection = this.$data.requestData.collectionIndex === undefined
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -111,6 +112,11 @@ export default {
|
||||
filterText: "",
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
collections() {
|
||||
return fb.currentUser !== null
|
||||
@@ -242,14 +248,12 @@ export default {
|
||||
this.$data.editingRequestIndex = undefined
|
||||
},
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -48,13 +49,16 @@ export default {
|
||||
name: undefined,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments")
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncEnvironments() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[1]) {
|
||||
if (fb.currentSettings[1].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
|
||||
fb.writeEnvironments(JSON.parse(JSON.stringify(this.$store.state.postwoman.environments)))
|
||||
}
|
||||
}
|
||||
},
|
||||
addNewEnvironment() {
|
||||
if (!this.$data.name) {
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -115,6 +116,11 @@ export default {
|
||||
doneButton: '<i class="material-icons">done</i>',
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments")
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingEnvironment(update) {
|
||||
this.name =
|
||||
@@ -135,11 +141,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
syncEnvironments() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[1]) {
|
||||
if (fb.currentSettings[1].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
|
||||
fb.writeEnvironments(JSON.parse(JSON.stringify(this.$store.state.postwoman.environments)))
|
||||
}
|
||||
}
|
||||
},
|
||||
clearContent({ target }) {
|
||||
this.$store.commit("postwoman/removeVariables", [])
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -49,13 +50,16 @@ export default {
|
||||
confirmRemove: false,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments")
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
syncEnvironments() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[1]) {
|
||||
if (fb.currentSettings[1].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
|
||||
fb.writeEnvironments(JSON.parse(JSON.stringify(this.$store.state.postwoman.environments)))
|
||||
}
|
||||
}
|
||||
},
|
||||
removeEnvironment() {
|
||||
this.$store.commit("postwoman/removeEnvironment", this.environmentIndex)
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -121,6 +122,11 @@ export default {
|
||||
showJsonCode: false,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments")
|
||||
}
|
||||
},
|
||||
props: {
|
||||
show: Boolean,
|
||||
},
|
||||
@@ -256,11 +262,9 @@ export default {
|
||||
this.fileImported()
|
||||
},
|
||||
syncToFBEnvironments() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[1]) {
|
||||
if (fb.currentSettings[1].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
|
||||
fb.writeEnvironments(JSON.parse(JSON.stringify(this.$store.state.postwoman.environments)))
|
||||
}
|
||||
}
|
||||
},
|
||||
fileImported() {
|
||||
this.$toast.info(this.$t("file_imported"), {
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -82,6 +83,11 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
environments() {
|
||||
return fb.currentUser !== null
|
||||
@@ -153,11 +159,9 @@ export default {
|
||||
this.$data.editingEnvironmentIndex = undefined
|
||||
},
|
||||
syncEnvironments() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[1]) {
|
||||
if (fb.currentSettings[1].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
|
||||
fb.writeEnvironments(JSON.parse(JSON.stringify(this.$store.state.postwoman.environments)))
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
83
helpers/__tests__/network-ExtDisabled.spec.js
Normal file
83
helpers/__tests__/network-ExtDisabled.spec.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { cancelRunningRequest, sendNetworkRequest } from "../network"
|
||||
|
||||
import AxiosStrategy, { cancelRunningAxiosRequest } from "../strategies/AxiosStrategy"
|
||||
import ExtensionStrategy, {
|
||||
cancelRunningExtensionRequest,
|
||||
hasExtensionInstalled,
|
||||
} from "../strategies/ExtensionStrategy"
|
||||
|
||||
jest.mock("../strategies/AxiosStrategy", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => Promise.resolve()),
|
||||
cancelRunningAxiosRequest: jest.fn(() => Promise.resolve()),
|
||||
}))
|
||||
|
||||
jest.mock("../strategies/ExtensionStrategy", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => Promise.resolve()),
|
||||
cancelRunningExtensionRequest: jest.fn(() => Promise.resolve()),
|
||||
hasExtensionInstalled: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock("~/newstore/settings", () => {
|
||||
return {
|
||||
settingsStore: {
|
||||
value: {
|
||||
EXTENSIONS_ENABLED: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
global.$nuxt = {
|
||||
$loading: {
|
||||
finish: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks() // Reset the call count for the mock functions
|
||||
})
|
||||
|
||||
describe("cancelRunningRequest", () => {
|
||||
test("cancels only axios request if extension not allowed in settings and extension is installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
cancelRunningRequest()
|
||||
|
||||
expect(cancelRunningExtensionRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningAxiosRequest).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("cancels only axios request if extension is not allowed and not installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
cancelRunningRequest()
|
||||
|
||||
expect(cancelRunningExtensionRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningAxiosRequest).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("sendNetworkRequest", () => {
|
||||
|
||||
test("runs only axios request if extension not allowed in settings and extension is installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
await sendNetworkRequest({})
|
||||
|
||||
expect(ExtensionStrategy).not.toHaveBeenCalled()
|
||||
expect(AxiosStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("runs only axios request if extension is not allowed and not installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
await sendNetworkRequest({})
|
||||
|
||||
expect(ExtensionStrategy).not.toHaveBeenCalled()
|
||||
expect(AxiosStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
82
helpers/__tests__/network-ExtEnabled.spec.js
Normal file
82
helpers/__tests__/network-ExtEnabled.spec.js
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
import { cancelRunningRequest, sendNetworkRequest } from "../network"
|
||||
|
||||
import AxiosStrategy, { cancelRunningAxiosRequest } from "../strategies/AxiosStrategy"
|
||||
import ExtensionStrategy, {
|
||||
cancelRunningExtensionRequest,
|
||||
hasExtensionInstalled,
|
||||
} from "../strategies/ExtensionStrategy"
|
||||
|
||||
jest.mock("../strategies/AxiosStrategy", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => Promise.resolve()),
|
||||
cancelRunningAxiosRequest: jest.fn(() => Promise.resolve()),
|
||||
}))
|
||||
|
||||
jest.mock("../strategies/ExtensionStrategy", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => Promise.resolve()),
|
||||
cancelRunningExtensionRequest: jest.fn(() => Promise.resolve()),
|
||||
hasExtensionInstalled: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock("~/newstore/settings", () => {
|
||||
return {
|
||||
settingsStore: {
|
||||
value: {
|
||||
EXTENSIONS_ENABLED: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
global.$nuxt = {
|
||||
$loading: {
|
||||
finish: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks() // Reset the call count for the mock functions
|
||||
})
|
||||
|
||||
describe("cancelRunningRequest", () => {
|
||||
test("cancels only extension request if extension allowed in settings and is installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
cancelRunningRequest()
|
||||
|
||||
expect(cancelRunningAxiosRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningExtensionRequest).toHaveBeenCalled()
|
||||
})
|
||||
test("cancels only axios request if extension is allowed but not installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
cancelRunningRequest()
|
||||
|
||||
expect(cancelRunningExtensionRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningAxiosRequest).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("sendNetworkRequest", () => {
|
||||
test("runs only extension request if extension allowed in settings and is installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
await sendNetworkRequest({})
|
||||
|
||||
expect(AxiosStrategy).not.toHaveBeenCalled()
|
||||
expect(ExtensionStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("runs only axios request if extension is allowed but not installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
await sendNetworkRequest({})
|
||||
|
||||
expect(ExtensionStrategy).not.toHaveBeenCalled()
|
||||
expect(AxiosStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,176 +0,0 @@
|
||||
import { cancelRunningRequest, sendNetworkRequest } from "../network"
|
||||
|
||||
import AxiosStrategy, { cancelRunningAxiosRequest } from "../strategies/AxiosStrategy"
|
||||
import ExtensionStrategy, {
|
||||
cancelRunningExtensionRequest,
|
||||
hasExtensionInstalled,
|
||||
} from "../strategies/ExtensionStrategy"
|
||||
|
||||
jest.mock("../strategies/AxiosStrategy", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => Promise.resolve()),
|
||||
cancelRunningAxiosRequest: jest.fn(() => Promise.resolve()),
|
||||
}))
|
||||
|
||||
jest.mock("../strategies/ExtensionStrategy", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => Promise.resolve()),
|
||||
cancelRunningExtensionRequest: jest.fn(() => Promise.resolve()),
|
||||
hasExtensionInstalled: jest.fn(),
|
||||
}))
|
||||
|
||||
const extensionAllowedStore = {
|
||||
state: {
|
||||
postwoman: {
|
||||
settings: {
|
||||
EXTENSIONS_ENABLED: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const extensionNotAllowedStore = {
|
||||
state: {
|
||||
postwoman: {
|
||||
settings: {
|
||||
EXTENSIONS_ENABLED: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const extensionUndefinedStore = {
|
||||
state: {
|
||||
postwoman: {
|
||||
settings: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
global.$nuxt = {
|
||||
$loading: {
|
||||
finish: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks() // Reset the call count for the mock functions
|
||||
})
|
||||
|
||||
describe("cancelRunningRequest", () => {
|
||||
test("cancels only extension request if extension allowed in settings and is installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
cancelRunningRequest(extensionAllowedStore)
|
||||
|
||||
expect(cancelRunningAxiosRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningExtensionRequest).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("cancels only extension request if extension setting is undefined and extension is installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
cancelRunningRequest(extensionUndefinedStore)
|
||||
|
||||
expect(cancelRunningAxiosRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningExtensionRequest).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("cancels only axios request if extension not allowed in settings and extension is installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
cancelRunningRequest(extensionNotAllowedStore)
|
||||
|
||||
expect(cancelRunningExtensionRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningAxiosRequest).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("cancels only axios request if extension is allowed but not installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
cancelRunningRequest(extensionAllowedStore)
|
||||
|
||||
expect(cancelRunningExtensionRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningAxiosRequest).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("cancels only axios request if extension is not allowed and not installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
cancelRunningRequest(extensionNotAllowedStore)
|
||||
|
||||
expect(cancelRunningExtensionRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningAxiosRequest).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("cancels only axios request if extension setting is undefined and not installed", () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
cancelRunningRequest(extensionUndefinedStore)
|
||||
|
||||
expect(cancelRunningExtensionRequest).not.toHaveBeenCalled()
|
||||
expect(cancelRunningAxiosRequest).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("sendNetworkRequest", () => {
|
||||
test("runs only extension request if extension allowed in settings and is installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
await sendNetworkRequest({}, extensionAllowedStore)
|
||||
|
||||
expect(AxiosStrategy).not.toHaveBeenCalled()
|
||||
expect(ExtensionStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("runs only extension request if extension setting is undefined and extension is installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
await sendNetworkRequest({}, extensionUndefinedStore)
|
||||
|
||||
expect(AxiosStrategy).not.toHaveBeenCalled()
|
||||
expect(ExtensionStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("runs only axios request if extension not allowed in settings and extension is installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(true)
|
||||
|
||||
await sendNetworkRequest({}, extensionNotAllowedStore)
|
||||
|
||||
expect(ExtensionStrategy).not.toHaveBeenCalled()
|
||||
expect(AxiosStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("runs only axios request if extension is allowed but not installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
await sendNetworkRequest({}, extensionAllowedStore)
|
||||
|
||||
expect(ExtensionStrategy).not.toHaveBeenCalled()
|
||||
expect(AxiosStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("runs only axios request if extension is not allowed and not installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
await sendNetworkRequest({}, extensionNotAllowedStore)
|
||||
|
||||
expect(ExtensionStrategy).not.toHaveBeenCalled()
|
||||
expect(AxiosStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("runs only axios request if extension setting is undefined and not installed and clears the progress bar", async () => {
|
||||
hasExtensionInstalled.mockReturnValue(false)
|
||||
|
||||
await sendNetworkRequest({}, extensionUndefinedStore)
|
||||
|
||||
expect(ExtensionStrategy).not.toHaveBeenCalled()
|
||||
expect(AxiosStrategy).toHaveBeenCalled()
|
||||
expect(global.$nuxt.$loading.finish).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,8 @@
|
||||
import firebase from "firebase/app"
|
||||
import "firebase/firestore"
|
||||
import "firebase/auth"
|
||||
import { ReplaySubject } from "rxjs"
|
||||
import { getSettingSubject, applySetting } from "~/newstore/settings"
|
||||
|
||||
// Initialize Firebase, copied from cloud console
|
||||
const firebaseConfig = {
|
||||
@@ -38,6 +40,41 @@ export class FirebaseInstance {
|
||||
this.currentGraphqlCollections = []
|
||||
this.currentEnvironments = []
|
||||
|
||||
this.currentUser$ = new ReplaySubject(1)
|
||||
this.idToken$ = new ReplaySubject(1)
|
||||
|
||||
let loadedSettings = false
|
||||
|
||||
getSettingSubject("syncCollections").subscribe((status) => {
|
||||
if (this.currentUser && loadedSettings) {
|
||||
this.writeSettings("syncCollections", status)
|
||||
}
|
||||
})
|
||||
|
||||
getSettingSubject("syncHistory").subscribe((status) => {
|
||||
if (this.currentUser && loadedSettings) {
|
||||
this.writeSettings("syncHistory", status)
|
||||
}
|
||||
})
|
||||
|
||||
getSettingSubject("syncEnvironments").subscribe((status) => {
|
||||
if (this.currentUser && loadedSettings) {
|
||||
this.writeSettings("syncEnvironments", status)
|
||||
}
|
||||
})
|
||||
|
||||
this.app.auth().onIdTokenChanged((user) => {
|
||||
if (user) {
|
||||
user.getIdToken().then((token) => {
|
||||
this.idToken = token
|
||||
this.idToken$.next(token)
|
||||
})
|
||||
} else {
|
||||
this.idToken = null
|
||||
this.idToken$.next(null)
|
||||
}
|
||||
})
|
||||
|
||||
this.app.auth().onAuthStateChanged((user) => {
|
||||
if (user) {
|
||||
this.currentUser = user
|
||||
@@ -87,6 +124,14 @@ export class FirebaseInstance {
|
||||
settings.push(setting)
|
||||
})
|
||||
this.currentSettings = settings
|
||||
|
||||
settings.forEach((e) => {
|
||||
if (e && e.name && e.value != null) {
|
||||
applySetting(e.name, e.value)
|
||||
}
|
||||
})
|
||||
|
||||
loadedSettings = true
|
||||
})
|
||||
|
||||
this.usersCollection
|
||||
|
||||
17
helpers/migrations.ts
Normal file
17
helpers/migrations.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { settingsStore, applySetting } from "~/newstore/settings"
|
||||
|
||||
/*
|
||||
* This file contains all the migrations we have to perform overtime in various (persisted)
|
||||
* state/store entries
|
||||
*/
|
||||
|
||||
export function performMigrations(): void {
|
||||
|
||||
// Migrate old default proxy URL to the new proxy URL (if not set / overridden)
|
||||
if (
|
||||
settingsStore.value.PROXY_URL === "https://hoppscotch.apollosoftware.xyz/"
|
||||
) {
|
||||
applySetting("PROXY_URL", "https://proxy.hoppscotch.io/")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,26 +3,25 @@ import ExtensionStrategy, {
|
||||
cancelRunningExtensionRequest,
|
||||
hasExtensionInstalled,
|
||||
} from "./strategies/ExtensionStrategy"
|
||||
import { settingsStore } from "~/newstore/settings"
|
||||
|
||||
export const cancelRunningRequest = (store) => {
|
||||
if (isExtensionsAllowed(store) && hasExtensionInstalled()) {
|
||||
export const cancelRunningRequest = () => {
|
||||
if (isExtensionsAllowed() && hasExtensionInstalled()) {
|
||||
cancelRunningExtensionRequest()
|
||||
} else {
|
||||
cancelRunningAxiosRequest()
|
||||
}
|
||||
}
|
||||
|
||||
const isExtensionsAllowed = ({ state }) =>
|
||||
typeof state.postwoman.settings.EXTENSIONS_ENABLED === "undefined" ||
|
||||
state.postwoman.settings.EXTENSIONS_ENABLED
|
||||
const isExtensionsAllowed = () => settingsStore.value.EXTENSIONS_ENABLED
|
||||
|
||||
const runAppropriateStrategy = (req, store) => {
|
||||
if (isExtensionsAllowed(store) && hasExtensionInstalled()) {
|
||||
return ExtensionStrategy(req, store)
|
||||
const runAppropriateStrategy = (req) => {
|
||||
if (isExtensionsAllowed() && hasExtensionInstalled()) {
|
||||
return ExtensionStrategy(req)
|
||||
}
|
||||
|
||||
return AxiosStrategy(req, store)
|
||||
return AxiosStrategy(req)
|
||||
}
|
||||
|
||||
export const sendNetworkRequest = (req, store) =>
|
||||
runAppropriateStrategy(req, store).finally(() => window.$nuxt.$loading.finish())
|
||||
export const sendNetworkRequest = (req) =>
|
||||
runAppropriateStrategy(req).finally(() => window.$nuxt.$loading.finish())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from "axios"
|
||||
import { decodeB64StringToArrayBuffer } from "../utils/b64"
|
||||
import { settingsStore } from "~/newstore/settings"
|
||||
|
||||
let cancelSource = axios.CancelToken.source()
|
||||
|
||||
@@ -10,10 +11,10 @@ export const cancelRunningAxiosRequest = () => {
|
||||
cancelSource = axios.CancelToken.source()
|
||||
}
|
||||
|
||||
const axiosWithProxy = async (req, { state }) => {
|
||||
const axiosWithProxy = async (req) => {
|
||||
try {
|
||||
const { data } = await axios.post(
|
||||
state.postwoman.settings.PROXY_URL || "https://proxy.hoppscotch.io",
|
||||
settingsStore.value.PROXY_URL || "https://proxy.hoppscotch.io",
|
||||
{
|
||||
...req,
|
||||
wantsBinary: true,
|
||||
@@ -60,11 +61,11 @@ const axiosWithoutProxy = async (req, _store) => {
|
||||
}
|
||||
}
|
||||
|
||||
const axiosStrategy = (req, store) => {
|
||||
if (store.state.postwoman.settings.PROXY_ENABLED) {
|
||||
return axiosWithProxy(req, store)
|
||||
const axiosStrategy = (req) => {
|
||||
if (settingsStore.value.PROXY_ENABLED) {
|
||||
return axiosWithProxy(req)
|
||||
}
|
||||
return axiosWithoutProxy(req, store)
|
||||
return axiosWithoutProxy(req)
|
||||
}
|
||||
|
||||
export const testables = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { decodeB64StringToArrayBuffer } from "../utils/b64"
|
||||
import { settingsStore } from "~/newstore/settings"
|
||||
|
||||
export const hasExtensionInstalled = () =>
|
||||
typeof window.__POSTWOMAN_EXTENSION_HOOK__ !== "undefined"
|
||||
@@ -15,12 +16,12 @@ export const cancelRunningExtensionRequest = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const extensionWithProxy = async (req, { state }) => {
|
||||
const extensionWithProxy = async (req) => {
|
||||
const backupTimeDataStart = new Date().getTime()
|
||||
|
||||
const res = await window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest({
|
||||
method: "post",
|
||||
url: state.postwoman.settings.PROXY_URL || "https://proxy.hoppscotch.io",
|
||||
url: settingsStore.value.PROXY_URL || "https://proxy.hoppscotch.io/",
|
||||
data: {
|
||||
...req,
|
||||
wantsBinary: true,
|
||||
@@ -53,7 +54,7 @@ const extensionWithProxy = async (req, { state }) => {
|
||||
return parsedData
|
||||
}
|
||||
|
||||
const extensionWithoutProxy = async (req, _store) => {
|
||||
const extensionWithoutProxy = async (req) => {
|
||||
const backupTimeDataStart = new Date().getTime()
|
||||
|
||||
const res = await window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest({
|
||||
@@ -74,11 +75,11 @@ const extensionWithoutProxy = async (req, _store) => {
|
||||
return res
|
||||
}
|
||||
|
||||
const extensionStrategy = (req, store) => {
|
||||
if (store.state.postwoman.settings.PROXY_ENABLED) {
|
||||
return extensionWithProxy(req, store)
|
||||
const extensionStrategy = (req) => {
|
||||
if (settingsStore.value.PROXY_ENABLED) {
|
||||
return extensionWithProxy(req)
|
||||
}
|
||||
return extensionWithoutProxy(req, store)
|
||||
return extensionWithoutProxy(req)
|
||||
}
|
||||
|
||||
export default extensionStrategy
|
||||
|
||||
@@ -2,24 +2,25 @@ import axios from "axios"
|
||||
import axiosStrategy from "../AxiosStrategy"
|
||||
|
||||
jest.mock("axios")
|
||||
jest.mock("~/newstore/settings", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
settingsStore: {
|
||||
value: {
|
||||
PROXY_ENABLED: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
axios.CancelToken.source.mockReturnValue({ token: "test" })
|
||||
axios.mockResolvedValue({})
|
||||
|
||||
describe("axiosStrategy", () => {
|
||||
describe("No-Proxy Requests", () => {
|
||||
const store = {
|
||||
state: {
|
||||
postwoman: {
|
||||
settings: {
|
||||
PROXY_ENABLED: false,
|
||||
PROXY_URL: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
test("sends request to the actual sender if proxy disabled", async () => {
|
||||
await axiosStrategy({ url: "test" }, store)
|
||||
await axiosStrategy({ url: "test" })
|
||||
|
||||
expect(axios).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -29,7 +30,7 @@ describe("axiosStrategy", () => {
|
||||
})
|
||||
|
||||
test("asks axios to return data as arraybuffer", async () => {
|
||||
await axiosStrategy({ url: "test" }, store)
|
||||
await axiosStrategy({ url: "test" })
|
||||
|
||||
expect(axios).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -39,21 +40,21 @@ describe("axiosStrategy", () => {
|
||||
})
|
||||
|
||||
test("resolves successful requests", async () => {
|
||||
await expect(axiosStrategy({}, store)).resolves.toBeDefined()
|
||||
await expect(axiosStrategy({})).resolves.toBeDefined()
|
||||
})
|
||||
|
||||
test("rejects cancel errors with text 'cancellation'", async () => {
|
||||
axios.isCancel.mockReturnValueOnce(true)
|
||||
axios.mockRejectedValue("err")
|
||||
|
||||
expect(axiosStrategy({}, store)).rejects.toBe("cancellation")
|
||||
expect(axiosStrategy({})).rejects.toBe("cancellation")
|
||||
})
|
||||
|
||||
test("rejects non-cancellation errors as-is", async () => {
|
||||
axios.isCancel.mockReturnValueOnce(false)
|
||||
axios.mockRejectedValue("err")
|
||||
|
||||
expect(axiosStrategy({}, store)).rejects.toBe("err")
|
||||
expect(axiosStrategy({})).rejects.toBe("err")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,6 +5,17 @@ jest.mock("../../utils/b64", () => ({
|
||||
__esModule: true,
|
||||
decodeB64StringToArrayBuffer: jest.fn((data) => `${data}-converted`),
|
||||
}))
|
||||
jest.mock("~/newstore/settings", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
settingsStore: {
|
||||
value: {
|
||||
PROXY_ENABLED: true,
|
||||
PROXY_URL: "test"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe("cancelRunningAxiosRequest", () => {
|
||||
test("cancels axios request and does that only 1 time", () => {
|
||||
@@ -17,16 +28,6 @@ describe("cancelRunningAxiosRequest", () => {
|
||||
|
||||
describe("axiosStrategy", () => {
|
||||
describe("Proxy Requests", () => {
|
||||
const store = {
|
||||
state: {
|
||||
postwoman: {
|
||||
settings: {
|
||||
PROXY_ENABLED: true,
|
||||
PROXY_URL: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
test("sends POST request to proxy if proxy is enabled", async () => {
|
||||
let passedURL
|
||||
@@ -36,7 +37,7 @@ describe("axiosStrategy", () => {
|
||||
return Promise.resolve({ data: { success: true, isBinary: false } })
|
||||
})
|
||||
|
||||
await axiosStrategy({}, store)
|
||||
await axiosStrategy({})
|
||||
|
||||
expect(passedURL).toEqual("test")
|
||||
})
|
||||
@@ -55,7 +56,7 @@ describe("axiosStrategy", () => {
|
||||
return Promise.resolve({ data: { success: true, isBinary: false } })
|
||||
})
|
||||
|
||||
await axiosStrategy(reqFields, store)
|
||||
await axiosStrategy(reqFields)
|
||||
|
||||
expect(passedFields).toMatchObject(reqFields)
|
||||
})
|
||||
@@ -68,7 +69,7 @@ describe("axiosStrategy", () => {
|
||||
return Promise.resolve({ data: { success: true, isBinary: false } })
|
||||
})
|
||||
|
||||
await axiosStrategy({}, store)
|
||||
await axiosStrategy({})
|
||||
|
||||
expect(passedFields).toHaveProperty("wantsBinary")
|
||||
})
|
||||
@@ -83,7 +84,7 @@ describe("axiosStrategy", () => {
|
||||
},
|
||||
})
|
||||
|
||||
await expect(axiosStrategy({}, store)).rejects.toThrow("test message")
|
||||
await expect(axiosStrategy({})).rejects.toThrow("test message")
|
||||
})
|
||||
|
||||
test("checks for proxy response success field and throws error 'Proxy Error' for non-success", async () => {
|
||||
@@ -94,7 +95,7 @@ describe("axiosStrategy", () => {
|
||||
},
|
||||
})
|
||||
|
||||
await expect(axiosStrategy({}, store)).rejects.toThrow("Proxy Error")
|
||||
await expect(axiosStrategy({})).rejects.toThrow("Proxy Error")
|
||||
})
|
||||
|
||||
test("checks for proxy response success and doesn't throw for success", async () => {
|
||||
@@ -105,7 +106,7 @@ describe("axiosStrategy", () => {
|
||||
},
|
||||
})
|
||||
|
||||
await expect(axiosStrategy({}, store)).resolves.toBeDefined()
|
||||
await expect(axiosStrategy({})).resolves.toBeDefined()
|
||||
})
|
||||
|
||||
test("checks isBinary response field and resolve with the converted value if so", async () => {
|
||||
@@ -117,7 +118,7 @@ describe("axiosStrategy", () => {
|
||||
},
|
||||
})
|
||||
|
||||
await expect(axiosStrategy({}, store)).resolves.toMatchObject({
|
||||
await expect(axiosStrategy({})).resolves.toMatchObject({
|
||||
data: "testdata-converted",
|
||||
})
|
||||
})
|
||||
@@ -131,7 +132,7 @@ describe("axiosStrategy", () => {
|
||||
},
|
||||
})
|
||||
|
||||
await expect(axiosStrategy({}, store)).resolves.toMatchObject({
|
||||
await expect(axiosStrategy({})).resolves.toMatchObject({
|
||||
data: "testdata",
|
||||
})
|
||||
})
|
||||
@@ -140,14 +141,14 @@ describe("axiosStrategy", () => {
|
||||
jest.spyOn(axios, "post").mockRejectedValue("errr")
|
||||
jest.spyOn(axios, "isCancel").mockReturnValueOnce(true)
|
||||
|
||||
await expect(axiosStrategy({}, store)).rejects.toBe("cancellation")
|
||||
await expect(axiosStrategy({})).rejects.toBe("cancellation")
|
||||
})
|
||||
|
||||
test("non-cancellation errors are thrown", async () => {
|
||||
jest.spyOn(axios, "post").mockRejectedValue("errr")
|
||||
jest.spyOn(axios, "isCancel").mockReturnValueOnce(false)
|
||||
|
||||
await expect(axiosStrategy({}, store)).rejects.toBe("errr")
|
||||
await expect(axiosStrategy({})).rejects.toBe("errr")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
220
helpers/strategies/__tests__/ExtensionStrategy-NoProxy.spec.js
Normal file
220
helpers/strategies/__tests__/ExtensionStrategy-NoProxy.spec.js
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
import extensionStrategy, {
|
||||
hasExtensionInstalled,
|
||||
hasChromeExtensionInstalled,
|
||||
hasFirefoxExtensionInstalled,
|
||||
cancelRunningExtensionRequest,
|
||||
} from "../ExtensionStrategy"
|
||||
|
||||
jest.mock("../../utils/b64", () => ({
|
||||
__esModule: true,
|
||||
decodeB64StringToArrayBuffer: jest.fn((data) => `${data}-converted`),
|
||||
}))
|
||||
|
||||
jest.mock("~/newstore/settings", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
settingsStore: {
|
||||
value: {
|
||||
EXTENSIONS_ENABLED: true,
|
||||
PROXY_ENABLED: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe("hasExtensionInstalled", () => {
|
||||
test("returns true if extension is present and hooked", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
||||
|
||||
expect(hasExtensionInstalled()).toEqual(true)
|
||||
})
|
||||
|
||||
test("returns false if extension not present or not hooked", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
||||
|
||||
expect(hasExtensionInstalled()).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("hasChromeExtensionInstalled", () => {
|
||||
test("returns true if extension is hooked and browser is chrome", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
||||
jest.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
|
||||
jest.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
|
||||
|
||||
expect(hasChromeExtensionInstalled()).toEqual(true)
|
||||
})
|
||||
|
||||
test("returns false if extension is hooked and browser is not chrome", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
||||
jest.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
|
||||
jest.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
|
||||
|
||||
expect(hasChromeExtensionInstalled()).toEqual(false)
|
||||
})
|
||||
|
||||
test("returns false if extension not installed and browser is chrome", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
||||
jest.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
|
||||
jest.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
|
||||
|
||||
expect(hasChromeExtensionInstalled()).toEqual(false)
|
||||
})
|
||||
|
||||
test("returns false if extension not installed and browser is not chrome", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
||||
jest.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
|
||||
jest.spyOn(navigator, "vendor", "get").mockReturnValue("Google")
|
||||
|
||||
expect(hasChromeExtensionInstalled()).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("hasFirefoxExtensionInstalled", () => {
|
||||
test("returns true if extension is hooked and browser is firefox", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
||||
jest.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
|
||||
|
||||
expect(hasFirefoxExtensionInstalled()).toEqual(true)
|
||||
})
|
||||
|
||||
test("returns false if extension is hooked and browser is not firefox", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
||||
jest.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
|
||||
|
||||
expect(hasFirefoxExtensionInstalled()).toEqual(false)
|
||||
})
|
||||
|
||||
test("returns false if extension not installed and browser is firefox", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
||||
jest.spyOn(navigator, "userAgent", "get").mockReturnValue("Firefox")
|
||||
|
||||
expect(hasFirefoxExtensionInstalled()).toEqual(false)
|
||||
})
|
||||
|
||||
test("returns false if extension not installed and browser is not firefox", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
||||
jest.spyOn(navigator, "userAgent", "get").mockReturnValue("Chrome")
|
||||
|
||||
expect(hasFirefoxExtensionInstalled()).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("cancelRunningExtensionRequest", () => {
|
||||
const cancelFunc = jest.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
cancelFunc.mockClear()
|
||||
})
|
||||
|
||||
test("cancels request if extension installed and function present in hook", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
cancelRunningRequest: cancelFunc,
|
||||
}
|
||||
|
||||
cancelRunningExtensionRequest()
|
||||
expect(cancelFunc).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("does not cancel request if extension not installed", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = undefined
|
||||
|
||||
cancelRunningExtensionRequest()
|
||||
expect(cancelFunc).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("does not cancel request if extension installed but function not present", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
||||
|
||||
cancelRunningExtensionRequest()
|
||||
expect(cancelFunc).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("extensionStrategy", () => {
|
||||
const sendReqFunc = jest.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
sendReqFunc.mockClear()
|
||||
})
|
||||
|
||||
describe("Non-Proxy Requests", () => {
|
||||
|
||||
test("ask extension to send request", async () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockResolvedValue({
|
||||
data: '{"success":true,"data":""}',
|
||||
})
|
||||
|
||||
await extensionStrategy({})
|
||||
|
||||
expect(sendReqFunc).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("sends request to the actual sender if proxy disabled", async () => {
|
||||
let passedUrl
|
||||
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockImplementation(({ method, url }) => {
|
||||
passedUrl = url
|
||||
|
||||
return Promise.resolve({
|
||||
data: '{"success":true,"data":""}',
|
||||
})
|
||||
})
|
||||
|
||||
await extensionStrategy({ url: "test" })
|
||||
|
||||
expect(passedUrl).toEqual("test")
|
||||
})
|
||||
|
||||
test("asks extension to get binary data", async () => {
|
||||
let passedFields
|
||||
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockImplementation((fields) => {
|
||||
passedFields = fields
|
||||
|
||||
return Promise.resolve({
|
||||
data: '{"success":true,"data":""}',
|
||||
})
|
||||
})
|
||||
|
||||
await extensionStrategy({})
|
||||
|
||||
expect(passedFields).toHaveProperty("wantsBinary")
|
||||
})
|
||||
|
||||
test("resolves successful requests", async () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockResolvedValue({
|
||||
data: '{"success":true,"data":""}',
|
||||
})
|
||||
|
||||
await expect(extensionStrategy({})).resolves.toBeDefined()
|
||||
})
|
||||
|
||||
test("rejects errors as-is", async () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockRejectedValue("err")
|
||||
|
||||
await expect(extensionStrategy({})).rejects.toBe("err")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -10,6 +10,19 @@ jest.mock("../../utils/b64", () => ({
|
||||
decodeB64StringToArrayBuffer: jest.fn((data) => `${data}-converted`),
|
||||
}))
|
||||
|
||||
jest.mock("~/newstore/settings", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
settingsStore: {
|
||||
value: {
|
||||
EXTENSIONS_ENABLED: true,
|
||||
PROXY_ENABLED: true,
|
||||
PROXY_URL: "test"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe("hasExtensionInstalled", () => {
|
||||
test("returns true if extension is present and hooked", () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {}
|
||||
@@ -127,16 +140,6 @@ describe("extensionStrategy", () => {
|
||||
})
|
||||
|
||||
describe("Proxy Requests", () => {
|
||||
const store = {
|
||||
state: {
|
||||
postwoman: {
|
||||
settings: {
|
||||
PROXY_ENABLED: true,
|
||||
PROXY_URL: "testURL",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
test("asks extension to send request", async () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
@@ -147,7 +150,7 @@ describe("extensionStrategy", () => {
|
||||
data: '{"success":true,"data":""}',
|
||||
})
|
||||
|
||||
await extensionStrategy({}, store)
|
||||
await extensionStrategy({})
|
||||
|
||||
expect(sendReqFunc).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -169,9 +172,9 @@ describe("extensionStrategy", () => {
|
||||
})
|
||||
})
|
||||
|
||||
await extensionStrategy({}, store)
|
||||
await extensionStrategy({})
|
||||
|
||||
expect(passedUrl).toEqual(store.state.postwoman.settings.PROXY_URL)
|
||||
expect(passedUrl).toEqual("test")
|
||||
expect(passedMethod).toEqual("post")
|
||||
})
|
||||
|
||||
@@ -196,7 +199,7 @@ describe("extensionStrategy", () => {
|
||||
})
|
||||
})
|
||||
|
||||
await extensionStrategy(reqFields, store)
|
||||
await extensionStrategy(reqFields)
|
||||
|
||||
expect(passedFields).toMatchObject(reqFields)
|
||||
})
|
||||
@@ -216,7 +219,7 @@ describe("extensionStrategy", () => {
|
||||
})
|
||||
})
|
||||
|
||||
await extensionStrategy({}, store)
|
||||
await extensionStrategy({})
|
||||
|
||||
expect(passedFields).toHaveProperty("wantsBinary")
|
||||
})
|
||||
@@ -230,7 +233,7 @@ describe("extensionStrategy", () => {
|
||||
data: '{"success":false,"data": { "message": "testerr" } }',
|
||||
})
|
||||
|
||||
await expect(extensionStrategy({}, store)).rejects.toThrow("testerr")
|
||||
await expect(extensionStrategy({})).rejects.toThrow("testerr")
|
||||
})
|
||||
|
||||
test("checks for proxy response success field and throws error 'Proxy Error' for non-success", async () => {
|
||||
@@ -242,7 +245,7 @@ describe("extensionStrategy", () => {
|
||||
data: '{"success":false,"data": {} }',
|
||||
})
|
||||
|
||||
await expect(extensionStrategy({}, store)).rejects.toThrow("Proxy Error")
|
||||
await expect(extensionStrategy({})).rejects.toThrow("Proxy Error")
|
||||
})
|
||||
|
||||
test("checks for proxy response success and doesn't throw for success", async () => {
|
||||
@@ -254,7 +257,7 @@ describe("extensionStrategy", () => {
|
||||
data: '{"success":true,"data": {} }',
|
||||
})
|
||||
|
||||
await expect(extensionStrategy({}, store)).resolves.toBeDefined()
|
||||
await expect(extensionStrategy({})).resolves.toBeDefined()
|
||||
})
|
||||
|
||||
test("checks isBinary response field and resolve with the converted value if so", async () => {
|
||||
@@ -266,7 +269,7 @@ describe("extensionStrategy", () => {
|
||||
data: '{"success": true, "isBinary": true, "data": "testdata" }',
|
||||
})
|
||||
|
||||
await expect(extensionStrategy({}, store)).resolves.toMatchObject({
|
||||
await expect(extensionStrategy({})).resolves.toMatchObject({
|
||||
data: "testdata-converted",
|
||||
})
|
||||
})
|
||||
@@ -280,7 +283,7 @@ describe("extensionStrategy", () => {
|
||||
data: '{"success": true, "isBinary": false, "data": "testdata" }',
|
||||
})
|
||||
|
||||
await expect(extensionStrategy({}, store)).resolves.toMatchObject({
|
||||
await expect(extensionStrategy({})).resolves.toMatchObject({
|
||||
data: "testdata",
|
||||
})
|
||||
})
|
||||
@@ -292,96 +295,7 @@ describe("extensionStrategy", () => {
|
||||
|
||||
sendReqFunc.mockRejectedValue("err")
|
||||
|
||||
await expect(extensionStrategy({}, store)).rejects.toBe("err")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Non-Proxy Requests", () => {
|
||||
const store = {
|
||||
state: {
|
||||
postwoman: {
|
||||
settings: {
|
||||
PROXY_ENABLED: false,
|
||||
PROXY_URL: "testURL",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
test("ask extension to send request", async () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockResolvedValue({
|
||||
data: '{"success":true,"data":""}',
|
||||
})
|
||||
|
||||
await extensionStrategy({}, store)
|
||||
|
||||
expect(sendReqFunc).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("sends request to the actual sender if proxy disabled", async () => {
|
||||
let passedUrl
|
||||
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockImplementation(({ method, url }) => {
|
||||
passedUrl = url
|
||||
|
||||
return Promise.resolve({
|
||||
data: '{"success":true,"data":""}',
|
||||
})
|
||||
})
|
||||
|
||||
await extensionStrategy({ url: "test" }, store)
|
||||
|
||||
expect(passedUrl).toEqual("test")
|
||||
})
|
||||
|
||||
test("asks extension to get binary data", async () => {
|
||||
let passedFields
|
||||
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockImplementation((fields) => {
|
||||
passedFields = fields
|
||||
|
||||
return Promise.resolve({
|
||||
data: '{"success":true,"data":""}',
|
||||
})
|
||||
})
|
||||
|
||||
await extensionStrategy({}, store)
|
||||
|
||||
expect(passedFields).toHaveProperty("wantsBinary")
|
||||
})
|
||||
|
||||
test("resolves successful requests", async () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockResolvedValue({
|
||||
data: '{"success":true,"data":""}',
|
||||
})
|
||||
|
||||
await expect(extensionStrategy({}, store)).resolves.toBeDefined()
|
||||
})
|
||||
|
||||
test("rejects errors as-is", async () => {
|
||||
global.__POSTWOMAN_EXTENSION_HOOK__ = {
|
||||
sendRequest: sendReqFunc,
|
||||
}
|
||||
|
||||
sendReqFunc.mockRejectedValue("err")
|
||||
|
||||
await expect(extensionStrategy({}, store)).rejects.toBe("err")
|
||||
await expect(extensionStrategy({})).rejects.toBe("err")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -14,6 +14,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { setupLocalPersistence } from "~/newstore/localpersistence"
|
||||
import { performMigrations } from "~/helpers/migrations"
|
||||
|
||||
export default {
|
||||
beforeMount() {
|
||||
let color = localStorage.getItem("THEME_COLOR") || "green"
|
||||
@@ -24,13 +27,7 @@ export default {
|
||||
document.body.classList.add("afterLoad")
|
||||
}
|
||||
|
||||
// Migrate old default proxy URL to the new proxy URL (if not set / overridden)
|
||||
if (
|
||||
this.$store.state.postwoman.settings.PROXY_URL &&
|
||||
this.$store.state.postwoman.settings.PROXY_URL === "https://hoppscotch.apollosoftware.xyz/"
|
||||
) {
|
||||
this.$store.state.postwoman.settings.PROXY_URL = "https://proxy.hoppscotch.io/"
|
||||
}
|
||||
performMigrations()
|
||||
|
||||
console.log(
|
||||
"%cWe ❤︎ open source!",
|
||||
@@ -61,6 +58,8 @@ export default {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setupLocalPersistence()
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("keydown", this._keyListener)
|
||||
|
||||
56
newstore/DispatchingStore.ts
Normal file
56
newstore/DispatchingStore.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Subject, BehaviorSubject } from "rxjs"
|
||||
import { map } from "rxjs/operators"
|
||||
import assign from "lodash/assign"
|
||||
import clone from "lodash/clone"
|
||||
|
||||
|
||||
type Dispatch<StoreType, DispatchersType extends Dispatchers<StoreType>, K extends keyof DispatchersType> = {
|
||||
dispatcher: K & string,
|
||||
payload: any
|
||||
}
|
||||
|
||||
export type Dispatchers<StoreType> = {
|
||||
[ key: string ]: (currentVal: StoreType, payload: any) => Partial<StoreType>
|
||||
}
|
||||
|
||||
export default class DispatchingStore<StoreType, DispatchersType extends Dispatchers<StoreType>> {
|
||||
|
||||
#state$: BehaviorSubject<StoreType>
|
||||
#dispatchers: Dispatchers<StoreType>
|
||||
#dispatches$: Subject<Dispatch<StoreType, DispatchersType, keyof DispatchersType>> = new Subject()
|
||||
|
||||
constructor(initialValue: StoreType, dispatchers: DispatchersType) {
|
||||
this.#state$ = new BehaviorSubject(initialValue)
|
||||
this.#dispatchers = dispatchers
|
||||
|
||||
this.#dispatches$
|
||||
.pipe(
|
||||
map(
|
||||
({ dispatcher, payload }) => this.#dispatchers[dispatcher](this.value, payload)
|
||||
)
|
||||
).subscribe(val => {
|
||||
const data = clone(this.value)
|
||||
assign(data, val)
|
||||
|
||||
this.#state$.next(data)
|
||||
})
|
||||
}
|
||||
|
||||
get subject$() {
|
||||
return this.#state$
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.subject$.value
|
||||
}
|
||||
|
||||
get dispatches$() {
|
||||
return this.#dispatches$
|
||||
}
|
||||
|
||||
dispatch({ dispatcher, payload }: Dispatch<StoreType, DispatchersType, keyof DispatchersType>) {
|
||||
if (!this.#dispatchers[dispatcher]) throw new Error(`Undefined dispatch type '${dispatcher}'`)
|
||||
|
||||
this.#dispatches$.next({ dispatcher, payload })
|
||||
}
|
||||
}
|
||||
185
newstore/__tests__/DispatchingStore.spec.js
Normal file
185
newstore/__tests__/DispatchingStore.spec.js
Normal file
@@ -0,0 +1,185 @@
|
||||
import { BehaviorSubject, Subject } from "rxjs"
|
||||
import isEqual from "lodash/isEqual"
|
||||
import DispatchingStore from "~/newstore/DispatchingStore"
|
||||
|
||||
describe("DispatchingStore", () => {
|
||||
|
||||
test("'subject$' property properly returns an BehaviorSubject", () => {
|
||||
const store = new DispatchingStore({}, {})
|
||||
|
||||
expect(store.subject$ instanceof BehaviorSubject).toEqual(true)
|
||||
})
|
||||
|
||||
test("'value' property properly returns the current state value", () => {
|
||||
const store = new DispatchingStore({}, {})
|
||||
|
||||
expect(store.value).toEqual({})
|
||||
})
|
||||
|
||||
test("'dispatches$' property properly returns a Subject", () => {
|
||||
const store = new DispatchingStore({}, {})
|
||||
|
||||
expect(store.dispatches$ instanceof Subject).toEqual(true)
|
||||
})
|
||||
|
||||
test("dispatch with invalid dispatcher are thrown", () => {
|
||||
const store = new DispatchingStore({}, {})
|
||||
|
||||
expect(() => {
|
||||
store.dispatch({
|
||||
dispatcher: "non-existent",
|
||||
payload: {}
|
||||
})
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
test("valid dispatcher calls run without throwing", () => {
|
||||
const store = new DispatchingStore({}, {
|
||||
testDispatcher(_currentValue, _payload) {
|
||||
// Nothing here
|
||||
}
|
||||
})
|
||||
|
||||
expect(() => {
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {}
|
||||
})
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
test("only correct dispatcher method is ran", () => {
|
||||
const dispatchFn = jest.fn().mockReturnValue({})
|
||||
const dontCallDispatchFn = jest.fn().mockReturnValue({})
|
||||
|
||||
const store = new DispatchingStore({}, {
|
||||
testDispatcher: dispatchFn,
|
||||
dontCallDispatcher: dontCallDispatchFn
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {}
|
||||
})
|
||||
|
||||
expect(dispatchFn).toHaveBeenCalledTimes(1)
|
||||
expect(dontCallDispatchFn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("passes current value and the payload to the dispatcher", () => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testPayload = { name: "alice" }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue({})
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: testPayload
|
||||
})
|
||||
|
||||
expect(testDispatchFn).toHaveBeenCalledWith(testInitValue, testPayload)
|
||||
})
|
||||
|
||||
test("dispatcher returns are used to update the store correctly", () => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testDispatchReturnVal = { name: "alice" }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue(testDispatchReturnVal)
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {} // Payload doesn't matter because the function is mocked
|
||||
})
|
||||
|
||||
expect(store.value).toEqual(testDispatchReturnVal)
|
||||
})
|
||||
|
||||
test("dispatching patches in new values if not existing on the store", () => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testDispatchReturnVal = { age: 25 }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue(testDispatchReturnVal)
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {}
|
||||
})
|
||||
|
||||
expect(store.value).toEqual({
|
||||
name: "bob",
|
||||
age: 25
|
||||
})
|
||||
})
|
||||
|
||||
test("emits the current store value to the new subscribers", done => {
|
||||
const testInitValue = { name: "bob" }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue({})
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn
|
||||
})
|
||||
|
||||
store.subject$.subscribe(value => {
|
||||
if (value === testInitValue) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test("emits the dispatched store value to the subscribers", done => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testDispatchReturnVal = { age: 25 }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue(testDispatchReturnVal)
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn
|
||||
})
|
||||
|
||||
store.subject$.subscribe(value => {
|
||||
if (isEqual(value, { name: "bob", age: 25 })) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {}
|
||||
})
|
||||
})
|
||||
|
||||
test("dispatching emits the new dispatch requests to the subscribers", () => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testPayload = { age: 25 }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue({})
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn
|
||||
})
|
||||
|
||||
store.dispatches$.subscribe(value => {
|
||||
if (isEqual(value, { dispatcher: "testDispatcher", payload: testPayload })) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {}
|
||||
})
|
||||
})
|
||||
})
|
||||
36
newstore/localpersistence.ts
Normal file
36
newstore/localpersistence.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { settingsStore, bulkApplySettings, defaultSettings } from "./settings"
|
||||
import clone from "lodash/clone"
|
||||
import assign from "lodash/assign"
|
||||
|
||||
function checkAndMigrateOldSettings() {
|
||||
// Don't do migration if the new settings object exists
|
||||
if (window.localStorage.getItem("settings")) return
|
||||
|
||||
const vuexData = JSON.parse(window.localStorage.getItem("vuex") || "{}")
|
||||
if (vuexData === {}) return
|
||||
|
||||
const settingsData = clone(defaultSettings)
|
||||
assign(settingsData, vuexData.postwoman.settings)
|
||||
|
||||
window.localStorage.setItem("settings", JSON.stringify(settingsData))
|
||||
}
|
||||
|
||||
function setupSettingsPersistence() {
|
||||
const settingsData = JSON.parse(window.localStorage.getItem("settings") || "{}")
|
||||
|
||||
if (settingsData) {
|
||||
bulkApplySettings(settingsData)
|
||||
}
|
||||
|
||||
|
||||
settingsStore.subject$
|
||||
.subscribe(settings => {
|
||||
window.localStorage.setItem("settings", JSON.stringify(settings))
|
||||
})
|
||||
}
|
||||
|
||||
export function setupLocalPersistence() {
|
||||
checkAndMigrateOldSettings()
|
||||
|
||||
setupSettingsPersistence()
|
||||
}
|
||||
91
newstore/settings.ts
Normal file
91
newstore/settings.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { pluck, distinctUntilChanged } from "rxjs/operators"
|
||||
import has from "lodash/has"
|
||||
import DispatchingStore from "./DispatchingStore"
|
||||
import type { Dispatchers } from "./DispatchingStore"
|
||||
import { Observable } from "rxjs"
|
||||
import type { KeysMatching } from "~/types/ts-utils"
|
||||
|
||||
|
||||
export const defaultSettings = {
|
||||
syncCollections: true,
|
||||
syncHistory: true,
|
||||
syncEnvironments: true,
|
||||
|
||||
SCROLL_INTO_ENABLED: true,
|
||||
PROXY_ENABLED: false,
|
||||
PROXY_URL: "https://proxy.hoppscotch.io/",
|
||||
PROXY_KEY: "",
|
||||
EXTENSIONS_ENABLED: true,
|
||||
EXPERIMENTAL_URL_BAR_ENABLED: false,
|
||||
URL_EXCLUDES: {
|
||||
auth: true,
|
||||
httpUser: true,
|
||||
httpPassword: true,
|
||||
bearerToken: true
|
||||
}
|
||||
}
|
||||
|
||||
export type SettingsType = typeof defaultSettings
|
||||
|
||||
const validKeys = Object.keys(defaultSettings)
|
||||
|
||||
const dispatchers: Dispatchers<SettingsType> = {
|
||||
bulkApplySettings(_currentState, payload: Partial<SettingsType>) {
|
||||
return payload
|
||||
},
|
||||
toggleSetting(currentState, { settingKey }: { settingKey: KeysMatching<SettingsType, boolean> }) {
|
||||
if (!has(currentState, settingKey)) {
|
||||
console.log(`Toggling of a non-existent setting key '${settingKey}' ignored.`)
|
||||
return {}
|
||||
}
|
||||
|
||||
const result: Partial<SettingsType> = {}
|
||||
result[settingKey] = !currentState[settingKey]
|
||||
|
||||
return result
|
||||
},
|
||||
applySetting<K extends keyof SettingsType>(_currentState: SettingsType, { settingKey, value }: { settingKey: K, value: SettingsType[K] }) {
|
||||
if (!validKeys.includes(settingKey)) {
|
||||
console.log(`Ignoring non-existent setting key '${settingKey}' assignment`)
|
||||
return {}
|
||||
}
|
||||
|
||||
const result: Partial<SettingsType> = {}
|
||||
result[settingKey] = value
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const settingsStore = new DispatchingStore(defaultSettings, dispatchers)
|
||||
|
||||
export function getSettingSubject<K extends keyof SettingsType>(settingKey: K): Observable<SettingsType[K]> {
|
||||
return settingsStore.subject$.pipe(pluck(settingKey), distinctUntilChanged())
|
||||
}
|
||||
|
||||
export function bulkApplySettings(settingsObj: Partial<SettingsType>) {
|
||||
settingsStore.dispatch({
|
||||
dispatcher: "bulkApplySettings",
|
||||
payload: settingsObj
|
||||
})
|
||||
}
|
||||
|
||||
export function toggleSetting(settingKey: KeysMatching<SettingsType, boolean>) {
|
||||
settingsStore.dispatch({
|
||||
dispatcher: "toggleSetting",
|
||||
payload: {
|
||||
settingKey
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function applySetting<K extends keyof SettingsType>(settingKey: K, value: SettingsType[K]) {
|
||||
settingsStore.dispatch({
|
||||
dispatcher: "applySetting",
|
||||
payload: {
|
||||
settingKey,
|
||||
value
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -92,6 +92,7 @@ export default {
|
||||
plugins: [
|
||||
"~/plugins/vuex-persist",
|
||||
"~/plugins/v-tooltip",
|
||||
"~/plugins/vue-rx",
|
||||
{ src: "~/plugins/web-worker", ssr: false },
|
||||
],
|
||||
|
||||
@@ -112,6 +113,8 @@ export default {
|
||||
"@nuxtjs/color-mode",
|
||||
// https: //github.com/nuxt-community/google-fonts-module
|
||||
"@nuxtjs/google-fonts",
|
||||
// https://github.com/nuxt/typescript
|
||||
"@nuxt/typescript-build",
|
||||
],
|
||||
|
||||
// Modules (https://go.nuxtjs.dev/config-modules)
|
||||
|
||||
38473
package-lock.json
generated
38473
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -32,33 +32,39 @@
|
||||
"firebase": "^8.3.1",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-language-service-interface": "^2.8.2",
|
||||
"lodash": "^4.17.20",
|
||||
"mustache": "^4.1.0",
|
||||
"nuxt": "^2.15.3",
|
||||
"nuxt-i18n": "^6.21.1",
|
||||
"paho-mqtt": "^1.1.0",
|
||||
"rxjs": "^6.6.3",
|
||||
"socket.io-client": "^4.0.0",
|
||||
"socketio-wildcard": "^2.0.0",
|
||||
"tern": "^0.24.3",
|
||||
"v-tooltip": "^2.1.3",
|
||||
"vue-rx": "^6.2.0",
|
||||
"vuejs-auto-complete": "^0.9.0",
|
||||
"vuex-persist": "^3.1.3",
|
||||
"yargs-parser": "^20.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.13.10",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.13",
|
||||
"@babel/preset-env": "^7.13.10",
|
||||
"@nuxt/types": "^2.15.3",
|
||||
"@nuxt/typescript-build": "^2.1.0",
|
||||
"@nuxtjs/color-mode": "^2.0.5",
|
||||
"@nuxtjs/google-analytics": "^2.4.0",
|
||||
"@nuxtjs/google-fonts": "^1.3.0",
|
||||
"@nuxtjs/pwa": "^3.3.5",
|
||||
"@nuxtjs/tailwindcss": "^4.0.1",
|
||||
"@testing-library/jest-dom": "^5.11.9",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@vue/test-utils": "^1.1.3",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"eslint": "^7.22.0",
|
||||
"eslint-plugin-vue": "^7.7.0",
|
||||
"firebase-mock": "^2.3.2",
|
||||
"husky": "^5.2.0",
|
||||
"jest": "^26.6.3",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
@@ -68,11 +74,13 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass": "^1.32.8",
|
||||
"sass-loader": "^10.1.1",
|
||||
"ts-jest": "^26.5.4",
|
||||
"vue-jest": "^3.0.7",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"js",
|
||||
"json",
|
||||
"vue"
|
||||
@@ -98,6 +106,7 @@
|
||||
"<rootDir>/components/**/*.vue",
|
||||
"<rootDir>/pages/*.vue"
|
||||
],
|
||||
"testURL": "http://localhost/"
|
||||
"testURL": "http://localhost/",
|
||||
"preset": "ts-jest/presets/js-with-babel"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +448,7 @@ import * as gql from "graphql"
|
||||
import { commonHeaders } from "~/helpers/headers"
|
||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
||||
import { sendNetworkRequest } from "~/helpers/network"
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { getSettingSubject } from "~/newstore/settings"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -469,13 +469,11 @@ export default {
|
||||
activeSidebar: true,
|
||||
editRequest: {},
|
||||
showSaveRequestModal: false,
|
||||
|
||||
settings: {
|
||||
SCROLL_INTO_ENABLED:
|
||||
typeof this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED !== "undefined"
|
||||
? this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED
|
||||
: true,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SCROLL_INTO_ENABLED: getSettingSubject("SCROLL_INTO_ENABLED"),
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -681,7 +679,7 @@ export default {
|
||||
const rootTypeName = this.resolveRootType(type).name
|
||||
|
||||
const target = document.getElementById(`type_${rootTypeName}`)
|
||||
if (target && this.settings.SCROLL_INTO_ENABLED) {
|
||||
if (target && this.SCROLL_INTO_ENABLED) {
|
||||
this.$refs.gqlTabs.$el
|
||||
.querySelector(".gqlTabs")
|
||||
.scrollTo({ top: target.offsetTop, behavior: "smooth" })
|
||||
@@ -739,7 +737,7 @@ export default {
|
||||
this.$nuxt.$loading.start()
|
||||
|
||||
this.response = this.$t("loading")
|
||||
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("response")
|
||||
if (this.SCROLL_INTO_ENABLED) this.scrollInto("response")
|
||||
|
||||
try {
|
||||
let headers = {}
|
||||
@@ -769,7 +767,7 @@ export default {
|
||||
star: false,
|
||||
headers: this.headers,
|
||||
}
|
||||
const res = await sendNetworkRequest(reqOptions, this.$store)
|
||||
const res = await sendNetworkRequest(reqOptions)
|
||||
|
||||
// HACK: Temporary trailing null character issue from the extension fix
|
||||
const responseText = new TextDecoder("utf-8").decode(res.data).replace(/\0+$/, "")
|
||||
@@ -946,7 +944,7 @@ export default {
|
||||
this.$nuxt.$loading.start()
|
||||
|
||||
this.schema = this.$t("loading")
|
||||
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("schema")
|
||||
if (this.SCROLL_INTO_ENABLED) this.scrollInto("schema")
|
||||
|
||||
try {
|
||||
const query = JSON.stringify({
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<li>
|
||||
<label for="url">{{ $t("url") }}</label>
|
||||
<input
|
||||
v-if="!this.$store.state.postwoman.settings.EXPERIMENTAL_URL_BAR_ENABLED"
|
||||
v-if="!EXPERIMENTAL_URL_BAR_ENABLED"
|
||||
:class="{ error: !isValidURL }"
|
||||
class="border-dashed md:border-l border-brdColor"
|
||||
@keyup.enter="isValidURL ? sendRequest() : null"
|
||||
@@ -280,7 +280,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
<div class="row-wrapper">
|
||||
<SmartToggle :on="!urlExcludes.auth" @change="setExclude('auth', !$event)">
|
||||
<SmartToggle :on="!URL_EXCLUDES.auth" @change="setExclude('auth', !$event)">
|
||||
{{ $t("include_in_url") }}
|
||||
</SmartToggle>
|
||||
</div>
|
||||
@@ -665,6 +665,8 @@ import { parseUrlAndPath } from "~/helpers/utils/uri"
|
||||
import { httpValid } from "~/helpers/utils/valid"
|
||||
import { knownContentTypes, isJSONContentType } from "~/helpers/utils/contenttypes"
|
||||
import { generateCodeWithGenerator } from "~/helpers/codegen/codegen"
|
||||
import { getSettingSubject, applySetting } from "~/newstore/settings"
|
||||
import clone from "lodash/clone"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -693,7 +695,6 @@ export default {
|
||||
showTokenRequestList: false,
|
||||
showSaveRequestModal: false,
|
||||
editRequest: {},
|
||||
urlExcludes: {},
|
||||
activeSidebar: true,
|
||||
fb,
|
||||
customMethod: false,
|
||||
@@ -701,12 +702,6 @@ export default {
|
||||
filenames: "",
|
||||
navigatorShare: navigator.share,
|
||||
runningRequest: false,
|
||||
settings: {
|
||||
SCROLL_INTO_ENABLED:
|
||||
typeof this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED !== "undefined"
|
||||
? this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED
|
||||
: true,
|
||||
},
|
||||
currentMethodIndex: 0,
|
||||
methodMenuItems: [
|
||||
"GET",
|
||||
@@ -722,16 +717,18 @@ export default {
|
||||
],
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SCROLL_INTO_ENABLED: getSettingSubject("SCROLL_INTO_ENABLED"),
|
||||
PROXY_ENABLED: getSettingSubject("PROXY_ENABLED"),
|
||||
URL_EXCLUDES: getSettingSubject("URL_EXCLUDES"),
|
||||
EXPERIMENTAL_URL_BAR_ENABLED: getSettingSubject("EXPERIMENTAL_URL_BAR_ENABLED"),
|
||||
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
SYNC_HISTORY: getSettingSubject("syncHistory"),
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
urlExcludes: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.$store.commit("postwoman/applySetting", [
|
||||
"URL_EXCLUDES",
|
||||
Object.assign({}, this.urlExcludes),
|
||||
])
|
||||
},
|
||||
},
|
||||
canListParameters: {
|
||||
immediate: true,
|
||||
handler(canListParameters) {
|
||||
@@ -1230,7 +1227,7 @@ export default {
|
||||
this.requestType = entry.requestType
|
||||
this.testScript = entry.testScript
|
||||
this.testsEnabled = entry.usesPostScripts
|
||||
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("request")
|
||||
if (this.SCROLL_INTO_ENABLED) this.scrollInto("request")
|
||||
},
|
||||
async makeRequest(auth, headers, requestBody, preRequestScript) {
|
||||
const requestOptions = {
|
||||
@@ -1260,14 +1257,14 @@ export default {
|
||||
if (typeof requestOptions.data === "string") {
|
||||
requestOptions.data = parseTemplateString(requestOptions.data)
|
||||
}
|
||||
return await sendNetworkRequest(requestOptions, this.$store)
|
||||
return await sendNetworkRequest(requestOptions)
|
||||
},
|
||||
cancelRequest() {
|
||||
cancelRunningRequest(this.$store)
|
||||
cancelRunningRequest()
|
||||
},
|
||||
async sendRequest() {
|
||||
this.$toast.clear()
|
||||
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("response")
|
||||
if (this.SCROLL_INTO_ENABLED) this.scrollInto("response")
|
||||
if (!this.isValidURL) {
|
||||
this.$toast.error(this.$t("url_invalid_format"), {
|
||||
icon: "error",
|
||||
@@ -1396,11 +1393,9 @@ export default {
|
||||
}
|
||||
|
||||
this.$refs.historyComponent.addEntry(entry)
|
||||
if (fb.currentUser !== null && fb.currentSettings[2]) {
|
||||
if (fb.currentSettings[2].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeHistory(entry)
|
||||
}
|
||||
}
|
||||
})()
|
||||
} catch (error) {
|
||||
this.runningRequest = false
|
||||
@@ -1456,11 +1451,9 @@ export default {
|
||||
}
|
||||
|
||||
this.$refs.historyComponent.addEntry(entry)
|
||||
if (fb.currentUser !== null && fb.currentSettings[2]) {
|
||||
if (fb.currentSettings[2].value) {
|
||||
if (fb.currentUser !== null && this.SYNC_HISTORY) {
|
||||
fb.writeHistory(entry)
|
||||
}
|
||||
}
|
||||
return
|
||||
} else {
|
||||
this.response.status = error.message
|
||||
@@ -1468,7 +1461,7 @@ export default {
|
||||
this.$toast.error(`${error} ${this.$t("f12_details")}`, {
|
||||
icon: "error",
|
||||
})
|
||||
if (!this.$store.state.postwoman.settings.PROXY_ENABLED) {
|
||||
if (!this.PROXY_ENABLED) {
|
||||
this.$toast.info(this.$t("enable_proxy"), {
|
||||
icon: "help",
|
||||
duration: 8000,
|
||||
@@ -1629,10 +1622,10 @@ export default {
|
||||
"method",
|
||||
"url",
|
||||
"path",
|
||||
!this.urlExcludes.auth ? "auth" : null,
|
||||
!this.urlExcludes.httpUser ? "httpUser" : null,
|
||||
!this.urlExcludes.httpPassword ? "httpPassword" : null,
|
||||
!this.urlExcludes.bearerToken ? "bearerToken" : null,
|
||||
!this.URL_EXCLUDES.auth ? "auth" : null,
|
||||
!this.URL_EXCLUDES.httpUser ? "httpUser" : null,
|
||||
!this.URL_EXCLUDES.httpPassword ? "httpPassword" : null,
|
||||
!this.URL_EXCLUDES.bearerToken ? "bearerToken" : null,
|
||||
"contentType",
|
||||
]
|
||||
.filter((item) => item !== null)
|
||||
@@ -1818,14 +1811,19 @@ export default {
|
||||
this.editRequest = {}
|
||||
},
|
||||
setExclude(excludedField, excluded) {
|
||||
const update = clone(this.URL_EXCLUDES)
|
||||
|
||||
if (excludedField === "auth") {
|
||||
this.urlExcludes.auth = excluded
|
||||
this.urlExcludes.httpUser = excluded
|
||||
this.urlExcludes.httpPassword = excluded
|
||||
this.urlExcludes.bearerToken = excluded
|
||||
update.auth = excluded
|
||||
update.httpUser = excluded
|
||||
update.httpPassword = excluded
|
||||
update.bearerToken = excluded
|
||||
} else {
|
||||
this.urlExcludes[excludedField] = excluded
|
||||
update[excludedField] = excluded
|
||||
}
|
||||
|
||||
applySetting("URL_EXCLUDES", update)
|
||||
|
||||
this.setRouteQueryState()
|
||||
},
|
||||
updateRawBody(rawParams) {
|
||||
@@ -1987,13 +1985,6 @@ export default {
|
||||
await this.oauthRedirectReq()
|
||||
},
|
||||
created() {
|
||||
this.urlExcludes = this.$store.state.postwoman.settings.URL_EXCLUDES || {
|
||||
// Exclude authentication by default for security reasons.
|
||||
auth: true,
|
||||
httpUser: true,
|
||||
httpPassword: true,
|
||||
bearerToken: true,
|
||||
}
|
||||
if (Object.keys(this.$route.query).length) this.setRouteQueries(this.$route.query)
|
||||
this.$watch(
|
||||
(vm) => [
|
||||
|
||||
@@ -24,16 +24,33 @@
|
||||
</button>
|
||||
<br />
|
||||
<FirebaseLogout />
|
||||
<p v-for="setting in fb.currentSettings" :key="setting.id">
|
||||
<p>
|
||||
<SmartToggle
|
||||
:key="setting.name"
|
||||
:on="setting.value"
|
||||
@change="toggleSettings(setting.name, setting.value)"
|
||||
:on="SYNC_COLLECTIONS"
|
||||
@change="toggleSettings('syncCollections', !SYNC_COLLECTIONS)"
|
||||
>
|
||||
{{ $t(setting.name) + " " + $t("sync") }}
|
||||
{{ setting.value ? $t("enabled") : $t("disabled") }}
|
||||
{{ $t("syncCollections") + " " + $t("sync") }}
|
||||
{{ SYNC_COLLECTIONS ? $t("enabled") : $t("disabled") }}
|
||||
</SmartToggle>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<SmartToggle
|
||||
:on="SYNC_ENVIRONMENTS"
|
||||
@change="toggleSettings('syncEnvironments', !SYNC_ENVIRONMENTS)"
|
||||
>
|
||||
{{ $t("syncEnvironments") + " " + $t("sync") }}
|
||||
{{ SYNC_ENVIRONMENTS ? $t("enabled") : $t("disabled") }}
|
||||
</SmartToggle>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<SmartToggle :on="SYNC_HISTORY" @change="toggleSettings('syncHistory', !SYNC_HISTORY)">
|
||||
{{ $t("syncHistory") + " " + $t("sync") }}
|
||||
{{ SYNC_HISTORY ? $t("enabled") : $t("disabled") }}
|
||||
</SmartToggle>
|
||||
</p>
|
||||
|
||||
<p v-if="fb.currentSettings.length !== 3">
|
||||
<button @click="initSettings">
|
||||
<i class="material-icons">sync</i>
|
||||
@@ -56,12 +73,9 @@
|
||||
<SmartColorModePicker />
|
||||
<SmartAccentModePicker />
|
||||
<span>
|
||||
<SmartToggle
|
||||
:on="settings.SCROLL_INTO_ENABLED"
|
||||
@change="toggleSetting('SCROLL_INTO_ENABLED')"
|
||||
>
|
||||
<SmartToggle :on="SCROLL_INTO_ENABLED" @change="toggleSetting('SCROLL_INTO_ENABLED')">
|
||||
{{ $t("scrollInto_use_toggle") }}
|
||||
{{ settings.SCROLL_INTO_ENABLED ? $t("enabled") : $t("disabled") }}
|
||||
{{ SCROLL_INTO_ENABLED ? $t("enabled") : $t("disabled") }}
|
||||
</SmartToggle>
|
||||
</span>
|
||||
</div>
|
||||
@@ -71,10 +85,7 @@
|
||||
<div class="flex flex-col">
|
||||
<label>{{ $t("extensions") }}</label>
|
||||
<div class="row-wrapper">
|
||||
<SmartToggle
|
||||
:on="settings.EXTENSIONS_ENABLED"
|
||||
@change="toggleSetting('EXTENSIONS_ENABLED')"
|
||||
>
|
||||
<SmartToggle :on="EXTENSIONS_ENABLED" @change="toggleSetting('EXTENSIONS_ENABLED')">
|
||||
{{ $t("extensions_use_toggle") }}
|
||||
</SmartToggle>
|
||||
</div>
|
||||
@@ -92,9 +103,9 @@
|
||||
<label>{{ $t("proxy") }}</label>
|
||||
<div class="row-wrapper">
|
||||
<span>
|
||||
<SmartToggle :on="settings.PROXY_ENABLED" @change="toggleSetting('PROXY_ENABLED')">
|
||||
<SmartToggle :on="PROXY_ENABLED" @change="toggleSetting('PROXY_ENABLED')">
|
||||
{{ $t("proxy") }}
|
||||
{{ settings.PROXY_ENABLED ? $t("enabled") : $t("disabled") }}
|
||||
{{ PROXY_ENABLED ? $t("enabled") : $t("disabled") }}
|
||||
</SmartToggle>
|
||||
</span>
|
||||
<a
|
||||
@@ -116,8 +127,8 @@
|
||||
<input
|
||||
id="url"
|
||||
type="url"
|
||||
v-model="settings.PROXY_URL"
|
||||
:disabled="!settings.PROXY_ENABLED"
|
||||
v-model="PROXY_URL"
|
||||
:disabled="!PROXY_ENABLED"
|
||||
:placeholder="$t('url')"
|
||||
/>
|
||||
<p class="info">
|
||||
@@ -166,7 +177,7 @@
|
||||
</p>
|
||||
<div class="row-wrapper">
|
||||
<SmartToggle
|
||||
:on="settings.EXPERIMENTAL_URL_BAR_ENABLED"
|
||||
:on="EXPERIMENTAL_URL_BAR_ENABLED"
|
||||
@change="toggleSetting('EXPERIMENTAL_URL_BAR_ENABLED')"
|
||||
>
|
||||
{{ $t("use_experimental_url_bar") }}
|
||||
@@ -177,40 +188,54 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { fb } from "~/helpers/fb"
|
||||
import { hasExtensionInstalled } from "../helpers/strategies/ExtensionStrategy"
|
||||
import {
|
||||
getSettingSubject,
|
||||
applySetting,
|
||||
toggleSetting,
|
||||
defaultSettings,
|
||||
} from "~/newstore/settings"
|
||||
import type { KeysMatching } from "~/types/ts-utils"
|
||||
|
||||
export default {
|
||||
import Vue from "vue"
|
||||
|
||||
type SettingsType = typeof defaultSettings
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
extensionVersion: hasExtensionInstalled()
|
||||
? window.__POSTWOMAN_EXTENSION_HOOK__.getVersion()
|
||||
: null,
|
||||
|
||||
settings: {
|
||||
SCROLL_INTO_ENABLED:
|
||||
typeof this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED !== "undefined"
|
||||
? this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED
|
||||
: true,
|
||||
|
||||
PROXY_ENABLED: this.$store.state.postwoman.settings.PROXY_ENABLED || false,
|
||||
PROXY_URL: this.$store.state.postwoman.settings.PROXY_URL || "https://proxy.hoppscotch.io",
|
||||
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || "",
|
||||
|
||||
EXTENSIONS_ENABLED:
|
||||
typeof this.$store.state.postwoman.settings.EXTENSIONS_ENABLED !== "undefined"
|
||||
? this.$store.state.postwoman.settings.EXTENSIONS_ENABLED
|
||||
: true,
|
||||
|
||||
EXPERIMENTAL_URL_BAR_ENABLED:
|
||||
typeof this.$store.state.postwoman.settings.EXPERIMENTAL_URL_BAR_ENABLED !== "undefined"
|
||||
? this.$store.state.postwoman.settings.EXPERIMENTAL_URL_BAR_ENABLED
|
||||
: false,
|
||||
},
|
||||
|
||||
doneButton: '<i class="material-icons">done</i>',
|
||||
fb,
|
||||
|
||||
SYNC_COLLECTIONS: true,
|
||||
SYNC_ENVIRONMENTS: true,
|
||||
SYNC_HISTORY: true,
|
||||
|
||||
PROXY_URL: "",
|
||||
PROXY_KEY: "",
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
return {
|
||||
SCROLL_INTO_ENABLED: getSettingSubject("SCROLL_INTO_ENABLED"),
|
||||
|
||||
PROXY_ENABLED: getSettingSubject("PROXY_ENABLED"),
|
||||
PROXY_URL: getSettingSubject("PROXY_URL"),
|
||||
PROXY_KEY: getSettingSubject("PROXY_KEY"),
|
||||
|
||||
EXTENSIONS_ENABLED: getSettingSubject("EXTENSIONS_ENABLED"),
|
||||
|
||||
EXPERIMENTAL_URL_BAR_ENABLED: getSettingSubject("EXPERIMENTAL_URL_BAR_ENABLED"),
|
||||
|
||||
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
|
||||
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
|
||||
SYNC_HISTORY: getSettingSubject("syncHistory"),
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -223,16 +248,15 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
applySetting(key, value) {
|
||||
this.settings[key] = value
|
||||
this.$store.commit("postwoman/applySetting", [key, value])
|
||||
applySetting<K extends keyof SettingsType>(key: K, value: SettingsType[K]) {
|
||||
applySetting(key, value)
|
||||
},
|
||||
toggleSetting(key) {
|
||||
this.settings[key] = !this.settings[key]
|
||||
this.$store.commit("postwoman/applySetting", [key, this.settings[key]])
|
||||
toggleSetting<K extends KeysMatching<SettingsType, boolean>>(key: K) {
|
||||
toggleSetting(key)
|
||||
},
|
||||
toggleSettings(name, value) {
|
||||
fb.writeSettings(name, !value)
|
||||
toggleSettings<K extends KeysMatching<SettingsType, boolean>>(name: K, value: SettingsType[K]) {
|
||||
this.applySetting(name, value)
|
||||
|
||||
if (name === "syncCollections" && value) {
|
||||
this.syncCollections()
|
||||
}
|
||||
@@ -241,21 +265,21 @@ export default {
|
||||
}
|
||||
},
|
||||
initSettings() {
|
||||
fb.writeSettings("syncHistory", true)
|
||||
fb.writeSettings("syncCollections", true)
|
||||
fb.writeSettings("syncEnvironments", true)
|
||||
applySetting("syncHistory", true)
|
||||
applySetting("syncCollections", true)
|
||||
applySetting("syncEnvironments", true)
|
||||
},
|
||||
resetProxy({ target }) {
|
||||
this.settings.PROXY_URL = `https://proxy.hoppscotch.io`
|
||||
resetProxy({ target }: { target: HTMLElement }) {
|
||||
applySetting("PROXY_URL", `https://proxy.hoppscotch.io/`)
|
||||
|
||||
target.innerHTML = this.doneButton
|
||||
this.$toast.info(this.$t("cleared"), {
|
||||
icon: "clear_all",
|
||||
})
|
||||
setTimeout(() => (target.innerHTML = '<i class="material-icons">clear_all</i>'), 1000)
|
||||
},
|
||||
syncCollections() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[0]) {
|
||||
if (fb.currentSettings[0].value) {
|
||||
syncCollections(): void {
|
||||
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
|
||||
fb.writeCollections(
|
||||
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
|
||||
"collections"
|
||||
@@ -265,21 +289,18 @@ export default {
|
||||
"collectionsGraphql"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
syncEnvironments() {
|
||||
if (fb.currentUser !== null && fb.currentSettings[1]) {
|
||||
if (fb.currentSettings[1].value) {
|
||||
syncEnvironments(): void {
|
||||
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
|
||||
fb.writeEnvironments(JSON.parse(JSON.stringify(this.$store.state.postwoman.environments)))
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
proxySettings() {
|
||||
proxySettings(): { url: string; key: string } {
|
||||
return {
|
||||
url: this.settings.PROXY_URL,
|
||||
key: this.settings.PROXY_KEY,
|
||||
url: this.PROXY_URL,
|
||||
key: this.PROXY_KEY,
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -288,5 +309,5 @@ export default {
|
||||
title: `Settings • Hoppscotch`,
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
4
plugins/vue-rx.js
Normal file
4
plugins/vue-rx.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import Vue from "vue"
|
||||
import VueRx from "vue-rx"
|
||||
|
||||
Vue.use(VueRx)
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./*"],
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"types": ["@types/node", "@nuxt/types", "nuxt-i18n", "@nuxtjs/toast", "vue-rx"]
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
21
types/pw-ext-hook.d.ts
vendored
Normal file
21
types/pw-ext-hook.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
interface PWExtensionRequestInfo {
|
||||
method: string
|
||||
url: string
|
||||
data: any & { wantsBinary: boolean }
|
||||
}
|
||||
|
||||
interface PWExtensionResponse {
|
||||
data: any
|
||||
config?: {
|
||||
timeData?: {
|
||||
startTime: number
|
||||
endTime: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface PWExtensionHook {
|
||||
getVersion: () => { major: number, minor: number }
|
||||
sendRequest: (req: PWExtensionRequestInfo) => Promise<PWExtensionResponse>
|
||||
cancelRunningRequest: () => void
|
||||
}
|
||||
1
types/ts-utils.d.ts
vendored
Normal file
1
types/ts-utils.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T];
|
||||
7
types/window.d.ts
vendored
Normal file
7
types/window.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export {}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__POSTWOMAN_EXTENSION_HOOK__: PWExtensionHook
|
||||
}
|
||||
}
|
||||
4
vue-shim.d.ts
vendored
Normal file
4
vue-shim.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module "*.vue" {
|
||||
import Vue from 'vue'
|
||||
export default Vue
|
||||
}
|
||||
Reference in New Issue
Block a user