feat: clean up cookies implementation

This commit is contained in:
Andrew Bastin
2023-10-25 22:28:41 +05:30
parent 7fff08c142
commit e2b15cedd4
5 changed files with 196 additions and 25 deletions

View File

@@ -59,13 +59,16 @@
"modal": {
"new_domain_name": "New domain name",
"set": "Set a cookie",
"cookie_string": "Cookie string",
"cookie_string": "Set-Cookie string",
"cookie_name": "Name",
"cookie_value": "Value",
"cookie_path": "Path",
"cookie_expires": "Expires",
"managed_tab": "Managed",
"raw_tab": "Raw"
"raw_tab": "Raw",
"interceptor_no_support": "Your currently selected interceptor does not support cookies.",
"no_domains": "No domains defined",
"no_cookies_in_domain": "No cookies set for this domain"
}
},
"app": {

View File

@@ -7,6 +7,14 @@
@close="hideModal"
>
<template #body>
<div
v-if="!currentInterceptorSupportsCookies"
class="flex flex-col gap-2 p-5 items-center"
>
<icon-lucide-info />
{{ t("cookies.modal.interceptor_no_support") }}
</div>
<div class="flex gap-x-2 border-b border-dividerLight pb-3">
<HoppSmartInput
v-model="newDomainText"
@@ -22,6 +30,14 @@
</div>
<div class="pt-3 flex flex-col gap-y-6">
<div
v-if="workingCookieJar.size === 0"
class="flex flex-col items-center p-5 gap-2"
>
<icon-lucide-info />
{{ t("cookies.modal.no_domains") }}
</div>
<div
v-else
v-for="[domain, entries] in workingCookieJar.entries()"
:key="domain"
class="flex flex-col gap-y-2"
@@ -43,6 +59,14 @@
<div class="border rounded border-divider">
<div class="divide-y divide-dividerLight">
<div
v-if="entries.length === 0"
class="flex flex-col items-center p-5 gap-2"
>
<icon-lucide-info />
{{ t("cookies.modal.no_cookies_in_domain") }}
</div>
<div
v-else
v-for="(entry, entryIndex) in entries"
:key="`${entry}-${entryIndex}`"
class="flex divide-x divide-dividerLight"
@@ -93,7 +117,7 @@
<CookiesEditCookie
:show="!!showEditModalFor"
:entry="showEditModalFor"
@save-cookie="saveCookieUpdate"
@save-cookie="saveCookie"
@hide-modal="showEditModalFor = null"
/>
</template>
@@ -107,7 +131,9 @@ import IconEdit from "~icons/lucide/edit"
import IconTrash2 from "~icons/lucide/trash-2"
import IconPlus from "~icons/lucide/plus"
import { cloneDeep } from "lodash-es"
import { ref, watch } from "vue"
import { ref, watch, computed } from "vue"
import { InterceptorService } from "~/services/interceptor.service"
import { EditCookieConfig } from "./EditCookie.vue"
const props = defineProps<{
show: boolean
@@ -121,10 +147,19 @@ const t = useI18n()
const newDomainText = ref("")
const interceptorService = useService(InterceptorService)
const cookieJarService = useService(CookieJarService)
const workingCookieJar = ref(cloneDeep(cookieJarService.cookieJar.value))
const currentInterceptorSupportsCookies = computed(() => {
const currentInterceptor = interceptorService.currentInterceptor.value
if (!currentInterceptor) return true
return currentInterceptor.supportsCookies ?? false
})
function addNewDomain() {
workingCookieJar.value.set(newDomainText.value, [])
newDomainText.value = ""
@@ -135,11 +170,7 @@ function deleteDomain(domain: string) {
}
function addCookieToDomain(domain: string) {
const entry = workingCookieJar.value.get(domain)
if (entry) {
entry.push("")
}
showEditModalFor.value = { type: "create", domain }
}
watch(
@@ -151,8 +182,7 @@ watch(
}
)
// Tuple of [domain, entryIndex]
const showEditModalFor = ref<[string, number, string] | null>(null)
const showEditModalFor = ref<EditCookieConfig | null>(null)
function saveCookieChanges() {
cookieJarService.cookieJar.value = workingCookieJar.value
@@ -164,7 +194,12 @@ function cancelCookieChanges() {
}
function editCookie(domain: string, entryIndex: number, cookieEntry: string) {
showEditModalFor.value = [domain, entryIndex, cookieEntry]
showEditModalFor.value = {
type: "edit",
domain,
entryIndex,
currentCookieEntry: cookieEntry,
}
}
function deleteCookie(domain: string, entryIndex: number) {
@@ -175,10 +210,21 @@ function deleteCookie(domain: string, entryIndex: number) {
}
}
function saveCookieUpdate(cookie: string) {
if (!showEditModalFor.value) return
function saveCookie(cookie: string) {
if (showEditModalFor.value?.type === "create") {
const { domain } = showEditModalFor.value
const [domain, entryIndex] = showEditModalFor.value!
const entry = workingCookieJar.value.get(domain)!
entry.push(cookie)
showEditModalFor.value = null
return
}
if (showEditModalFor.value?.type !== "edit") return
const { domain, entryIndex } = showEditModalFor.value!
const entry = workingCookieJar.value.get(domain)

View File

@@ -7,10 +7,12 @@
@close="hideModal"
>
<template #body>
<HoppSmartInput
v-model="rawCookieString"
:placeholder="t('cookies.modal.cookie_string')"
/>
<div class="h-46 border rounded border-dividerLight">
<div
ref="cookieEditor"
class="h-full border-t rounded-b border-dividerLight"
></div>
</div>
</template>
<template #footer>
@@ -28,21 +30,46 @@
@click="cancelCookieChange"
/>
</div>
<span class="flex">
<HoppButtonSecondary
:icon="pasteIcon"
:label="`${t('action.paste')}`"
filled
outline
@click="handlePaste"
class="self-end"
/>
</span>
</template>
</HoppSmartModal>
</template>
<script lang="ts">
export type EditCookieConfig =
| { type: "create"; domain: string }
| {
type: "edit"
domain: string
entryIndex: number
currentCookieEntry: string
}
</script>
<script setup lang="ts">
import { useI18n } from "@composables/i18n"
import { useCodemirror } from "~/composables/codemirror"
import { watch, ref } from "vue"
import { refAutoReset } from "@vueuse/core"
import IconClipboard from "~icons/lucide/clipboard"
import IconCheck from "~icons/lucide/check"
import { useToast } from "~/composables/toast"
// TODO: Build Managed Mode!
const props = defineProps<{
show: boolean
// Tuple of [domain, entryIndex, cookieEntry]
entry: [string, number, string] | null
entry: EditCookieConfig | null
}>()
const emit = defineEmits<{
@@ -52,16 +79,33 @@ const emit = defineEmits<{
const t = useI18n()
const toast = useToast()
const cookieEditor = ref<HTMLElement>()
const rawCookieString = ref("")
useCodemirror(cookieEditor, rawCookieString, {
extendedEditorConfig: {
mode: "text/plain",
placeholder: `${t("cookies.modal.cookie_string")}`,
lineWrapping: true,
},
linter: null,
completer: null,
environmentHighlights: false,
})
const pasteIcon = refAutoReset<typeof IconClipboard | typeof IconCheck>(
IconClipboard,
1000
)
watch(
() => props.entry,
() => {
if (!props.entry) return
if (props.entry?.type !== "edit") return
const cookieEntry = props.entry[2]
rawCookieString.value = cookieEntry
rawCookieString.value = props.entry.currentCookieEntry
}
)
@@ -73,6 +117,19 @@ function cancelCookieChange() {
hideModal()
}
async function handlePaste() {
try {
const text = await navigator.clipboard.readText()
if (text) {
rawCookieString.value = text
pasteIcon.value = IconCheck
}
} catch (e) {
console.error("Failed to copy: ", e)
toast.error(t("profile.no_permission").toString())
}
}
function saveCookieChange() {
emit("save-cookie", rawCookieString.value)
}

View File

@@ -72,6 +72,61 @@ describe("InterceptorService", () => {
expect(service.currentInterceptorID.value).not.toEqual("unknown")
})
it("currentInterceptor points to the instance of the currently selected interceptor", () => {
const container = new TestContainer()
const service = container.bind(InterceptorService)
const interceptor = {
interceptorID: "test",
name: () => "test interceptor",
selectable: { type: "selectable" as const },
runRequest: () => {
throw new Error("not implemented")
},
}
service.registerInterceptor(interceptor)
service.currentInterceptorID.value = "test"
expect(service.currentInterceptor.value).toBe(interceptor)
})
it("currentInterceptor updates when the currentInterceptorID changes", () => {
const container = new TestContainer()
const service = container.bind(InterceptorService)
const interceptor = {
interceptorID: "test",
name: () => "test interceptor",
selectable: { type: "selectable" as const },
runRequest: () => {
throw new Error("not implemented")
},
}
const interceptor_2 = {
interceptorID: "test2",
name: () => "test interceptor",
selectable: { type: "selectable" as const },
runRequest: () => {
throw new Error("not implemented")
},
}
service.registerInterceptor(interceptor)
service.registerInterceptor(interceptor_2)
service.currentInterceptorID.value = "test"
expect(service.currentInterceptor.value).toBe(interceptor)
service.currentInterceptorID.value = "test2"
expect(service.currentInterceptor.value).not.toBe(interceptor)
expect(service.currentInterceptor.value).toBe(interceptor_2)
})
describe("registerInterceptor", () => {
it("should register the interceptor", () => {
const container = new TestContainer()

View File

@@ -167,6 +167,16 @@ export class InterceptorService extends Service {
Array.from(this.interceptors.values())
)
/**
* Gives an instance to the current interceptor.
* NOTE: Do not update from here, this is only for reading.
*/
public currentInterceptor = computed(() => {
if (this.currentInterceptorID.value === null) return null
return this.interceptors.get(this.currentInterceptorID.value)
})
constructor() {
super()