resolve merge conflicts

This commit is contained in:
Nicholas Palenchar
2020-01-24 09:18:11 -05:00
26 changed files with 3648 additions and 1731 deletions

View File

@@ -69,8 +69,8 @@ _Customized themes are synced with local session storage_
- Instant loading with [Service Workers](https://developers.google.com/web/fundamentals/primers/service-workers) - Instant loading with [Service Workers](https://developers.google.com/web/fundamentals/primers/service-workers)
- Offline support - Offline support
- Low RAM/memory and CPU usage - Low RAM/memory and CPU usage
- [Add to Home Screen](https://developers.google.com/web/fundamentals/app-install-banners) (button in footer) - Add to Home Screen
- [Desktop PWA](https://developers.google.com/web/progressive-web-apps/desktop) support (button in footer) - Desktop PWA
- ([full features](https://developers.google.com/web/progressive-web-apps)) - ([full features](https://developers.google.com/web/progressive-web-apps))
🚀 **Request**: Retrieve response from endpoint instantly. 🚀 **Request**: Retrieve response from endpoint instantly.
@@ -220,11 +220,13 @@ _**All `i18n` contributions are welcome to `i18n` [branch](https://github.com/li
- **[Proxy β](https://github.com/postwoman-io/postwoman-proxy)** - A simple proxy server created for Postwoman - **[Proxy β](https://github.com/postwoman-io/postwoman-proxy)** - A simple proxy server created for Postwoman
- **[CLI β](https://github.com/postwoman-io/postwoman-cli)** - A CLI solution for Postwoman - **[CLI β](https://github.com/postwoman-io/postwoman-cli)** - A CLI solution for Postwoman
- **[Browser Extensions](https://github.com/AndrewBastin/postwoman-firefox)** - Browser extensions that simplifies access to Postwoman - **Browser Extensions** - Browser extensions that simplifies access to Postwoman
> [![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_16x16.png) **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/postwoman)  |  ![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_16x16.png) **Chrome (coming soon)** [![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_16x16.png) **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/postwoman) ([GitHub](https://github.com/AndrewBastin/postwoman-firefox))  |  [![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_16x16.png) **Chrome**](https://chrome.google.com/webstore/detail/postwoman-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld) ([GitHub](https://github.com/AndrewBastin/postwoman-chrome))
_Add-ons are developed and maintained under **[Official Postwoman Organization](https://github.com/postwoman-io)**_ >**Extensions fixes `CORS` issues.**
_Add-ons are developed and maintained under **[Official Postwoman Organization](https://github.com/postwoman-io)**._
**To find out more, please check out [Postwoman Wiki](https://github.com/liyasthomas/postwoman/wiki).** **To find out more, please check out [Postwoman Wiki](https://github.com/liyasthomas/postwoman/wiki).**

View File

@@ -27,6 +27,7 @@
-webkit-font-feature-settings: "liga"; -webkit-font-feature-settings: "liga";
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
font-feature-settings: "liga"; font-feature-settings: "liga";
border-radius: 50%;
} }
/* poppins-500 - latin */ /* poppins-500 - latin */

View File

@@ -698,6 +698,7 @@ ol li {
.show-on-large-screen { .show-on-large-screen {
display: inline-flex; display: inline-flex;
flex: 1;
} }
.info-response { .info-response {
@@ -852,10 +853,7 @@ input[type="radio"]:checked + label + .tab {
} }
@media (max-width: $responsiveWidth) { @media (max-width: $responsiveWidth) {
.content { .content,
flex-flow: column;
}
.columns { .columns {
flex-flow: column; flex-flow: column;
} }

View File

@@ -56,6 +56,10 @@ export default {
}, },
methods: { methods: {
addNewCollection() { addNewCollection() {
if (!this.$data.name) {
this.$toast.info('Please provide a valid name for the collection')
return;
}
this.$store.commit("postwoman/addNewCollection", { this.$store.commit("postwoman/addNewCollection", {
name: this.$data.name name: this.$data.name
}); });

View File

@@ -59,6 +59,10 @@ export default {
}, },
methods: { methods: {
saveCollection() { saveCollection() {
if (!this.$data.name) {
this.$toast.info('Please provide a valid name for the collection');
return;
}
const collectionUpdated = { const collectionUpdated = {
...this.$props.editingCollection, ...this.$props.editingCollection,
name: this.$data.name name: this.$data.name

View File

@@ -0,0 +1,85 @@
<template>
<virtual-list
v-if="fb.currentFeeds.length !== 0"
class="virtual-list"
:class="{ filled: fb.currentFeeds.length }"
:size="56"
:remain="Math.min(8, fb.currentFeeds.length)"
>
<ul v-for="feed in fb.currentFeeds" :key="feed.id">
<div class="show-on-large-screen">
<li>
<input
:aria-label="$t('label')"
type="text"
readonly
:value="feed.label"
:placeholder="$t('no_label')"
class="bg-color"
/>
</li>
<button class="icon" @click="saveFeed(feed)">
<i class="material-icons">get_app</i>
</button>
<button class="icon" @click="deleteFeed(feed)">
<i class="material-icons">delete</i>
</button>
</div>
</ul>
</virtual-list>
<ul v-else>
<li>
<label class="info">{{ $t("empty") }}</label>
</li>
</ul>
</template>
<style scoped lang="scss">
.virtual-list {
max-height: calc(100vh - 288px);
}
.bg-color {
background-color: transparent;
}
</style>
<script>
import { fb } from "../../functions/fb";
export default {
components: {
VirtualList: () => import("vue-virtual-scroll-list")
},
data() {
return {
fb
};
},
methods: {
deleteFeed(feed) {
fb.deleteFeed(feed.id);
this.$toast.error(this.$t("deleted"), {
icon: "delete"
});
},
saveFeed(feed) {
const dataToWrite = JSON.stringify(feed.message, null, 2);
const file = new Blob([dataToWrite], { type: "application/json" });
const a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = (feed.label + " on " + Date()).replace(/\./g, "[dot]");
document.body.appendChild(a);
a.click();
this.$toast.success(this.$t("download_started"), {
icon: "done"
});
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 1000);
}
}
};
</script>

View File

@@ -0,0 +1,55 @@
<template>
<div>
<ul>
<li>
<input
:aria-label="$t('label')"
type="text"
autofocus
v-model="message"
:placeholder="$t('paste_a_collection')"
/>
</li>
</ul>
<ul>
<li>
<input
:aria-label="$t('label')"
type="text"
autofocus
v-model="label"
:placeholder="$t('label')"
/>
</li>
<button
class="icon"
:disabled="!(this.message && this.label)"
value="Save"
@click="formPost"
>
<i class="material-icons">add</i>
<span>Add</span>
</button>
</ul>
</div>
</template>
<script>
import { fb } from "../../functions/fb";
export default {
data() {
return {
message: null,
label: null
};
},
methods: {
formPost() {
fb.writeFeeds(this.message, this.label);
this.message = null;
this.label = null;
}
}
};
</script>

View File

@@ -0,0 +1,115 @@
<template>
<v-popover>
<button class="icon" v-tooltip="$t('login_with')">
<i class="material-icons">vpn_key</i>
</button>
<template slot="popover">
<div>
<button class="icon" @click="signInWithGoogle" v-close-popover>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="material-icons"
>
<path
d="M12.24 10.285V14.4h6.806c-.275 1.765-2.056 5.174-6.806 5.174-4.095 0-7.439-3.389-7.439-7.574s3.345-7.574 7.439-7.574c2.33 0 3.891.989 4.785 1.849l3.254-3.138C18.189 1.186 15.479 0 12.24 0c-6.635 0-12 5.365-12 12s5.365 12 12 12c6.926 0 11.52-4.869 11.52-11.726 0-.788-.085-1.39-.189-1.989H12.24z"
/>
</svg>
<span>Google</span>
</button>
</div>
<div>
<button class="icon" @click="signInWithGithub" v-close-popover>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="material-icons"
>
<path
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
/>
</svg>
<span>GitHub</span>
</button>
</div>
</template>
</v-popover>
</template>
<script>
import firebase from "firebase/app";
import { fb } from "../../functions/fb";
export default {
data() {
return {
fb
};
},
methods: {
signInWithGoogle() {
const provider = new firebase.auth.GoogleAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", false);
fb.writeSettings("syncCollections", true);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
}
});
}
})
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
},
signInWithGithub() {
const provider = new firebase.auth.GithubAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", false);
fb.writeSettings("syncCollections", true);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
}
});
}
})
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
}
}
};
</script>

View File

@@ -0,0 +1,136 @@
<template>
<pre ref="editor"></pre>
</template>
<script>
const DEFAULT_THEME = "twilight";
import ace from "ace-builds";
import * as gql from "graphql";
import "ace-builds/webpack-resolver";
import debounce from "../../functions/utils/debounce";
export default {
props: {
value: {
type: String,
default: ""
},
theme: {
type: String,
required: false
},
lang: {
type: String,
default: "json"
},
options: {
type: Object,
default: {}
}
},
data() {
return {
editor: null,
cacheValue: "",
validationSchema: null
};
},
watch: {
value(value) {
if (value !== this.cacheValue) {
this.editor.session.setValue(value, 1);
this.cacheValue = value;
}
},
theme() {
this.editor.setTheme("ace/theme/" + this.defineTheme());
},
lang(value) {
this.editor.getSession().setMode("ace/mode/" + value);
},
options(value) {
this.editor.setOptions(value);
}
},
mounted() {
const editor = ace.edit(this.$refs.editor, {
theme: "ace/theme/" + this.defineTheme(),
mode: "ace/mode/" + this.lang,
...this.options
});
if (this.value) editor.setValue(this.value, 1);
this.editor = editor;
this.cacheValue = this.value;
editor.on("change", () => {
const content = editor.getValue();
this.$emit("input", content);
this.parseContents(content);
this.cacheValue = content;
});
this.parseContents(this.value);
},
methods: {
defineTheme() {
if (this.theme) {
return this.theme;
} else {
return (
this.$store.state.postwoman.settings.THEME_ACE_EDITOR || DEFAULT_THEME
);
}
},
setValidationSchema(schema) {
this.validationSchema = schema;
this.parseContents(this.cacheValue);
},
parseContents: debounce(function(content) {
if (content !== "") {
try {
const doc = gql.parse(content);
if (this.validationSchema) {
this.editor.session.setAnnotations(
gql.validate(this.validationSchema, doc)
.map((err) => {
return {
row: err.locations[0].line - 1,
column: err.locations[0].column - 1,
text: err.message,
type: "error"
}
})
)
}
} catch (e) {
this.editor.session.setAnnotations([
{
row: e.locations[0].line - 1,
column: e.locations[0].column - 1,
text: e.message,
type: "error"
}
]);
}
} else {
this.editor.session.setAnnotations([]);
}
}, 2000)
},
beforeDestroy() {
this.editor.destroy();
this.editor.container.remove();
}
};
</script>

View File

@@ -21,7 +21,7 @@
<button <button
class="icon" class="icon"
:class="{ stared: entry.star }" :class="{ stared: entry.star }"
@click="toggleStar(index)" @click="toggleStar(entry)"
v-tooltip="{ v-tooltip="{
content: !entry.star ? $t('add_star') : $t('remove_star') content: !entry.star ? $t('add_star') : $t('remove_star')
}" }"
@@ -331,6 +331,7 @@ ol li {
<script> <script>
import { findStatusGroup } from "../pages/index"; import { findStatusGroup } from "../pages/index";
import { fb } from "../functions/fb";
const updateOnLocalStorage = (propertyName, property) => const updateOnLocalStorage = (propertyName, property) =>
window.localStorage.setItem(propertyName, JSON.stringify(property)); window.localStorage.setItem(propertyName, JSON.stringify(property));
@@ -341,11 +342,11 @@ export default {
VirtualList: () => import("vue-virtual-scroll-list") VirtualList: () => import("vue-virtual-scroll-list")
}, },
data() { data() {
const localStorageHistory = JSON.parse(
window.localStorage.getItem("history")
);
return { return {
history: localStorageHistory || [], history:
fb.currentUser !== null
? fb.currentHistory
: JSON.parse(window.localStorage.getItem("history")) || [],
filterText: "", filterText: "",
showFilter: false, showFilter: false,
isClearingHistory: false, isClearingHistory: false,
@@ -359,6 +360,10 @@ export default {
}, },
computed: { computed: {
filteredHistory() { filteredHistory() {
this.history =
fb.currentUser !== null
? fb.currentHistory
: JSON.parse(window.localStorage.getItem("history")) || [];
return this.history.filter(entry => { return this.history.filter(entry => {
const filterText = this.filterText.toLowerCase(); const filterText = this.filterText.toLowerCase();
return Object.keys(entry).some(key => { return Object.keys(entry).some(key => {
@@ -371,6 +376,9 @@ export default {
}, },
methods: { methods: {
clearHistory() { clearHistory() {
if (fb.currentUser !== null) {
fb.clearHistory();
}
this.history = []; this.history = [];
this.filterText = ""; this.filterText = "";
this.disableHistoryClearing(); this.disableHistoryClearing();
@@ -391,6 +399,9 @@ export default {
); );
}, },
deleteHistory(entry) { deleteHistory(entry) {
if (fb.currentUser !== null) {
fb.deleteHistory(entry);
}
this.history.splice(this.history.indexOf(entry), 1); this.history.splice(this.history.indexOf(entry), 1);
if (this.history.length === 0) { if (this.history.length === 0) {
this.filterText = ""; this.filterText = "";
@@ -497,8 +508,11 @@ export default {
toggleCollapse() { toggleCollapse() {
this.showMore = !this.showMore; this.showMore = !this.showMore;
}, },
toggleStar(index) { toggleStar(entry) {
this.history[index]["star"] = !this.history[index]["star"]; if (fb.currentUser !== null) {
fb.toggleStar(entry, !entry.star);
}
entry.star = !entry.star;
updateOnLocalStorage("history", this.history); updateOnLocalStorage("history", this.history);
} }
} }

View File

@@ -1,7 +1,14 @@
service cloud.firestore { service cloud.firestore {
match /databases/{database}/documents { match /databases/{database}/documents {
match /{document=**} { match /{document=**} {
allow read, write; allow read, write: if request.auth.uid != null;
}
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
} }
} }
} }

173
functions/fb.js Normal file
View File

@@ -0,0 +1,173 @@
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/auth";
// Initialize Firebase, copied from cloud console
const firebaseConfig = {
apiKey: "AIzaSyCMsFreESs58-hRxTtiqQrIcimh4i1wbsM",
authDomain: "postwoman-api.firebaseapp.com",
databaseURL: "https://postwoman-api.firebaseio.com",
projectId: "postwoman-api",
storageBucket: "postwoman-api.appspot.com",
messagingSenderId: "421993993223",
appId: "1:421993993223:web:ec0baa8ee8c02ffa1fc6a2",
measurementId: "G-ERJ6025CEB"
};
firebase.initializeApp(firebaseConfig);
// a reference to the users collection
const usersCollection = firebase.firestore().collection("users");
// the shared state object that any vue component
// can get access to
export const fb = {
currentUser: {},
currentFeeds: [],
currentSettings: [],
currentHistory: [],
writeFeeds: async (message, label) => {
const dt = {
createdOn: new Date(),
author: fb.currentUser.uid,
author_name: fb.currentUser.displayName,
author_image: fb.currentUser.photoURL,
message,
label
};
try {
return usersCollection
.doc(fb.currentUser.uid)
.collection("feeds")
.add(dt);
} catch (e) {
return console.error("error inserting", dt, e);
}
},
deleteFeed: id => {
usersCollection
.doc(fb.currentUser.uid)
.collection("feeds")
.doc(id)
.delete()
.catch(e => console.error("error deleting", id, e));
},
writeSettings: async (setting, value) => {
const st = {
updatedOn: new Date(),
author: fb.currentUser.uid,
author_name: fb.currentUser.displayName,
author_image: fb.currentUser.photoURL,
name: setting,
value
};
try {
return usersCollection
.doc(fb.currentUser.uid)
.collection("settings")
.doc(setting)
.set(st);
} catch (e) {
return console.error("error updating", st, e);
}
},
writeHistory: async entry => {
const hs = entry;
try {
return usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.add(hs);
} catch (e) {
return console.error("error inserting", hs, e);
}
},
deleteHistory: entry => {
usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.doc(entry.id)
.delete()
.catch(e => console.error("error deleting", entry, e));
},
clearHistory: () => {
usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.get()
.then(({ docs }) => {
docs.forEach(e => fb.deleteHistory(e));
});
},
toggleStar: (entry, value) => {
usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.doc(entry.id)
.update({ star: value })
.catch(e => console.error("error deleting", entry, e));
}
};
// When a user logs in or out, save that in the store
firebase.auth().onAuthStateChanged(user => {
if (user) {
fb.currentUser = user;
fb.currentUser.providerData.forEach(profile => {
let us = {
updatedOn: new Date(),
provider: profile.providerId,
name: profile.displayName,
email: profile.email,
photoUrl: profile.photoURL,
uid: profile.uid
};
try {
usersCollection.doc(fb.currentUser.uid).set(us);
} catch (e) {
console.error("error updating", us, e);
}
});
usersCollection
.doc(fb.currentUser.uid)
.collection("feeds")
.orderBy("createdOn", "desc")
.onSnapshot(feedsRef => {
const feeds = [];
feedsRef.forEach(doc => {
const feed = doc.data();
feed.id = doc.id;
feeds.push(feed);
});
fb.currentFeeds = feeds;
});
usersCollection
.doc(fb.currentUser.uid)
.collection("settings")
.onSnapshot(settingsRef => {
const settings = [];
settingsRef.forEach(doc => {
const setting = doc.data();
setting.id = doc.id;
settings.push(setting);
});
fb.currentSettings = settings;
});
usersCollection
.doc(fb.currentUser.uid)
.collection("history")
.onSnapshot(historyRef => {
const history = [];
historyRef.forEach(doc => {
const entry = doc.data();
entry.id = doc.id;
history.push(entry);
});
fb.currentHistory = history;
});
} else {
fb.currentUser = null;
}
});

View File

@@ -1,19 +1,19 @@
import AxiosStrategy from "./strategies/AxiosStrategy"; import AxiosStrategy from "./strategies/AxiosStrategy";
import ProxyStrategy from "./strategies/ProxyStrategy";
import FirefoxStrategy from "./strategies/FirefoxStrategy"; import FirefoxStrategy from "./strategies/FirefoxStrategy";
import ChromeStrategy, { hasChromeExtensionInstalled } from "./strategies/ChromeStrategy";
const runAppropriateStrategy = (req, store) => { const runAppropriateStrategy = (req, store) => {
// Chrome Provides a chrome object for scripts to access
// Check its availability to say whether you are in Google Chrome
if (window.chrome && hasChromeExtensionInstalled()) {
return ChromeStrategy(req, store);
}
// The firefox plugin injects a function to send requests through it // The firefox plugin injects a function to send requests through it
// If that is available, then we can use the FirefoxStrategy // If that is available, then we can use the FirefoxStrategy
if (window.firefoxExtSendRequest) { if (window.firefoxExtSendRequest) {
return FirefoxStrategy(req, store); return FirefoxStrategy(req, store);
} }
if (store.state.postwoman.settings.PROXY_ENABLED) {
return ProxyStrategy(req, store);
}
return AxiosStrategy(req, store); return AxiosStrategy(req, store);
} }

View File

@@ -1,8 +1,23 @@
import axios from "axios"; import axios from "axios";
const axiosStrategy = async (req, _store) => { const axiosWithProxy = async (req, { state }) => {
const { data } = await axios.post(
state.postwoman.settings.PROXY_URL || "https://postwoman.apollotv.xyz/",
req
);
return data;
};
const axiosWithoutProxy = async (req, _store) => {
const res = await axios(req); const res = await axios(req);
return res; return res;
}; };
const axiosStrategy = (req, store) => {
if (store.state.postwoman.settings.PROXY_ENABLED) {
return axiosWithProxy(req, store);
}
return axiosWithoutProxy(req, store);
};
export default axiosStrategy; export default axiosStrategy;

View File

@@ -0,0 +1,56 @@
const EXTENSION_ID = "amknoiejhlmhancpahfcfcfhllgkpbld";
// Check if the Chrome Extension is present
// The Chrome extension injects an empty span to help detection.
// Also check for the presence of window.chrome object to confirm smooth operations
export const hasChromeExtensionInstalled = () => {
return document.getElementById("chromePWExtensionDetect") !== null;
}
const chromeWithoutProxy = (req, _store) => new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
EXTENSION_ID, {
messageType: "send-req",
data: {
config: req
}
}, (message) => {
if (message.data.error) {
reject(message.data.error);
} else {
resolve(message.data.response);
}
}
);
});
const chromeWithProxy = (req, { state }) => new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
EXTENSION_ID, {
messageType: "send-req",
data: {
config: {
method: "post",
url: state.postwoman.settings.PROXY_URL || "https://postwoman.apollotv.xyz/",
data: req
}
}
}, (message) => {
if (message.data.error) {
reject(error);
} else {
resolve(message.data.response.data);
}
}
)
});
const chromeStrategy = (req, store) => {
if (store.state.postwoman.settings.PROXY_ENABLED) {
return chromeWithProxy(req, store);
} else {
return chromeWithoutProxy(req, store);
}
}
export default chromeStrategy;

View File

@@ -1,15 +1,50 @@
const firefoxStrategy = (req, _store) => new Promise((resolve, reject) => { const firefoxWithProxy = (req, { state }) =>
new Promise((resolve, reject) => {
const eventListener = event => {
window.removeEventListener("firefoxExtSendRequestComplete", event);
const eventListener = (event) => { if (event.detail.error) {
window.removeEventListener("firefoxExtSendRequestComplete", eventListener); reject(JSON.parse(event.detail.error));
} else {
resolve(JSON.parse(event.detail.response).data);
}
};
if (event.detail.error) reject(JSON.parse(event.detail.error)); window.addEventListener("firefoxExtSendRequestComplete", eventListener);
else resolve(JSON.parse(event.detail.response));
window.firefoxExtSendRequest({
method: "post",
url:
state.postwoman.settings.PROXY_URL || "https://postwoman.apollotv.xyz/",
data: req
});
});
const firefoxWithoutProxy = (req, _store) =>
new Promise((resolve, reject) => {
const eventListener = ({ detail }) => {
window.removeEventListener(
"firefoxExtSendRequestComplete",
eventListener
);
if (detail.error) {
reject(JSON.parse(detail.error));
} else {
resolve(JSON.parse(detail.response));
}
}; };
window.addEventListener("firefoxExtSendRequestComplete", eventListener); window.addEventListener("firefoxExtSendRequestComplete", eventListener);
window.firefoxExtSendRequest(req); window.firefoxExtSendRequest(req);
}); });
const firefoxStrategy = (req, store) => {
if (store.state.postwoman.settings.PROXY_ENABLED) {
return firefoxWithProxy(req, store);
}
return firefoxWithoutProxy(req, store);
};
export default firefoxStrategy; export default firefoxStrategy;

View File

@@ -1,12 +0,0 @@
import axios from "axios";
const proxyStrategy = async (req, store) => {
const { data } = await axios.post(
store.state.postwoman.settings.PROXY_URL ||
"https://postwoman.apollotv.xyz/",
req
);
return data;
};
export default proxyStrategy;

View File

@@ -0,0 +1,15 @@
// Debounce is a higher order function which makes its enclosed function be executed
// only if the function wasn't called again till 'delay' time has passed, this helps reduce impact of heavy working
// functions which might be called frequently
// NOTE : Don't use lambda functions as this doesn't get bound properly in them, use the 'function (args) {}' format
const debounce = (func, delay) => {
let inDebounce
return function() {
const context = this
const args = arguments
clearTimeout(inDebounce)
inDebounce = setTimeout(() => func.apply(context, args), delay)
}
}
export default debounce;

View File

@@ -249,5 +249,15 @@ export default {
extensions: "Extensions", extensions: "Extensions",
extensions_info1: "Browser extension that simplifies access to Postwoman", extensions_info1: "Browser extension that simplifies access to Postwoman",
extensions_info2: "Get Postwoman browser extension!", extensions_info2: "Get Postwoman browser extension!",
installed: "Installed" installed: "Installed",
login_with: "Login with",
logged_out: "Logged out",
logout: "Logout",
account: "Account",
sync: "Sync",
syncHistory: "History",
syncCollections: "Collections",
turn_on: "Turn on",
login_first: "Login first",
paste_a_collection: "Paste a Collection"
}; };

View File

@@ -237,6 +237,11 @@
<div v-else-if="$route.path === '/settings'"> <div v-else-if="$route.path === '/settings'">
<nav class="secondary-nav"> <nav class="secondary-nav">
<ul> <ul>
<li>
<a href="#account" v-tooltip.right="$t('account')">
<i class="material-icons">person</i>
</a>
</li>
<li> <li>
<a href="#theme" v-tooltip.right="$t('theme')"> <a href="#theme" v-tooltip.right="$t('theme')">
<i class="material-icons">brush</i> <i class="material-icons">brush</i>
@@ -288,6 +293,46 @@
> >
<i class="material-icons">offline_bolt</i> <i class="material-icons">offline_bolt</i>
</button> </button>
<login v-if="!fb.currentUser" />
<span v-if="fb.currentUser">
<v-popover>
<button
class="icon"
v-tooltip="
(fb.currentUser.displayName ||
'<label><i>Name not found</i></label>') +
'<br>' +
(fb.currentUser.email ||
'<label><i>Email not found</i></label>')
"
>
<img
v-if="fb.currentUser.photoURL"
:src="fb.currentUser.photoURL"
class="material-icons"
/>
<i v-else class="material-icons">account_circle</i>
</button>
<template slot="popover">
<div>
<nuxt-link :to="localePath('settings')" v-close-popover>
<button class="icon">
<i class="material-icons">settings</i>
<span>
{{ $t("settings") }}
</span>
</button>
</nuxt-link>
</div>
<div>
<button class="icon" @click="logout" v-close-popover>
<i class="material-icons">exit_to_app</i>
<span>{{ $t("logout") }}</span>
</button>
</div>
</template>
</v-popover>
</span>
<v-popover> <v-popover>
<button class="icon" v-tooltip="$t('more')"> <button class="icon" v-tooltip="$t('more')">
<i class="material-icons">more_vert</i> <i class="material-icons">more_vert</i>
@@ -352,15 +397,7 @@
<div class="flex-wrap"> <div class="flex-wrap">
<span v-if="version.name" class="mono"> <span v-if="version.name" class="mono">
<a <a
href="https://liyasthomas.web.app" class="link"
target="_blank"
rel="noopener"
>
<button class="icon" v-tooltip="'Liyas Thomas'">
🦄
</button>
</a>
<a
:href=" :href="
'https://github.com/liyasthomas/postwoman/releases/tag/' + 'https://github.com/liyasthomas/postwoman/releases/tag/' +
version.name version.name
@@ -371,6 +408,14 @@
> >
{{ version.name }} {{ version.name }}
</a> </a>
<a
class="link"
href="https://www.netlify.com"
target="_blank"
rel="noopener"
>
Powered by Netlify
</a>
<!-- <span v-if="version.hash"> <!-- <span v-if="version.hash">
- -
<a <a
@@ -382,6 +427,15 @@
<!-- <span v-if="version.variant">({{version.variant}})</span> --> <!-- <span v-if="version.variant">({{version.variant}})</span> -->
</span> </span>
<span> <span>
<a
href="https://liyasthomas.web.app"
target="_blank"
rel="noopener"
>
<button class="icon" v-tooltip="'Liyas Thomas'">
🦄
</button>
</a>
<a <a
href="mailto:liyascthomas@gmail.com" href="mailto:liyascthomas@gmail.com"
target="_blank" target="_blank"
@@ -461,7 +515,12 @@
</a> </a>
</div> </div>
<div> <div>
<button class="icon" disabled> <a
href="https://chrome.google.com/webstore/detail/postwoman-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld"
target="_blank"
rel="noopener"
>
<button class="icon">
<svg <svg
class="material-icons" class="material-icons"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -473,7 +532,7 @@
d="M2.897 4.181c2.43-2.828 5.763-4.181 9.072-4.181 4.288 0 8.535 2.273 10.717 6.554-2.722.001-6.984 0-9.293 0-1.674.001-2.755-.037-3.926.579-1.376.724-2.415 2.067-2.777 3.644l-3.793-6.596zm5.11 7.819c0 2.2 1.789 3.99 3.988 3.99s3.988-1.79 3.988-3.99-1.789-3.991-3.988-3.991-3.988 1.791-3.988 3.991zm5.536 5.223c-2.238.666-4.858-.073-6.293-2.549-1.095-1.891-3.989-6.933-5.305-9.225-1.33 2.04-1.945 4.294-1.945 6.507 0 5.448 3.726 10.65 9.673 11.818l3.87-6.551zm2.158-9.214c1.864 1.734 2.271 4.542 1.007 6.719-.951 1.641-3.988 6.766-5.46 9.248 7.189.443 12.752-5.36 12.752-11.972 0-1.313-.22-2.66-.69-3.995h-7.609z" d="M2.897 4.181c2.43-2.828 5.763-4.181 9.072-4.181 4.288 0 8.535 2.273 10.717 6.554-2.722.001-6.984 0-9.293 0-1.674.001-2.755-.037-3.926.579-1.376.724-2.415 2.067-2.777 3.644l-3.793-6.596zm5.11 7.819c0 2.2 1.789 3.99 3.988 3.99s3.988-1.79 3.988-3.99-1.789-3.991-3.988-3.991-3.988 1.791-3.988 3.991zm5.536 5.223c-2.238.666-4.858-.073-6.293-2.549-1.095-1.891-3.989-6.933-5.305-9.225-1.33 2.04-1.945 4.294-1.945 6.507 0 5.448 3.726 10.65 9.673 11.818l3.87-6.551zm2.158-9.214c1.864 1.734 2.271 4.542 1.007 6.719-.951 1.641-3.988 6.766-5.46 9.248 7.189.443 12.752-5.36 12.752-11.972 0-1.313-.22-2.66-.69-3.995h-7.609z"
/> />
</svg> </svg>
<span>Chrome (coming soon)</span> <span>Chrome</span>
<span <span
class="icon" class="icon"
v-if="chromeExtInstalled" v-if="chromeExtInstalled"
@@ -482,6 +541,7 @@
<i class="material-icons">done</i> <i class="material-icons">done</i>
</span> </span>
</button> </button>
</a>
</div> </div>
</div> </div>
<div slot="footer"></div> <div slot="footer"></div>
@@ -588,16 +648,31 @@
</div> </div>
</template> </template>
<style scoped lang="scss"></style> <style scoped lang="scss">
.link {
margin: 8px 16px;
}
@media (max-width: 768px) {
.link {
display: flex;
flex: 1;
}
}
</style>
<script> <script>
import intializePwa from "../assets/js/pwa"; import intializePwa from "../assets/js/pwa";
import * as version from "../.postwoman/version.json"; import * as version from "../.postwoman/version.json";
import { hasChromeExtensionInstalled } from "../functions/strategies/ChromeStrategy";
import firebase from 'firebase/app';
import { fb } from '../functions/fb';
export default { export default {
components: { components: {
logo: () => import("../components/logo"), logo: () => import("../components/logo"),
modal: () => import("../components/modal") modal: () => import("../components/modal"),
login: () => import("../components/firebase/login")
}, },
methods: { methods: {
@@ -606,6 +681,21 @@ export default {
"nuxt-link-exact-active": this.$route.path === path, "nuxt-link-exact-active": this.$route.path === path,
"nuxt-link-active": this.$route.path === path "nuxt-link-active": this.$route.path === path
}; };
},
logout() {
fb.currentUser = null;
firebase
.auth()
.signOut()
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
this.$toast.info(this.$t("logged_out"), {
icon: "vpn_key"
});
} }
}, },
@@ -619,9 +709,9 @@ export default {
showExtensions: false, showExtensions: false,
showShortcuts: false, showShortcuts: false,
showSupport: false, showSupport: false,
firefoxExtInstalled: firefoxExtInstalled: window.firefoxExtSendRequest,
window.firefoxExtSendRequest !== undefined ? true : false, chromeExtInstalled: window.chrome && hasChromeExtensionInstalled(),
chromeExtInstalled: false fb
}; };
}, },
@@ -742,7 +832,7 @@ export default {
watch: { watch: {
$route() { $route() {
this.$toast.clear(); // this.$toast.clear();
} }
}, },

994
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,11 +26,13 @@
"@nuxtjs/sitemap": "^2.0.1", "@nuxtjs/sitemap": "^2.0.1",
"@nuxtjs/toast": "^3.3.0", "@nuxtjs/toast": "^3.3.0",
"ace-builds": "^1.4.8", "ace-builds": "^1.4.8",
"firebase": "^7.7.0",
"graphql": "^14.5.8", "graphql": "^14.5.8",
"nuxt": "^2.11.0", "nuxt": "^2.11.0",
"nuxt-i18n": "^6.4.1", "nuxt-i18n": "^6.4.1",
"v-tooltip": "^2.0.2", "v-tooltip": "^2.0.2",
"vue-virtual-scroll-list": "^1.4.4", "vue-virtual-scroll-list": "^1.4.4",
"vuefire": "^2.2.1",
"vuejs-auto-complete": "^0.9.0", "vuejs-auto-complete": "^0.9.0",
"vuex-persist": "^2.2.0", "vuex-persist": "^2.2.0",
"yargs-parser": "^16.1.0" "yargs-parser": "^16.1.0"

View File

@@ -175,7 +175,8 @@
</button> </button>
</div> </div>
</div> </div>
<Editor <QueryEditor
ref="queryEditor"
v-model="gqlQueryString" v-model="gqlQueryString"
:options="{ :options="{
maxLines: responseBodyMaxLines, maxLines: responseBodyMaxLines,
@@ -391,6 +392,7 @@ import axios from "axios";
import * as gql from "graphql"; import * as gql from "graphql";
import textareaAutoHeight from "../directives/textareaAutoHeight"; import textareaAutoHeight from "../directives/textareaAutoHeight";
import AceEditor from "../components/ace-editor"; import AceEditor from "../components/ace-editor";
import QueryEditor from "../components/graphql/queryeditor";
import { sendNetworkRequest } from "../functions/network"; import { sendNetworkRequest } from "../functions/network";
export default { export default {
@@ -402,7 +404,8 @@ export default {
"gql-field": () => import("../components/graphql/field"), "gql-field": () => import("../components/graphql/field"),
"gql-type": () => import("../components/graphql/type"), "gql-type": () => import("../components/graphql/type"),
autocomplete: () => import("../components/autocomplete"), autocomplete: () => import("../components/autocomplete"),
Editor: AceEditor Editor: AceEditor,
QueryEditor: QueryEditor
}, },
data() { data() {
return { return {
@@ -543,6 +546,7 @@ export default {
responseBodyMaxLines: 16 responseBodyMaxLines: 16
}; };
}, },
computed: { computed: {
url: { url: {
get() { get() {
@@ -809,6 +813,8 @@ export default {
} }
this.gqlTypes = types; this.gqlTypes = types;
this.$refs.queryEditor.setValidationSchema(schema);
this.$nuxt.$loading.finish(); this.$nuxt.$loading.finish();
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
this.$toast.info(this.$t("finished_in", { duration }), { this.$toast.info(this.$t("finished_in", { duration }), {
@@ -816,7 +822,7 @@ export default {
}); });
} catch (error) { } catch (error) {
this.$nuxt.$loading.finish(); this.$nuxt.$loading.finish();
this.schemaString = `${error}. ${check_console_details}`; this.schemaString = `${error}. ${this.$t("check_console_details")}`;
this.$toast.error(`${error} ${this.$t("f12_details")}`, { this.$toast.error(`${error} ${this.$t("f12_details")}`, {
icon: "error" icon: "error"
}); });

View File

@@ -117,8 +117,7 @@
:source="validContentTypes" :source="validContentTypes"
:spellcheck="false" :spellcheck="false"
v-model="contentType" v-model="contentType"
>Content Type >Content Type</autocomplete
</autocomplete
> >
</li> </li>
</ul> </ul>
@@ -283,26 +282,6 @@
>close</i >close</i
> >
</button> </button>
<button
:class="'icon' + (showPreRequestScript ? ' info-response' : '')"
id="preRequestScriptButto"
v-tooltip.bottom="{
content: !testsEnabled
? 'Enable Tests'
: 'Disable Tests'
}"
@click="testsEnabled = !testsEnabled"
>
<i
class="material-icons"
:class="testsEnabled"
v-if="!testsEnabled"
>code</i
>
<i class="material-icons" :class="testsEnabled" v-else
>close</i
>
</button>
</span> </span>
<span> <span>
<button <button
@@ -336,46 +315,9 @@
</span> </span>
</div> </div>
</pw-section> </pw-section>
<pw-section
v-if="testsEnabled"
class="orange"
label="Tests"
ref="postRequestTests"
>
<ul>
<li>
<div class="flex-wrap">
<label for="generatedCode">{{ $t("javascript_code") }}</label>
<div>
<a
href="https://github.com/liyasthomas/postwoman/wiki/Post-Request-Tests"
target="_blank"
rel="noopener"
>
<button class="icon" v-tooltip="$t('wiki')">
<i class="material-icons">help</i>
</button>
</a>
</div>
</div>
<Editor
v-model="testScript"
:lang="'javascript'"
:options="{
maxLines: responseBodyMaxLines,
minLines: '16',
fontSize: '16px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false
}"
/>
</li>
</ul>
</pw-section>
<section id="options"> <section id="options">
<input id="tab-one" type="radio" name="options" checked="checked"/> <input id="tab-one" type="radio" name="options" checked="checked" />
<label for="tab-one">{{ $t("authentication") }}</label> <label for="tab-one">{{ $t("authentication") }}</label>
<div class="tab"> <div class="tab">
<pw-section <pw-section
@@ -603,7 +545,7 @@
</ul> </ul>
</pw-section> </pw-section>
</div> </div>
<input id="tab-two" type="radio" name="options"/> <input id="tab-two" type="radio" name="options" />
<label for="tab-two">{{ $t("headers") }}</label> <label for="tab-two">{{ $t("headers") }}</label>
<div class="tab"> <div class="tab">
<pw-section class="orange" label="Headers" ref="headers"> <pw-section class="orange" label="Headers" ref="headers">
@@ -685,7 +627,7 @@
</ul> </ul>
</pw-section> </pw-section>
</div> </div>
<input id="tab-three" type="radio" name="options"/> <input id="tab-three" type="radio" name="options" />
<label for="tab-three">{{ $t("parameters") }}</label> <label for="tab-three">{{ $t("parameters") }}</label>
<div class="tab"> <div class="tab">
<pw-section class="pink" label="Parameters" ref="parameters"> <pw-section class="pink" label="Parameters" ref="parameters">
@@ -803,7 +745,7 @@
<ul v-for="(value, key) in response.headers" :key="key"> <ul v-for="(value, key) in response.headers" :key="key">
<li> <li>
<label :for="key">{{ key }}</label> <label :for="key">{{ key }}</label>
<input :id="key" :value="value" :name="key" readonly/> <input :id="key" :value="value" :name="key" readonly />
</li> </li>
</ul> </ul>
<ul v-if="response.body"> <ul v-if="response.body">
@@ -888,12 +830,12 @@
</div> </div>
<aside v-if="activeSidebar" class="sticky-inner inner-right"> <aside v-if="activeSidebar" class="sticky-inner inner-right">
<section> <section>
<input id="history-tab" type="radio" name="side" checked="checked"/> <input id="history-tab" type="radio" name="side" checked="checked" />
<label for="history-tab">{{ $t("history") }}</label> <label for="history-tab">{{ $t("history") }}</label>
<div class="tab"> <div class="tab">
<history @useHistory="handleUseHistory" ref="historyComponent"/> <history @useHistory="handleUseHistory" ref="historyComponent" />
</div> </div>
<input id="collection-tab" type="radio" name="side"/> <input id="collection-tab" type="radio" name="side" />
<label for="collection-tab">{{ $t("collections") }}</label> <label for="collection-tab">{{ $t("collections") }}</label>
<div class="tab"> <div class="tab">
<pw-section <pw-section
@@ -901,7 +843,27 @@
:label="$t('collections')" :label="$t('collections')"
ref="collections" ref="collections"
> >
<collections/> <collections />
</pw-section>
</div>
<input id="sync-tab" type="radio" name="side" />
<label for="sync-tab">{{ $t("sync") }}</label>
<div class="tab">
<pw-section
v-if="fb.currentUser"
class="pink"
label="Sync"
ref="sync"
>
<inputform />
<ballsfeed />
</pw-section>
<pw-section v-else>
<ul>
<li>
<label>{{ $t("login_first") }}</label>
</li>
</ul>
</pw-section> </pw-section>
</div> </div>
</section> </section>
@@ -1058,7 +1020,7 @@
/> />
</li> </li>
<li> <li>
<input :value="token.value" readonly/> <input :value="token.value" readonly />
</li> </li>
<div class="flex-wrap"> <div class="flex-wrap">
<li> <li>
@@ -1151,7 +1113,7 @@
<ul> <ul>
<li> <li>
<label for="token-req-name">{{ $t("token_req_name") }}</label> <label for="token-req-name">{{ $t("token_req_name") }}</label>
<input v-model="tokenReqName"/> <input v-model="tokenReqName" />
</li> </li>
</ul> </ul>
<ul> <ul>
@@ -1184,19 +1146,19 @@
</template> </template>
<script> <script>
import section from "../components/section"; import section from "../components/section";
import url from "url"; import url from "url";
import querystring from "querystring"; import querystring from "querystring";
import textareaAutoHeight from "../directives/textareaAutoHeight"; import textareaAutoHeight from "../directives/textareaAutoHeight";
import parseCurlCommand from "../assets/js/curlparser.js"; import parseCurlCommand from "../assets/js/curlparser.js";
import getEnvironmentVariablesFromScript from "../functions/preRequest"; import getEnvironmentVariablesFromScript from "../functions/preRequest";
import runTestScriptWitVariables from '../functions/postWomanTesting'; import parseTemplateString from "../functions/templating";
import parseTemplateString from "../functions/templating"; import AceEditor from "../components/ace-editor";
import AceEditor from "../components/ace-editor"; import { tokenRequest, oauthRedirect } from "../assets/js/oauth";
import {tokenRequest, oauthRedirect} from "../assets/js/oauth"; import { sendNetworkRequest } from "../functions/network";
import {sendNetworkRequest} from "../functions/network"; import { fb } from "../functions/fb";
const statusCategories = [ const statusCategories = [
{ {
name: "informational", name: "informational",
statusCodeRegex: new RegExp(/[1][0-9]+/), statusCodeRegex: new RegExp(/[1][0-9]+/),
@@ -1228,8 +1190,8 @@
statusCodeRegex: new RegExp(/.*/), statusCodeRegex: new RegExp(/.*/),
className: "missing-data-response" className: "missing-data-response"
} }
]; ];
const parseHeaders = xhr => { const parseHeaders = xhr => {
const headers = xhr const headers = xhr
.getAllResponseHeaders() .getAllResponseHeaders()
.trim() .trim()
@@ -1242,11 +1204,11 @@
headerMap[header] = value; headerMap[header] = value;
}); });
return headerMap; return headerMap;
}; };
export const findStatusGroup = responseStatus => export const findStatusGroup = responseStatus =>
statusCategories.find(status => status.statusCodeRegex.test(responseStatus)); statusCategories.find(status => status.statusCodeRegex.test(responseStatus));
export default { export default {
directives: { directives: {
textareaAutoHeight textareaAutoHeight
}, },
@@ -1259,15 +1221,15 @@
autocomplete: () => import("../components/autocomplete"), autocomplete: () => import("../components/autocomplete"),
collections: () => import("../components/collections"), collections: () => import("../components/collections"),
saveRequestAs: () => import("../components/collections/saveRequestAs"), saveRequestAs: () => import("../components/collections/saveRequestAs"),
Editor: AceEditor Editor: AceEditor,
inputform: () => import("../components/firebase/inputform"),
ballsfeed: () => import("../components/firebase/feeds")
}, },
data() { data() {
return { return {
showModal: false, showModal: false,
showPreRequestScript: false, showPreRequestScript: false,
testsEnabled: false,
preRequestScript: "// pw.env.set('variable', 'value');", preRequestScript: "// pw.env.set('variable', 'value');",
testScript: "// pw.expect('variable').toBe('value');",
copyButton: '<i class="material-icons">file_copy</i>', copyButton: '<i class="material-icons">file_copy</i>',
downloadButton: '<i class="material-icons">get_app</i>', downloadButton: '<i class="material-icons">get_app</i>',
doneButton: '<i class="material-icons">done</i>', doneButton: '<i class="material-icons">done</i>',
@@ -1431,12 +1393,12 @@
], ],
showRequestModal: false, showRequestModal: false,
editRequest: {}, editRequest: {},
urlExcludes: {}, urlExcludes: {},
responseBodyText: "", responseBodyText: "",
responseBodyType: "text", responseBodyType: "text",
responseBodyMaxLines: 16, responseBodyMaxLines: 16,
activeSidebar: true activeSidebar: true,
fb
}; };
}, },
watch: { watch: {
@@ -1456,7 +1418,7 @@
if (status && this.rawParams === "") this.rawParams = "{}"; if (status && this.rawParams === "") this.rawParams = "{}";
else this.setRouteQueryState(); else this.setRouteQueryState();
}, },
"response.body": function (val) { "response.body": function(val) {
if ( if (
this.response.body === this.$t("waiting_send_req") || this.response.body === this.$t("waiting_send_req") ||
this.response.body === this.$t("loading") this.response.body === this.$t("loading")
@@ -1480,15 +1442,15 @@
} }
}, },
params: { params: {
handler: function (newValue) { handler: function(newValue) {
if (!this.paramsWatchEnabled) { if (!this.paramsWatchEnabled) {
this.paramsWatchEnabled = true; this.paramsWatchEnabled = true;
return; return;
} }
let path = this.path; let path = this.path;
let queryString = newValue let queryString = newValue
.filter(({key}) => !!key) .filter(({ key }) => !!key)
.map(({key, value}) => `${key}=${value}`) .map(({ key, value }) => `${key}=${value}`)
.join("&"); .join("&");
queryString = queryString === "" ? "" : `?${queryString}`; queryString = queryString === "" ? "" : `?${queryString}`;
if (path.includes("?")) { if (path.includes("?")) {
@@ -1531,7 +1493,7 @@
return this.$store.state.request.url; return this.$store.state.request.url;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "url"}); this.$store.commit("setState", { value, attribute: "url" });
} }
}, },
method: { method: {
@@ -1539,7 +1501,7 @@
return this.$store.state.request.method; return this.$store.state.request.method;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "method"}); this.$store.commit("setState", { value, attribute: "method" });
} }
}, },
path: { path: {
@@ -1547,7 +1509,7 @@
return this.$store.state.request.path; return this.$store.state.request.path;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "path"}); this.$store.commit("setState", { value, attribute: "path" });
} }
}, },
label: { label: {
@@ -1555,7 +1517,7 @@
return this.$store.state.request.label; return this.$store.state.request.label;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "label"}); this.$store.commit("setState", { value, attribute: "label" });
} }
}, },
auth: { auth: {
@@ -1563,7 +1525,7 @@
return this.$store.state.request.auth; return this.$store.state.request.auth;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "auth"}); this.$store.commit("setState", { value, attribute: "auth" });
} }
}, },
httpUser: { httpUser: {
@@ -1571,7 +1533,7 @@
return this.$store.state.request.httpUser; return this.$store.state.request.httpUser;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "httpUser"}); this.$store.commit("setState", { value, attribute: "httpUser" });
} }
}, },
httpPassword: { httpPassword: {
@@ -1579,7 +1541,7 @@
return this.$store.state.request.httpPassword; return this.$store.state.request.httpPassword;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "httpPassword"}); this.$store.commit("setState", { value, attribute: "httpPassword" });
} }
}, },
bearerToken: { bearerToken: {
@@ -1587,7 +1549,7 @@
return this.$store.state.request.bearerToken; return this.$store.state.request.bearerToken;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "bearerToken"}); this.$store.commit("setState", { value, attribute: "bearerToken" });
} }
}, },
tokens: { tokens: {
@@ -1595,7 +1557,7 @@
return this.$store.state.oauth2.tokens; return this.$store.state.oauth2.tokens;
}, },
set(value) { set(value) {
this.$store.commit("setOAuth2", {value, attribute: "tokens"}); this.$store.commit("setOAuth2", { value, attribute: "tokens" });
} }
}, },
tokenReqs: { tokenReqs: {
@@ -1603,7 +1565,7 @@
return this.$store.state.oauth2.tokenReqs; return this.$store.state.oauth2.tokenReqs;
}, },
set(value) { set(value) {
this.$store.commit("setOAuth2", {value, attribute: "tokenReqs"}); this.$store.commit("setOAuth2", { value, attribute: "tokenReqs" });
} }
}, },
tokenReqSelect: { tokenReqSelect: {
@@ -1611,7 +1573,7 @@
return this.$store.state.oauth2.tokenReqSelect; return this.$store.state.oauth2.tokenReqSelect;
}, },
set(value) { set(value) {
this.$store.commit("setOAuth2", {value, attribute: "tokenReqSelect"}); this.$store.commit("setOAuth2", { value, attribute: "tokenReqSelect" });
} }
}, },
tokenReqName: { tokenReqName: {
@@ -1619,7 +1581,7 @@
return this.$store.state.oauth2.tokenReqName; return this.$store.state.oauth2.tokenReqName;
}, },
set(value) { set(value) {
this.$store.commit("setOAuth2", {value, attribute: "tokenReqName"}); this.$store.commit("setOAuth2", { value, attribute: "tokenReqName" });
} }
}, },
accessTokenName: { accessTokenName: {
@@ -1649,7 +1611,7 @@
return this.$store.state.oauth2.authUrl; return this.$store.state.oauth2.authUrl;
}, },
set(value) { set(value) {
this.$store.commit("setOAuth2", {value, attribute: "authUrl"}); this.$store.commit("setOAuth2", { value, attribute: "authUrl" });
} }
}, },
accessTokenUrl: { accessTokenUrl: {
@@ -1657,7 +1619,7 @@
return this.$store.state.oauth2.accessTokenUrl; return this.$store.state.oauth2.accessTokenUrl;
}, },
set(value) { set(value) {
this.$store.commit("setOAuth2", {value, attribute: "accessTokenUrl"}); this.$store.commit("setOAuth2", { value, attribute: "accessTokenUrl" });
} }
}, },
clientId: { clientId: {
@@ -1665,7 +1627,7 @@
return this.$store.state.oauth2.clientId; return this.$store.state.oauth2.clientId;
}, },
set(value) { set(value) {
this.$store.commit("setOAuth2", {value, attribute: "clientId"}); this.$store.commit("setOAuth2", { value, attribute: "clientId" });
} }
}, },
scope: { scope: {
@@ -1673,7 +1635,7 @@
return this.$store.state.oauth2.scope; return this.$store.state.oauth2.scope;
}, },
set(value) { set(value) {
this.$store.commit("setOAuth2", {value, attribute: "scope"}); this.$store.commit("setOAuth2", { value, attribute: "scope" });
} }
}, },
state: { state: {
@@ -1681,7 +1643,7 @@
return this.$store.state.oauth2.state; return this.$store.state.oauth2.state;
}, },
set(value) { set(value) {
this.$store.commit("setOAuth2", {value, attribute: "state"}); this.$store.commit("setOAuth2", { value, attribute: "state" });
} }
}, },
headers: { headers: {
@@ -1689,7 +1651,7 @@
return this.$store.state.request.headers; return this.$store.state.request.headers;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "headers"}); this.$store.commit("setState", { value, attribute: "headers" });
} }
}, },
params: { params: {
@@ -1697,7 +1659,7 @@
return this.$store.state.request.params; return this.$store.state.request.params;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "params"}); this.$store.commit("setState", { value, attribute: "params" });
} }
}, },
bodyParams: { bodyParams: {
@@ -1705,7 +1667,7 @@
return this.$store.state.request.bodyParams; return this.$store.state.request.bodyParams;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "bodyParams"}); this.$store.commit("setState", { value, attribute: "bodyParams" });
} }
}, },
rawParams: { rawParams: {
@@ -1713,7 +1675,7 @@
return this.$store.state.request.rawParams; return this.$store.state.request.rawParams;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "rawParams"}); this.$store.commit("setState", { value, attribute: "rawParams" });
} }
}, },
rawInput: { rawInput: {
@@ -1721,7 +1683,7 @@
return this.$store.state.request.rawInput; return this.$store.state.request.rawInput;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "rawInput"}); this.$store.commit("setState", { value, attribute: "rawInput" });
} }
}, },
requestType: { requestType: {
@@ -1729,7 +1691,7 @@
return this.$store.state.request.requestType; return this.$store.state.request.requestType;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "requestType"}); this.$store.commit("setState", { value, attribute: "requestType" });
} }
}, },
contentType: { contentType: {
@@ -1737,7 +1699,7 @@
return this.$store.state.request.contentType; return this.$store.state.request.contentType;
}, },
set(value) { set(value) {
this.$store.commit("setState", {value, attribute: "contentType"}); this.$store.commit("setState", { value, attribute: "contentType" });
} }
}, },
passwordFieldType: { passwordFieldType: {
@@ -1787,14 +1749,14 @@
return this.path.match(/^([^?]*)\??/)[1]; return this.path.match(/^([^?]*)\??/)[1];
}, },
rawRequestBody() { rawRequestBody() {
const {bodyParams} = this; const { bodyParams } = this;
if (this.contentType === "application/json") { if (this.contentType === "application/json") {
try { try {
const obj = JSON.parse( const obj = JSON.parse(
`{${bodyParams `{${bodyParams
.filter(({key}) => !!key) .filter(({ key }) => !!key)
.map( .map(
({key, value}) => ` ({ key, value }) => `
"${key}": "${value}" "${key}": "${value}"
` `
) )
@@ -1806,22 +1768,22 @@
} }
} else { } else {
return bodyParams return bodyParams
.filter(({key}) => !!key) .filter(({ key }) => !!key)
.map(({key, value}) => `${key}=${encodeURIComponent(value)}`) .map(({ key, value }) => `${key}=${encodeURIComponent(value)}`)
.join("&"); .join("&");
} }
}, },
headerString() { headerString() {
const result = this.headers const result = this.headers
.filter(({key}) => !!key) .filter(({ key }) => !!key)
.map(({key, value}) => `${key}: ${value}`) .map(({ key, value }) => `${key}: ${value}`)
.join(",\n"); .join(",\n");
return result === "" ? "" : `${result}`; return result === "" ? "" : `${result}`;
}, },
queryString() { queryString() {
const result = this.params const result = this.params
.filter(({key}) => !!key) .filter(({ key }) => !!key)
.map(({key, value}) => `${key}=${encodeURIComponent(value)}`) .map(({ key, value }) => `${key}=${encodeURIComponent(value)}`)
.join("&"); .join("&");
return result === "" ? "" : `?${result}`; return result === "" ? "" : `?${result}`;
}, },
@@ -2138,10 +2100,10 @@
); );
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
this.$toast.info(this.$t("finished_in", {duration}), { this.$toast.info(this.$t("finished_in", { duration }), {
icon: "done" icon: "done"
}); });
// tests
(() => { (() => {
const status = (this.response.status = payload.status); const status = (this.response.status = payload.status);
const headers = (this.response.headers = payload.headers); const headers = (this.response.headers = payload.headers);
@@ -2167,15 +2129,12 @@
star: false star: false
}; };
this.$refs.historyComponent.addEntry(entry); this.$refs.historyComponent.addEntry(entry);
if (fb.currentUser !== null) {
if (fb.currentSettings[1].value) {
fb.writeHistory(entry);
}
}
})(); })();
const syntheticResponse = {
status: this.response.status,
body: this.response.body,
headers: this.response.headers
};
const testResults = runTestScriptWitVariables(this.testScript, {response: syntheticResponse});
} catch (error) { } catch (error) {
console.error(error); console.error(error);
if (error.response) { if (error.response) {
@@ -2193,10 +2152,14 @@
url: this.url, url: this.url,
path: this.path, path: this.path,
usesScripts: Boolean(this.preRequestScript), usesScripts: Boolean(this.preRequestScript),
preRequestScript: this.preRequestScript, preRequestScript: this.preRequestScript
testCode: this.testCode
}; };
this.$refs.historyComponent.addEntry(entry); this.$refs.historyComponent.addEntry(entry);
if (fb.currentUser !== null) {
if (fb.currentSettings[1].value) {
fb.writeHistory(entry);
}
}
return; return;
} else { } else {
this.response.status = error.message; this.response.status = error.message;
@@ -2209,9 +2172,9 @@
icon: "help", icon: "help",
duration: 8000, duration: 8000,
action: { action: {
text: "Settings", text: this.$t("yes"),
onClick: (e, toastObject) => { onClick: (e, toastObject) => {
this.$router.push({path: "/settings"}); this.$router.push({ path: "/settings" });
} }
} }
}); });
@@ -2262,7 +2225,7 @@
}); });
}, },
addRequestParam() { addRequestParam() {
this.$store.commit("addParams", {key: "", value: ""}); this.$store.commit("addParams", { key: "", value: "" });
return false; return false;
}, },
removeRequestParam(index) { removeRequestParam(index) {
@@ -2282,7 +2245,7 @@
}); });
}, },
addRequestBodyParam() { addRequestBodyParam() {
this.$store.commit("addBodyParams", {key: "", value: ""}); this.$store.commit("addBodyParams", { key: "", value: "" });
return false; return false;
}, },
removeRequestBodyParam(index) { removeRequestBodyParam(index) {
@@ -2311,8 +2274,7 @@
text: `Postwoman • API request builder at ${time} on ${date}`, text: `Postwoman • API request builder at ${time} on ${date}`,
url: window.location.href url: window.location.href
}) })
.then(() => { .then(() => {})
})
.catch(console.error); .catch(console.error);
} else { } else {
const dummy = document.createElement("input"); const dummy = document.createElement("input");
@@ -2370,7 +2332,7 @@
}, },
downloadResponse() { downloadResponse() {
const dataToWrite = JSON.stringify(this.response.body, null, 2); const dataToWrite = JSON.stringify(this.response.body, null, 2);
const file = new Blob([dataToWrite], {type: this.responseType}); const file = new Blob([dataToWrite], { type: this.responseType });
const a = document.createElement("a"), const a = document.createElement("a"),
url = URL.createObjectURL(file); url = URL.createObjectURL(file);
a.href = url; a.href = url;
@@ -2698,7 +2660,7 @@
}); });
} }
}, },
addOAuthToken({name, value}) { addOAuthToken({ name, value }) {
this.$store.commit("addOAuthToken", { this.$store.commit("addOAuthToken", {
name, name,
value value
@@ -2778,7 +2740,7 @@
}, },
async mounted() { async mounted() {
this.observeRequestButton(); this.observeRequestButton();
this._keyListener = function (e) { this._keyListener = function(e) {
if (e.key === "g" && (e.ctrlKey || e.metaKey)) { if (e.key === "g" && (e.ctrlKey || e.metaKey)) {
e.preventDefault(); e.preventDefault();
this.sendRequest(); this.sendRequest();
@@ -2831,5 +2793,5 @@
beforeDestroy() { beforeDestroy() {
document.removeEventListener("keydown", this._keyListener); document.removeEventListener("keydown", this._keyListener);
} }
}; };
</script> </script>

View File

@@ -1,5 +1,88 @@
<template> <template>
<div class="page"> <div class="page">
<pw-section class="green" :label="$t('account')" ref="account">
<ul>
<li>
<div v-if="fb.currentUser">
<button class="icon">
<img
v-if="fb.currentUser.photoURL"
:src="fb.currentUser.photoURL"
class="material-icons"
/>
<i v-else class="material-icons">account_circle</i>
<span>
{{ fb.currentUser.displayName || "Name not found" }}
</span>
</button>
<br />
<button class="icon">
<i class="material-icons">email</i>
<span>
{{ fb.currentUser.email || "Email not found" }}
</span>
</button>
<br />
<button class="icon" @click="logout">
<i class="material-icons">exit_to_app</i>
<span>{{ $t("logout") }}</span>
</button>
<br />
<p v-for="setting in fb.currentSettings" :key="setting.id">
<pw-toggle
:key="setting.name"
:on="setting.value"
@change="toggleSettings(setting.name, setting.value)"
>
{{ $t(setting.name) + " " + $t("sync") }}
{{ setting.value ? $t("enabled") : $t("disabled") }}
</pw-toggle>
</p>
<p v-if="fb.currentSettings.length == 0">
<button class="" @click="writeSettings">
<i class="material-icons">sync</i>
<span>{{ $t("turn_on") + " " + $t("sync") }}</span>
</button>
</p>
</div>
<div v-else>
<label>{{ $t("login_with") }}</label>
<p>
<button class="icon" @click="signInWithGoogle">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="material-icons"
>
<path
d="M12.24 10.285V14.4h6.806c-.275 1.765-2.056 5.174-6.806 5.174-4.095 0-7.439-3.389-7.439-7.574s3.345-7.574 7.439-7.574c2.33 0 3.891.989 4.785 1.849l3.254-3.138C18.189 1.186 15.479 0 12.24 0c-6.635 0-12 5.365-12 12s5.365 12 12 12c6.926 0 11.52-4.869 11.52-11.726 0-.788-.085-1.39-.189-1.989H12.24z"
/>
</svg>
<span>Google</span>
</button>
<br />
<button class="icon" @click="signInWithGithub">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="material-icons"
>
<path
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
/>
</svg>
<span>GitHub</span>
</button>
</p>
</div>
</li>
</ul>
</pw-section>
<pw-section class="cyan" :label="$t('theme')" ref="theme"> <pw-section class="cyan" :label="$t('theme')" ref="theme">
<ul> <ul>
<li> <li>
@@ -137,6 +220,9 @@
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
<script> <script>
import firebase from "firebase/app";
import { fb } from "../functions/fb";
export default { export default {
components: { components: {
"pw-section": () => import("../components/section"), "pw-section": () => import("../components/section"),
@@ -238,7 +324,9 @@ export default {
this.$store.state.postwoman.settings.PROXY_URL || this.$store.state.postwoman.settings.PROXY_URL ||
"https://postwoman.apollotv.xyz/", "https://postwoman.apollotv.xyz/",
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || "" PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || ""
} },
fb
}; };
}, },
@@ -289,8 +377,88 @@ export default {
toggleSetting(key) { toggleSetting(key) {
this.settings[key] = !this.settings[key]; this.settings[key] = !this.settings[key];
this.$store.commit("postwoman/applySetting", [key, this.settings[key]]); this.$store.commit("postwoman/applySetting", [key, this.settings[key]]);
},
logout() {
fb.currentUser = null;
firebase
.auth()
.signOut()
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
this.$toast.info(this.$t("logged_out"), {
icon: "vpn_key"
});
},
signInWithGoogle() {
const provider = new firebase.auth.GoogleAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", false);
fb.writeSettings("syncCollections", true);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
}
});
}
})
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
},
signInWithGithub() {
const provider = new firebase.auth.GithubAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", false);
fb.writeSettings("syncCollections", true);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
}
});
}
})
.catch(err => {
this.$toast.show(err.message || err, {
icon: "error"
});
});
},
toggleSettings(s, v) {
fb.writeSettings(s, !v);
},
writeSettings() {
fb.writeSettings("syncHistory", false);
fb.writeSettings("syncCollections", true);
} }
}, },
beforeMount() { beforeMount() {
this.settings.THEME_COLOR = this.getActiveColor(); this.settings.THEME_COLOR = this.getActiveColor();
}, },

View File

@@ -111,6 +111,12 @@ export const mutations = {
}, },
addNewCollection({ collections }, collection) { addNewCollection({ collections }, collection) {
const { name } = collection
const duplicateCollection = collections.some(item => item.name === name)
if (duplicateCollection) {
this.$toast.info('Duplicate collection');
return;
}
collections.push({ collections.push({
name: "", name: "",
folders: [], folders: [],
@@ -126,6 +132,12 @@ export const mutations = {
editCollection({ collections }, payload) { editCollection({ collections }, payload) {
const { collection, collectionIndex } = payload; const { collection, collectionIndex } = payload;
const { name } = collection
const duplicateCollection = collections.some(item => item.name === name)
if (duplicateCollection) {
this.$toast.info('Duplicate collection');
return;
}
collections[collectionIndex] = collection; collections[collectionIndex] = collection;
}, },