fix: atproto oauth dev redirect, tab isolation, and concurrent load guard
- Use buildLoopbackClientId(window.location) for dev to include port in redirect URI - Bind Vite dev server to 127.0.0.1 explicitly - Remove scope override in signInRedirect (use metadata default) - Clear OAuth callback params from URL after session restore - Replace follows badge with DaisyUI tabs (All / Following) - Use separate PublicNoteList instances per tab to isolate v-infinite-scroll state - Add isLoading guard in onLoadMore to prevent concurrent fetches
This commit is contained in:
@@ -19,6 +19,8 @@ const initializeAuth = async () => {
|
|||||||
did.value = session.did
|
did.value = session.did
|
||||||
handle.value = resolvedHandle
|
handle.value = resolvedHandle
|
||||||
await saveSession(session.did, resolvedHandle)
|
await saveSession(session.did, resolvedHandle)
|
||||||
|
|
||||||
|
window.history.replaceState(null, '', window.location.pathname)
|
||||||
} else {
|
} else {
|
||||||
const stored = await loadSession()
|
const stored = await loadSession()
|
||||||
did.value = stored?.did ?? ''
|
did.value = stored?.did ?? ''
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export function usePublicNoteList(options?: UsePublicNoteListOptions) {
|
|||||||
const canLoadMore = computed(() => cursor.value !== undefined)
|
const canLoadMore = computed(() => cursor.value !== undefined)
|
||||||
|
|
||||||
const onLoadMore = async () => {
|
const onLoadMore = async () => {
|
||||||
|
if (isLoading.value) return
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
const path = options?.did?.value ? `/${options.did.value}/notes` : "/notes"
|
const path = options?.did?.value ? `/${options.did.value}/notes` : "/notes"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { BrowserOAuthClient } from '@atproto/oauth-client-browser'
|
import { BrowserOAuthClient, buildLoopbackClientId } from '@atproto/oauth-client-browser'
|
||||||
|
|
||||||
const CLIENT_ID = import.meta.env.DEV
|
const getClientId = () =>
|
||||||
? 'http://localhost'
|
import.meta.env.DEV
|
||||||
|
? buildLoopbackClientId(new URL(window.location.origin))
|
||||||
: 'https://remanso.space/client-metadata.json'
|
: 'https://remanso.space/client-metadata.json'
|
||||||
|
|
||||||
let clientPromise: Promise<BrowserOAuthClient> | null = null
|
let clientPromise: Promise<BrowserOAuthClient> | null = null
|
||||||
@@ -9,7 +10,7 @@ let clientPromise: Promise<BrowserOAuthClient> | null = null
|
|||||||
export const getOAuthClient = (): Promise<BrowserOAuthClient> => {
|
export const getOAuthClient = (): Promise<BrowserOAuthClient> => {
|
||||||
if (!clientPromise) {
|
if (!clientPromise) {
|
||||||
clientPromise = BrowserOAuthClient.load({
|
clientPromise = BrowserOAuthClient.load({
|
||||||
clientId: CLIENT_ID,
|
clientId: getClientId(),
|
||||||
handleResolver: 'https://bsky.social',
|
handleResolver: 'https://bsky.social',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -18,7 +19,7 @@ export const getOAuthClient = (): Promise<BrowserOAuthClient> => {
|
|||||||
|
|
||||||
export const signInWithHandle = async (handle: string): Promise<void> => {
|
export const signInWithHandle = async (handle: string): Promise<void> => {
|
||||||
const client = await getOAuthClient()
|
const client = await getOAuthClient()
|
||||||
await client.signInRedirect(handle, { scope: 'atproto transition:generic' })
|
await client.signInRedirect(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const restoreSession = async () => {
|
export const restoreSession = async () => {
|
||||||
|
|||||||
@@ -5,11 +5,15 @@ import SignInAtproto from "@/components/SignInAtproto.vue"
|
|||||||
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
||||||
import { useFollows } from "@/hooks/useFollows.hook"
|
import { useFollows } from "@/hooks/useFollows.hook"
|
||||||
import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook"
|
import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook"
|
||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
const { did, isLoggedIn } = useATProtoLogin()
|
const { did, isLoggedIn } = useATProtoLogin()
|
||||||
const { follows } = useFollows(did)
|
const { follows } = useFollows(did)
|
||||||
const { notes, isLoading, canLoadMore, onLoadMore, getAuthor } =
|
|
||||||
usePublicNoteList({ followsFilter: follows })
|
const tab = ref<'all' | 'following'>('all')
|
||||||
|
|
||||||
|
const all = usePublicNoteList()
|
||||||
|
const following = usePublicNoteList({ followsFilter: follows })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -19,28 +23,48 @@ const { notes, isLoading, canLoadMore, onLoadMore, getAuthor } =
|
|||||||
<h1>Remanso notes</h1>
|
<h1>Remanso notes</h1>
|
||||||
<sign-in-atproto />
|
<sign-in-atproto />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isLoggedIn && follows.size > 0" class="follows-badge">
|
|
||||||
Showing follows only
|
<div v-if="isLoggedIn" role="tablist" class="tabs tabs-border">
|
||||||
|
<a role="tab" class="tab" :class="{ 'tab-active': tab === 'all' }" @click="tab = 'all'">All</a>
|
||||||
|
<a role="tab" class="tab" :class="{ 'tab-active': tab === 'following' }" @click="tab = 'following'">Following</a>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isLoading"></div>
|
|
||||||
<div v-else>
|
|
||||||
<PublicNoteList
|
<PublicNoteList
|
||||||
:notes="notes"
|
v-if="tab === 'all'"
|
||||||
:can-load-more="canLoadMore"
|
:notes="all.notes.value"
|
||||||
:on-load-more="onLoadMore"
|
:can-load-more="all.canLoadMore.value"
|
||||||
|
:on-load-more="all.onLoadMore"
|
||||||
>
|
>
|
||||||
<template #meta="{ note }">
|
<template #meta="{ note }">
|
||||||
<router-link
|
<router-link
|
||||||
v-if="getAuthor(note.did)"
|
v-if="all.getAuthor(note.did)"
|
||||||
:to="{
|
:to="{ name: 'PublicNoteListByDidView', params: { did: note.did } }"
|
||||||
name: 'PublicNoteListByDidView',
|
|
||||||
params: { did: note.did },
|
|
||||||
}"
|
|
||||||
class="link link-hover"
|
class="link link-hover"
|
||||||
>
|
>
|
||||||
{{ getAuthor(note.did) }}
|
{{ all.getAuthor(note.did) }}
|
||||||
|
</router-link>
|
||||||
|
<template v-if="note.publishedAt">
|
||||||
|
<span> • </span>
|
||||||
|
<span>{{ new Date(note.publishedAt).toLocaleDateString() }}</span>
|
||||||
|
</template>
|
||||||
|
<div v-else class="skeleton h-4 w-20"></div>
|
||||||
|
</template>
|
||||||
|
</PublicNoteList>
|
||||||
|
|
||||||
|
<PublicNoteList
|
||||||
|
v-else
|
||||||
|
:notes="following.notes.value"
|
||||||
|
:can-load-more="following.canLoadMore.value"
|
||||||
|
:on-load-more="following.onLoadMore"
|
||||||
|
>
|
||||||
|
<template #meta="{ note }">
|
||||||
|
<router-link
|
||||||
|
v-if="following.getAuthor(note.did)"
|
||||||
|
:to="{ name: 'PublicNoteListByDidView', params: { did: note.did } }"
|
||||||
|
class="link link-hover"
|
||||||
|
>
|
||||||
|
{{ following.getAuthor(note.did) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<template v-if="note.publishedAt">
|
<template v-if="note.publishedAt">
|
||||||
<span> • </span>
|
<span> • </span>
|
||||||
<span>{{ new Date(note.publishedAt).toLocaleDateString() }}</span>
|
<span>{{ new Date(note.publishedAt).toLocaleDateString() }}</span>
|
||||||
@@ -48,7 +72,6 @@ const { notes, isLoading, canLoadMore, onLoadMore, getAuthor } =
|
|||||||
<div v-else class="skeleton h-4 w-20"></div>
|
<div v-else class="skeleton h-4 w-20"></div>
|
||||||
</template>
|
</template>
|
||||||
</PublicNoteList>
|
</PublicNoteList>
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -83,11 +106,4 @@ const { notes, isLoading, canLoadMore, onLoadMore, getAuthor } =
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.follows-badge {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ export default defineConfig(({ command }) => {
|
|||||||
config.define = {
|
config.define = {
|
||||||
global: {},
|
global: {},
|
||||||
}
|
}
|
||||||
|
config.server = {
|
||||||
|
host: '127.0.0.1',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|||||||
Reference in New Issue
Block a user