Skip to content

Commit 9e35b23

Browse files
authored
Merge pull request #2 from arabcoders/dev
[FEAT] shorter payload URLs
2 parents 87824ee + ecc5f02 commit 9e35b23

44 files changed

Lines changed: 3029 additions & 1298 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"sqlmap",
3333
"srem",
3434
"unixepoch",
35+
"unref",
3536
"Whois"
3637
]
3738
}

app/components/Header.vue

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<code
1818
class="hidden select-none cursor-pointer sm:inline-block rounded bg-gray-100 dark:bg-gray-800 px-2 py-0.5 text-xs font-mono text-gray-900 dark:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1919
@click="copyPayloadUrl">
20-
/api/payload/{{ shortSlug(selectedToken) }}
20+
/api/payload/{{ friendlyId || shortSlug(selectedToken) }}
2121
</code>
2222
</UTooltip>
2323
</div>
@@ -68,6 +68,12 @@
6868
<div v-if="showMobileExtras && hasMobileExtras" id="header-mobile-extras"
6969
class="mt-3 grid gap-3 rounded-lg border border-gray-200 dark:border-gray-800 bg-white/90 dark:bg-gray-900/90 p-3 shadow-sm md:hidden">
7070
<ClientOnly>
71+
<code v-if="selectedToken"
72+
class="select-none cursor-pointer sm:inline-block rounded bg-gray-100 dark:bg-gray-800 px-2 py-0.5 text-xs font-mono text-gray-900 dark:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
73+
@click="copyPayloadUrl">
74+
/api/payload/{{ friendlyId || shortSlug(selectedToken) }}
75+
</code>
76+
7177
<UButton v-if="sessionInfo && sessionRestoreEnabled" color="neutral" variant="soft" size="sm"
7278
icon="i-lucide-user" @click="copySessionId">
7379
{{ sessionInfo.friendlyId }}
@@ -95,7 +101,7 @@
95101
<script setup lang="ts">
96102
import { computed, watch, onMounted, onUnmounted, ref } from 'vue'
97103
import { useRoute } from 'vue-router'
98-
import { useTokens } from '~/composables/useTokens'
104+
import { useTokensStore } from '~/stores/tokens'
99105
import { useSSE } from '~/composables/useSSE'
100106
import type { SSEEventPayload } from '~~/shared/types'
101107
import { notify } from '~/composables/useNotificationBridge'
@@ -105,22 +111,38 @@ const route = useRoute()
105111
const colorMode = useColorMode()
106112
const runtimeConfig = useRuntimeConfig()
107113
108-
const { tokens, loadTokens, deleteToken: removeToken } = useTokens()
114+
const tokensStore = useTokensStore()
115+
const { data: tokens, refetch: refetchTokens } = tokensStore.useTokensList()
116+
const { mutateAsync: deleteToken } = tokensStore.useDeleteToken()
117+
109118
const sse = useSSE()
110119
111120
const sessionRestoreEnabled = runtimeConfig.public?.sessionRestoreEnabled !== false
112121
113122
const selectedToken = ref<string>('')
123+
const friendlyId = ref<string>('')
114124
const isDeleting = ref(false)
115125
const showDeleteModal = ref(false)
116126
const showRestoreModal = ref(false)
117127
const sessionInfo = ref<{ friendlyId: string } | null>(null)
118128
const authRequired = ref(false)
119129
const showMobileExtras = ref(false)
120130
131+
// Query for the currently selected token to get friendlyId
132+
const { data: currentTokenData } = tokensStore.useToken(selectedToken)
133+
134+
// Watch for changes in token data to update friendlyId
135+
watch(currentTokenData, (tokenData) => {
136+
if (tokenData) {
137+
friendlyId.value = tokenData.friendlyId || ''
138+
} else {
139+
friendlyId.value = ''
140+
}
141+
})
142+
121143
const checkAuthStatus = async () => {
122144
try {
123-
const response = await $fetch('/api/auth/status')
145+
const response = await $fetch<{ required: boolean }>('/api/auth/status')
124146
authRequired.value = response.required
125147
} catch {
126148
authRequired.value = false
@@ -135,7 +157,7 @@ watch(selectedToken, newVal => {
135157
136158
const loadSessionInfo = async () => {
137159
try {
138-
const data = await $fetch('/api/session')
160+
const data = await $fetch<{ friendlyId: string }>('/api/session')
139161
if (data?.friendlyId) {
140162
sessionInfo.value = { friendlyId: data.friendlyId }
141163
}
@@ -165,12 +187,12 @@ const copySessionId = async () => {
165187
}
166188
167189
const copyPayloadUrl = async () => {
168-
if (!selectedToken.value) {
190+
if (!selectedToken.value && !friendlyId.value) {
169191
return
170192
}
171193
172194
const origin = 'undefined' !== typeof window ? window.location.origin : ''
173-
const url = `${origin}/api/payload/${selectedToken.value}`
195+
const url = `${origin}/api/payload/${friendlyId.value || selectedToken.value}`
174196
175197
try {
176198
if (false === (await copyText(url))) {
@@ -203,12 +225,13 @@ const tokenOptions = computed(() => {
203225
})
204226
})
205227
206-
watch(() => route.path, (path) => {
228+
watch(() => route.path, async (path) => {
207229
const match = path.match(/\/token\/(.+)/)
208230
if (match && match[1]) {
209231
selectedToken.value = match[1]
210232
} else {
211233
selectedToken.value = ''
234+
friendlyId.value = ''
212235
}
213236
showMobileExtras.value = false
214237
}, { immediate: true })
@@ -228,13 +251,12 @@ function handleClientEvent(payload: SSEEventPayload) {
228251
return
229252
}
230253
231-
loadTokens()
254+
refetchTokens()
232255
}
233256
234257
let unsubscribe: (() => void) | null = null
235258
236259
onMounted(async () => {
237-
await loadTokens()
238260
await loadSessionInfo()
239261
await checkAuthStatus()
240262
unsubscribe = sse.onAny(handleClientEvent)
@@ -257,7 +279,7 @@ const toggleMobileExtras = () => {
257279
258280
const handleLogout = async () => {
259281
try {
260-
await $fetch('/api/auth/logout', { method: 'POST' })
282+
await $fetch<{ ok: boolean }>('/api/auth/logout', { method: 'POST' })
261283
notify({
262284
title: 'Logged out',
263285
description: 'You have been logged out successfully.',
@@ -296,7 +318,7 @@ const confirmDelete = async () => {
296318
const activeIndex = currentTokens.findIndex((t: { id: string }) => t.id === selectedToken.value)
297319
const fallback = activeIndex !== -1 ? currentTokens[activeIndex + 1] ?? currentTokens[activeIndex - 1] : undefined
298320
299-
await removeToken(selectedToken.value)
321+
await deleteToken(selectedToken.value)
300322
301323
if (fallback) {
302324
await navigateTo(`/token/${fallback.id}`)

app/components/IngestRequestModal.vue

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Content-Type: application/json
104104

105105
<script setup lang="ts">
106106
import { ref, computed, watch } from 'vue'
107+
import { useRequestsStore } from '~/stores/requests'
107108
108109
const props = withDefaults(defineProps<{
109110
modelValue: boolean
@@ -115,6 +116,9 @@ const emit = defineEmits<{
115116
(e: 'success'): void
116117
}>()
117118
119+
const requestsStore = useRequestsStore()
120+
const { mutateAsync: ingestRequest, isPending: loading } = requestsStore.useIngestRequest()
121+
118122
const isOpen = computed({
119123
get: () => props.modelValue,
120124
set: (value) => emit('update:modelValue', value)
@@ -123,7 +127,6 @@ const isOpen = computed({
123127
const rawRequest = ref('')
124128
const clientIp = ref('')
125129
const remoteIp = ref('')
126-
const loading = ref(false)
127130
const error = ref<string | null>(null)
128131
const success = ref<{ id: string } | null>(null)
129132
const errors = ref<{
@@ -182,7 +185,6 @@ const handleIngest = async () => {
182185
return
183186
}
184187
185-
loading.value = true
186188
error.value = null
187189
success.value = null
188190
@@ -206,21 +208,8 @@ const handleIngest = async () => {
206208
body.remoteIp = remoteIp.value
207209
}
208210
209-
const res = await fetch(`/api/token/${props.tokenId}/ingest`, {
210-
method: 'POST',
211-
headers: {
212-
'Content-Type': 'application/json',
213-
},
214-
body: JSON.stringify(body),
215-
})
216-
217-
if (!res.ok) {
218-
const data = await res.json().catch(() => ({}))
219-
throw new Error(data.message || `HTTP ${res.status}: ${res.statusText}`)
220-
}
221-
222-
const data = await res.json()
223-
success.value = { id: data.request.id }
211+
const data = await ingestRequest({ tokenId: props.tokenId, body })
212+
success.value = { id: data?.request?.id || '' }
224213
225214
emit('success')
226215
@@ -229,8 +218,6 @@ const handleIngest = async () => {
229218
} catch (err) {
230219
error.value = err instanceof Error ? err.message : 'Failed to ingest request'
231220
console.error('Failed to ingest request:', err)
232-
} finally {
233-
loading.value = false
234221
}
235222
}
236223

app/components/NotificationToggle.vue

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
<template>
22
<ClientOnly>
3-
<UTooltip :text="tooltipText" :shortcuts="[]">
4-
<UButton :icon="currentIcon" :color="notificationType === 'browser' ? 'primary' : 'neutral'" variant="ghost"
5-
:aria-label="ariaLabel" :disabled="!isBrowserNotificationSupported && notificationType === 'toast'"
6-
@click="handleToggle" />
7-
</UTooltip>
3+
<div class="flex items-center gap-2">
4+
<UTooltip :text="muteTooltipText" :shortcuts="[]">
5+
<UButton :icon="muteIcon" :color="isMuted ? 'error' : 'neutral'" variant="ghost"
6+
:aria-label="muteAriaLabel" @click="handleMuteToggle" />
7+
</UTooltip>
8+
<UTooltip :text="tooltipText" :shortcuts="[]">
9+
<UButton :icon="currentIcon" :color="notificationType === 'browser' ? 'primary' : 'neutral'"
10+
variant="ghost" :aria-label="ariaLabel"
11+
:disabled="!isBrowserNotificationSupported && notificationType === 'toast'"
12+
@click="handleToggle" />
13+
</UTooltip>
14+
</div>
815
</ClientOnly>
916
</template>
1017

@@ -16,10 +23,18 @@ const {
1623
notificationType,
1724
toggleNotificationType,
1825
isBrowserNotificationSupported,
19-
browserPermission
26+
browserPermission,
27+
isMuted,
28+
toggleMute
2029
} = useNotificationBridge()
2130
22-
const currentIcon = computed(() => 'browser' === notificationType.value ? 'i-lucide-bell' : 'i-lucide-bell-off')
31+
const currentIcon = computed(() => 'browser' === notificationType.value ? 'i-lucide-monitor' : 'i-lucide-message-square')
32+
33+
const muteIcon = computed(() => isMuted.value ? 'i-lucide-bell-off' : 'i-lucide-bell-ring')
34+
35+
const muteTooltipText = computed(() => isMuted.value ? 'Notifications muted (click to unmute)' : 'Notifications active (click to mute)')
36+
37+
const muteAriaLabel = computed(() => isMuted.value ? 'Unmute notifications' : 'Mute notifications')
2338
2439
const tooltipText = computed(() => {
2540
if (!isBrowserNotificationSupported.value) {
@@ -40,4 +55,6 @@ const tooltipText = computed(() => {
4055
const ariaLabel = computed(() => 'browser' === notificationType.value ? 'Switch to toast notifications' : 'Switch to browser notifications')
4156
4257
const handleToggle = async () => await toggleNotificationType()
58+
59+
const handleMuteToggle = () => toggleMute()
4360
</script>

app/components/RestoreSessionModal.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<p class="text-sm text-gray-600 dark:text-gray-400">
1313
Enter your session ID to restore your tokens and requests.
1414
</p>
15-
15+
1616
<div class="space-y-2">
1717
<label for="session-id" class="block font-bold text-sm text-gray-700 dark:text-gray-300">
1818
Session ID
@@ -58,7 +58,7 @@ const restore = async () => {
5858
loading.value = true
5959
6060
try {
61-
const response = await $fetch('/api/session/restore', {
61+
const response = await $fetch<{ success: boolean }>('/api/session/restore', {
6262
method: 'POST',
6363
body: { sessionId: sessionId.value.trim() },
6464
})

app/components/TokenSidebar.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@
3737
<div class="flex-1 min-w-0 flex items-center gap-2">
3838
<ULink :to="`/token/${token.id}`"
3939
class="font-mono text-sm text-primary hover:underline block truncate">
40-
{{ shortSlug(token.id) }}
40+
{{ token.friendlyId ? token.friendlyId : shortSlug(token.id) }}
4141
</ULink>
42-
<UBadge v-if="incomingTokenIds && incomingTokenIds.has(token.id)" color="success" variant="solid" size="xs"
43-
class="font-semibold uppercase">
42+
<UBadge v-if="incomingTokenIds && incomingTokenIds.has(token.id)" color="success"
43+
variant="solid" size="xs" class="font-semibold uppercase">
4444
New
4545
</UBadge>
4646
</div>
@@ -55,7 +55,8 @@
5555
</UTooltip>
5656
</div>
5757
</div>
58-
<div class="flex items-center justify-between gap-3 text-xs text-gray-500 dark:text-gray-400 select-none">
58+
<div
59+
class="flex items-center justify-between gap-3 text-xs text-gray-500 dark:text-gray-400 select-none">
5960
<span>{{ getRequestCount(token) }} requests</span>
6061
<span>{{ formatDate(token.createdAt) }}</span>
6162
</div>

app/components/token/RawRequestCard.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</UTooltip>
1818

1919
<UTooltip v-if="request" text="Download raw request">
20-
<ULink type="button" variant="ghost" color="neutral" size="xs" role="button"
20+
<ULink :external="true" type="button" variant="ghost" color="neutral" size="xs" role="button"
2121
:href="`/api/token/${tokenId}/requests/${request?.id}/raw`" target="_blank">
2222
<UIcon name="i-lucide-download" size="xs" class="h-4 w-4" />
2323
</ULink>

app/components/token/RequestDetailsCard.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@
167167
<div class="flex items-center gap-1">
168168
<div class="flex justify-end">
169169
<UTooltip text="Download body">
170-
<ULink role="button" variant="ghost" color="neutral" size="xs" target="_blank"
170+
<ULink :external="true" role="button" variant="ghost" color="neutral" size="xs" target="_blank"
171171
:href="`/api/token/${tokenId}/requests/${request.id}/body/download`">
172172
<UIcon name="i-lucide-download" size="xs" class="h-4 w-4" />
173173
</ULink>
@@ -306,7 +306,6 @@ const isBinary = computed(() => Boolean(props.request?.isBinary))
306306
307307
watch([() => props.request?.id, isBodyOpen], async ([newId, isOpen]: [string | undefined, boolean]) => {
308308
if (!newId || !isOpen) {
309-
console.log('Not loading body - no request or not open')
310309
return
311310
}
312311

0 commit comments

Comments
 (0)