diff --git a/components/account/AccountFollowButton.vue b/components/account/AccountFollowButton.vue
index d20becef..d8aa2512 100644
--- a/components/account/AccountFollowButton.vue
+++ b/components/account/AccountFollowButton.vue
@@ -1,5 +1,5 @@
diff --git a/components/account/AccountMe.client.vue b/components/account/AccountMe.client.vue
index 68cb7640..9bb41928 100644
--- a/components/account/AccountMe.client.vue
+++ b/components/account/AccountMe.client.vue
@@ -1,7 +1,5 @@
diff --git a/components/common/RichContent.ts b/components/common/RichContent.ts
index a91b5fd0..aacd687e 100644
--- a/components/common/RichContent.ts
+++ b/components/common/RichContent.ts
@@ -1,5 +1,3 @@
-import type { Emoji } from 'masto'
-
export default defineComponent({
props: {
content: {
@@ -8,15 +6,11 @@ export default defineComponent({
},
},
setup(props) {
- const emojis = shallowRef>({})
-
- onMounted(() => {
- const { server } = useAppCookies()
- const { serverInfos } = useClientState()
- if (server.value)
- emojis.value = serverInfos.value[server.value].customEmojis || {}
- })
-
- return () => h('div', { class: 'rich-content' }, contentToVNode(props.content, undefined, emojis.value))
+ const serverInfos = useServerInfo(currentServer.value)
+ return () => h(
+ 'div',
+ { class: 'rich-content' },
+ contentToVNode(props.content, undefined, serverInfos.value?.customEmojis),
+ )
},
})
diff --git a/components/nav/NavSide.vue b/components/nav/NavSide.vue
index 3ca6a28a..4b0b5e85 100644
--- a/components/nav/NavSide.vue
+++ b/components/nav/NavSide.vue
@@ -1,10 +1,9 @@
-
+
Home
@@ -26,7 +25,7 @@ const isLogin = useLoginState()
Federated
-
+
Conversations
diff --git a/components/publish/PublishWidget.client.vue b/components/publish/PublishWidget.client.vue
index d3410787..49b286f1 100644
--- a/components/publish/PublishWidget.client.vue
+++ b/components/publish/PublishWidget.client.vue
@@ -11,8 +11,6 @@ const {
inReplyToId?: string
}>()
-const masto = await useMasto()
-
let isSending = $ref(false)
const storageKey = `nuxtodon-draft-${draftKey}`
function getDefaultStatus(): CreateStatusParamsWithStatus {
diff --git a/components/status/StatusActions.vue b/components/status/StatusActions.vue
index c5343bb8..a041e07a 100644
--- a/components/status/StatusActions.vue
+++ b/components/status/StatusActions.vue
@@ -5,8 +5,6 @@ const { status } = defineProps<{
status: Status
}>()
-const masto = await useMasto()
-
// Use different states to let the user press different actions right after the other
const isLoading = $ref({ reblogged: false, favourited: false, bookmarked: false })
async function toggleStatusAction(action: 'reblogged' | 'favourited' | 'bookmarked', newStatus: Promise) {
diff --git a/composables/accounts.ts b/composables/accounts.ts
new file mode 100644
index 00000000..1e7736a0
--- /dev/null
+++ b/composables/accounts.ts
@@ -0,0 +1,40 @@
+import { login as loginMasto } from 'masto'
+import type { UserLogin } from '~/types'
+import { DEFAULT_SERVER } from '~/constants'
+
+const accounts = useLocalStorage('nuxtodon-accounts', [], { deep: true })
+const currentId = useLocalStorage('nuxtodon-current-user', '')
+
+export const currentUser = computed(() => {
+ let user: UserLogin | undefined
+ if (currentId.value) {
+ user = accounts.value.find(user => user.account?.id === currentId.value)
+ if (user)
+ return user
+ }
+ // Fallback to the first account
+ return accounts.value[0]
+})
+
+export const currentServer = computed(() => currentUser.value?.server || DEFAULT_SERVER)
+
+export async function loginCallback(user: UserLogin) {
+ const existing = accounts.value.findIndex(u => u.server === user.server && u.token === user.token)
+ if (existing !== -1) {
+ if (currentId.value === accounts.value[existing].account?.id)
+ return null
+ currentId.value = user.account?.id
+ return true
+ }
+
+ const masto = await loginMasto({
+ url: `https://${user.server}`,
+ accessToken: user.token,
+ })
+ const me = await masto.accounts.verifyCredentials()
+ user.account = me
+
+ accounts.value.push(user)
+ currentId.value = me.id
+ return true
+}
diff --git a/composables/client.ts b/composables/client.ts
index 3a1e2bab..be2c7ace 100644
--- a/composables/client.ts
+++ b/composables/client.ts
@@ -1,10 +1,9 @@
-import type { MastoClient } from 'masto'
-import type { ClientState } from '~/plugins/store.client'
+import { login } from 'masto'
+import { currentUser } from './accounts'
+import { DEFAULT_SERVER } from '~/constants'
-export function useMasto() {
- return useNuxtApp().$masto as Promise
-}
-
-export function useClientState() {
- return useNuxtApp().$clientState as ClientState
-}
+// TODO: improve upsteam to make this synchronous (delayed auth)
+export const masto = await login({
+ url: `https://${currentUser.value?.server || DEFAULT_SERVER}`,
+ accessToken: currentUser.value?.token || undefined,
+})
diff --git a/composables/cookies.ts b/composables/cookies.ts
deleted file mode 100644
index 80174975..00000000
--- a/composables/cookies.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { DEFAULT_SERVER } from '~/constants'
-
-export function useAppCookies() {
- const server = useCookie('nuxtodon-server', { default: () => DEFAULT_SERVER })
- const token = useCookie('nuxtodon-token')
-
- return {
- server,
- token,
- }
-}
-
-export function useLoginState() {
- const token = useCookie('nuxtodon-token')
- return computed(() => !!token.value)
-}
diff --git a/composables/masto.ts b/composables/masto.ts
index 4a7c9f48..b8fb2032 100644
--- a/composables/masto.ts
+++ b/composables/masto.ts
@@ -1,5 +1,5 @@
import type { Ref } from 'vue'
-import type { Account, MastoClient, Relationship } from 'masto'
+import type { Account, Relationship } from 'masto'
export function getDisplayName(account: Account) {
return account.displayName || account.username
@@ -28,8 +28,6 @@ export function useRelationship(account: Account): Ref
}
async function fetchRelationships() {
- const masto = await useMasto()
-
const requested = Array.from(requestedRelationships.entries())
requestedRelationships.clear()
diff --git a/composables/servers.ts b/composables/servers.ts
new file mode 100644
index 00000000..0cc3359e
--- /dev/null
+++ b/composables/servers.ts
@@ -0,0 +1,41 @@
+import type { ServerInfo } from '~/types'
+
+const ServerInfoTTL = 60 * 60 * 1000 * 12 // 12 hour
+
+const serverInfoPromise = new Map>()
+const serverInfos = useLocalStorage>('nuxtodon-server-info', {})
+
+async function _fetchServerInfo(server: string) {
+ if (!serverInfos.value[server]) {
+ // @ts-expect-error init
+ serverInfos.value[server] = {
+ timeUpdated: 0,
+ server,
+ }
+ }
+ if (serverInfos.value[server].timeUpdated + ServerInfoTTL < Date.now()) {
+ await Promise.allSettled([
+ masto.instances.fetch().then((r) => {
+ Object.assign(serverInfos.value[server], r)
+ }),
+ masto.customEmojis.fetchAll().then((r) => {
+ serverInfos.value[server].customEmojis = Object.fromEntries(r.map(i => [i.shortcode, i]))
+ }),
+ ])
+ }
+ return serverInfos.value[server]
+}
+
+export function fetchServerInfo(server: string) {
+ if (!serverInfoPromise.has(server))
+ serverInfoPromise.set(server, _fetchServerInfo(server))
+ return serverInfoPromise.get(server)!
+}
+
+export function useServerInfo(server: string) {
+ const info = ref()
+ fetchServerInfo(server).then((r) => {
+ info.value = r
+ })
+ return info
+}
diff --git a/middleware/auth.ts b/middleware/auth.ts
index 8e72eff8..23f66476 100644
--- a/middleware/auth.ts
+++ b/middleware/auth.ts
@@ -1,7 +1,5 @@
export default defineNuxtRouteMiddleware((from) => {
- const token = useCookie('nuxtodon-token')
-
- if (!token.value)
+ if (!currentUser.value)
return navigateTo('/public')
else if (from.path === '/')
return navigateTo('/home')
diff --git a/nuxt.config.ts b/nuxt.config.ts
index cda81c77..5c467d50 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,4 +1,5 @@
export default defineNuxtConfig({
+ ssr: false,
modules: [
'@vueuse/nuxt',
'@unocss/nuxt',
diff --git a/pages/@[user]/[post].vue b/pages/@[user]/[post].vue
index 6500b1a2..ae1a5674 100644
--- a/pages/@[user]/[post].vue
+++ b/pages/@[user]/[post].vue
@@ -1,10 +1,7 @@
diff --git a/pages/@[user]/followers.vue b/pages/@[user]/followers.vue
index 0310e5b1..6a7adebf 100644
--- a/pages/@[user]/followers.vue
+++ b/pages/@[user]/followers.vue
@@ -5,7 +5,7 @@ const props = defineProps<{
const params = useRoute().params
const user = $computed(() => params.user as string)
-const masto = await useMasto()
+
const { data: account } = await useAsyncData(`${user}:info`, () => masto.accounts.lookup({ acct: user }))
const paginator = masto.accounts.getFollowersIterable(account.value!.id!, {})
diff --git a/pages/@[user]/following.vue b/pages/@[user]/following.vue
index 309da352..a3369cb1 100644
--- a/pages/@[user]/following.vue
+++ b/pages/@[user]/following.vue
@@ -5,7 +5,7 @@ const props = defineProps<{
const params = useRoute().params
const user = $computed(() => params.user as string)
-const masto = await useMasto()
+
const { data: account } = await useAsyncData(`${user}:info`, () => masto.accounts.lookup({ acct: user }))
const paginator = masto.accounts.getFollowingIterable(account.value!.id!, {})
diff --git a/pages/@[user]/index.vue b/pages/@[user]/index.vue
index 1b1aa2ec..036a90d4 100644
--- a/pages/@[user]/index.vue
+++ b/pages/@[user]/index.vue
@@ -5,7 +5,7 @@ const props = defineProps<{
const params = useRoute().params
const user = $computed(() => params.user as string)
-const masto = await useMasto()
+
const { data: account } = await useAsyncData(`${user}:info`, () => masto.accounts.lookup({ acct: user }))
const tabNames = ['Posts', 'Posts and replies'] as const
diff --git a/pages/bookmarks.vue b/pages/bookmarks.vue
index fa6f6013..336ef6e1 100644
--- a/pages/bookmarks.vue
+++ b/pages/bookmarks.vue
@@ -3,7 +3,6 @@ definePageMeta({
middleware: 'auth',
})
-const masto = await useMasto()
const paginator = masto.bookmarks.getIterator()
diff --git a/pages/conversations.vue b/pages/conversations.vue
index 94bb7f38..0c2bb1c7 100644
--- a/pages/conversations.vue
+++ b/pages/conversations.vue
@@ -3,7 +3,6 @@ definePageMeta({
middleware: 'auth',
})
-const masto = await useMasto()
const paginator = masto.conversations.getIterator()
diff --git a/pages/explore.vue b/pages/explore.vue
index f4745d71..46787e33 100644
--- a/pages/explore.vue
+++ b/pages/explore.vue
@@ -3,7 +3,6 @@ definePageMeta({
middleware: 'auth',
})
-const masto = await useMasto()
const paginator = masto.trends.getStatuses()
diff --git a/pages/favourites.vue b/pages/favourites.vue
index cedd2a03..1ab38091 100644
--- a/pages/favourites.vue
+++ b/pages/favourites.vue
@@ -3,7 +3,6 @@ definePageMeta({
middleware: 'auth',
})
-const masto = await useMasto()
const paginator = masto.favourites.getIterator()
diff --git a/pages/home.vue b/pages/home.vue
index 32a96acd..bb63043f 100644
--- a/pages/home.vue
+++ b/pages/home.vue
@@ -3,7 +3,6 @@ definePageMeta({
middleware: 'auth',
})
-const masto = await useMasto()
const paginator = masto.timelines.getHomeIterable()
diff --git a/pages/login/callback.vue b/pages/login/callback.vue
index cdcf9473..5df2dda1 100644
--- a/pages/login/callback.vue
+++ b/pages/login/callback.vue
@@ -6,8 +6,7 @@ definePageMeta({
const { query } = useRoute()
onMounted(async () => {
- const { login } = useClientState()
- await login(query as any)
+ await loginCallback(query as any)
await nextTick()
await nextTick()
location.pathname = '/'
diff --git a/pages/notifications.vue b/pages/notifications.vue
index e4062d4d..f205c5cb 100644
--- a/pages/notifications.vue
+++ b/pages/notifications.vue
@@ -3,8 +3,6 @@ definePageMeta({
middleware: 'auth',
})
-const masto = await useMasto()
-
const tabNames = ['All', 'Mentions'] as const
const tab = $(useLocalStorage('nuxtodon-notifications-tab', 'All'))
diff --git a/pages/public/index.vue b/pages/public/index.vue
index afa9a0a2..c41566f5 100644
--- a/pages/public/index.vue
+++ b/pages/public/index.vue
@@ -1,5 +1,5 @@
diff --git a/pages/public/local.vue b/pages/public/local.vue
index e87ff527..08459eed 100644
--- a/pages/public/local.vue
+++ b/pages/public/local.vue
@@ -4,7 +4,7 @@ const router = useRouter()
if (!token.value)
router.replace('/public')
-const masto = await useMasto()
+
const { data: timelines } = await useAsyncData('timelines-home', () => masto.timelines.fetchPublic({ local: true }).then(r => r.value))
diff --git a/pages/tags/[tag].vue b/pages/tags/[tag].vue
index dc662249..611fa13d 100644
--- a/pages/tags/[tag].vue
+++ b/pages/tags/[tag].vue
@@ -1,7 +1,7 @@
diff --git a/plugins/masto.ts b/plugins/masto.ts
deleted file mode 100644
index 4e4eae82..00000000
--- a/plugins/masto.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { login } from 'masto'
-
-export default defineNuxtPlugin((nuxt) => {
- const { server, token } = useAppCookies()
-
- const masto = login({
- url: `https://${server.value}`,
- accessToken: token.value || undefined,
- })
-
- nuxt.$masto = masto
-})
diff --git a/plugins/store.client.ts b/plugins/store.client.ts
deleted file mode 100644
index fa4a13ac..00000000
--- a/plugins/store.client.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { login as loginMasto } from 'masto'
-import type { ServerInfo, UserLogin } from '~/types'
-
-const ServerInfoTTL = 60 * 60 * 1000 * 12 // 12 hour
-
-function createClientState() {
- const { server, token } = useAppCookies()
-
- const accounts = useLocalStorage('nuxtodon-accounts', [], { deep: true })
- const currentId = useLocalStorage('nuxtodon-current-user', '')
-
- const currentUser = computed(() => {
- let user: UserLogin | undefined
- if (currentId.value) {
- user = accounts.value.find(user => user.account?.id === currentId.value)
- if (user)
- return user
- }
- // Fallback to the first account
- return accounts.value[0]
- })
-
- async function login(user: UserLogin) {
- const existing = accounts.value.findIndex(u => u.server === user.server && u.token === user.token)
- if (existing !== -1) {
- if (currentId.value === accounts.value[existing].account?.id)
- return null
- currentId.value = user.account?.id
- server.value = user.server
- token.value = user.token
- return true
- }
-
- const masto = await loginMasto({
- url: `https://${user.server}`,
- accessToken: user.token,
- })
- const me = await masto.accounts.verifyCredentials()
- user.account = me
-
- accounts.value.push(user)
- currentId.value = me.id
- server.value = user.server
- token.value = user.token
- return true
- }
-
- const serverInfos = useLocalStorage>('nuxtodon-server-info', {})
-
- async function fetchServerInfo(server: string) {
- if (!serverInfos.value[server]) {
- // @ts-expect-error init
- serverInfos.value[server] = {
- timeUpdated: 0,
- server,
- }
- }
- if (serverInfos.value[server].timeUpdated + ServerInfoTTL < Date.now()) {
- const masto = await useMasto()
- await Promise.allSettled([
- masto.instances.fetch().then((r) => {
- Object.assign(serverInfos.value[server], r)
- }),
- masto.customEmojis.fetchAll().then((r) => {
- serverInfos.value[server].customEmojis = Object.fromEntries(r.map(i => [i.shortcode, i]))
- }),
- ])
- }
- return serverInfos.value[server]
- }
-
- if (server.value)
- fetchServerInfo(server.value)
-
- return {
- currentUser,
- accounts,
- login,
- serverInfos,
- fetchServerInfo,
- }
-}
-
-export type ClientState = ReturnType
-
-export default defineNuxtPlugin((nuxt) => {
- nuxt.$clientState = createClientState()
-})