Skip to content

Commit

Permalink
fix: change page reload and account switch logic
Browse files Browse the repository at this point in the history
  • Loading branch information
userquin committed Sep 23, 2024
1 parent 54344ac commit db57722
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 48 deletions.
79 changes: 34 additions & 45 deletions composables/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const mock = process.mock

const users: Ref<UserLogin[]> | RemovableRef<UserLogin[]> = import.meta.server ? ref<UserLogin[]>([]) : ref<UserLogin[]>([]) as RemovableRef<UserLogin[]>
const nodes = useLocalStorage<Record<string, any>>(STORAGE_KEY_NODES, {}, { deep: true })
const currentUserHandle = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER_HANDLE, mock ? mock.user.account.id : '')
export const currentUserHandle = useLocalStorage<string>(STORAGE_KEY_CURRENT_USER_HANDLE, mock ? mock.user.account.id : '')
export const instanceStorage = useLocalStorage<Record<string, mastodon.v1.Instance>>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true })

export type ElkInstance = Partial<mastodon.v1.Instance> & {
Expand All @@ -32,17 +32,24 @@ export function getInstanceCache(server: string): mastodon.v1.Instance | undefin
}

export const currentUser = computed<UserLogin | undefined>(() => {
if (currentUserHandle.value) {
const user = users.value.find(user => user.account?.acct === currentUserHandle.value)
const handle = currentUserHandle.value
const currentUsers = users.value
if (handle) {
const user = currentUsers.find(user => user.account?.acct === handle)
if (user)
return user
}
// Fallback to the first account
return users.value[0]
return currentUsers.length ? currentUsers[0] : undefined
})

const publicInstance = ref<ElkInstance | null>(null)
export const currentInstance = computed<null | ElkInstance>(() => currentUser.value ? instanceStorage.value[currentUser.value.server] ?? null : publicInstance.value)
export const currentInstance = computed<null | ElkInstance>(() => {
const user = currentUser.value
const storage = instanceStorage.value
const instance = publicInstance.value
return user ? storage[user.server] ?? null : instance
})

export function getInstanceDomain(instance: ElkInstance) {
return instance.accountDomain || withoutProtocol(instance.uri)
Expand All @@ -55,44 +62,6 @@ export const currentNodeInfo = computed<null | Record<string, any>>(() => nodes.
export const isGotoSocial = computed(() => currentNodeInfo.value?.software?.name === 'gotosocial')
export const isGlitchEdition = computed(() => currentInstance.value?.version?.includes('+glitch'))

// when multiple tabs: we need to reload window when sign in, switch account or sign out
if (import.meta.client) {
// fix #2972: now users loaded from idb, we need to wait for it
const initialLoad = ref(true)
watchOnce(users, () => {
initialLoad.value = false
}, { immediate: true, flush: 'sync' })

const windowReload = () => {
if (document.visibilityState === 'visible' && !initialLoad.value)
window.location.reload()
}
watch(currentUserHandle, async (handle, oldHandle) => {
// when sign in or switch account
if (handle) {
if (handle === currentUser.value?.account?.acct) {
// when sign in, the other tab will not have the user, idb is not reactive
const newUser = users.value.find(user => user.account?.acct === handle)
// if the user is there, then we are switching account
if (newUser) {
// check if the change is on current tab: if so, don't reload
if (document.hasFocus() || document.visibilityState === 'visible')
return
}
}

window.addEventListener('visibilitychange', windowReload, { capture: true })
}
// when sign out
else if (oldHandle) {
const oldUser = users.value.find(user => user.account?.acct === oldHandle)
// when sign out, the other tab will not have the user, idb is not reactive
if (oldUser)
window.addEventListener('visibilitychange', windowReload, { capture: true })
}
}, { immediate: true, flush: 'post' })
}

export function useUsers() {
return users
}
Expand All @@ -102,7 +71,11 @@ export function useSelfAccount(user: MaybeRefOrGetter<mastodon.v1.Account | unde

export const characterLimit = computed(() => currentInstance.value?.configuration?.statuses.maxCharacters ?? DEFAULT_POST_CHARS_LIMIT)

export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { account?: mastodon.v1.AccountCredentials }>) {
export async function loginTo(
masto: ElkMasto,
user: Overwrite<UserLogin, { account?: mastodon.v1.AccountCredentials }>,
forceNotify = false,
) {
const { client } = masto
const instance = mastoLogin(masto, user)

Expand All @@ -115,6 +88,9 @@ export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { acco
if (!user?.token) {
publicServer.value = user.server
publicInstance.value = instance
if (import.meta.client) {
useNuxtApp().$notifyCredentialsChanged()
}
return
}

Expand All @@ -123,6 +99,7 @@ export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { acco
}

const account = getUser()?.account
const oldAcct = currentUserHandle.value
if (account)
currentUserHandle.value = account.acct

Expand All @@ -149,6 +126,14 @@ export async function loginTo(masto: ElkMasto, user: Overwrite<UserLogin, { acco
}

currentUserHandle.value = me.acct

if (import.meta.client) {
// Prevent sending multiple notifications (check signOut):
// - when sign-out with multiple accounts oldAcct === me.acct
if (forceNotify || oldAcct !== me.acct) {
useNuxtApp().$notifyCredentialsChanged(me.acct)
}
}
}

const accountPreferencesMap = new Map<string, Partial<mastodon.v1.Preference>>()
Expand Down Expand Up @@ -282,6 +267,8 @@ export async function signOut() {

const index = users.value.findIndex(u => u.account?.id === _currentUserId)

let forceNotify = false

if (index !== -1) {
// Clear stale data
clearUserLocalStorage()
Expand All @@ -295,6 +282,8 @@ export async function signOut() {
currentUserHandle.value = ''
// Remove the current user from the users
users.value.splice(index, 1)
// forceNotify when there are more accounts
forceNotify = users.value.length > 0
}

// Set currentUserId to next user if available
Expand All @@ -303,7 +292,7 @@ export async function signOut() {
if (!currentUserHandle.value)
await useRouter().push('/')

loginTo(masto, currentUser.value || { server: publicServer.value })
await loginTo(masto, currentUser.value || { server: publicServer.value }, forceNotify)
}

export function checkLogin() {
Expand Down
43 changes: 40 additions & 3 deletions plugins/0.setup-users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { STORAGE_KEY_USERS } from '~/constants'
const mock = process.mock

export default defineNuxtPlugin({
parallel: import.meta.server,
parallel: false,
async setup() {
const users = useUsers()

Expand All @@ -24,11 +24,48 @@ export default defineNuxtPlugin({
if (import.meta.server) {
users.value = defaultUsers
}

if (removeUsersOnLocalStorage)
globalThis.localStorage.removeItem(STORAGE_KEY_USERS)

let notifyCredentialsChanged = (_acct?: string) => {}

// when multiple tabs: we need to reload window when sign in, switch account or sign out
if (import.meta.client) {
const handlingMessage = ref(false)
const channel = new BroadcastChannel('elk')

onBeforeUnmount(() => {
channel.close()
})

channel.addEventListener('message', () => {
if (!handlingMessage.value) {
setTimeout(() => {
// force reload home page
reloadNuxtApp({
ttl: 0,
force: true,
path: '/',
})
}, 0)
return
}
handlingMessage.value = false
})

notifyCredentialsChanged = (acct) => {
handlingMessage.value = true
nextTick(() => channel.postMessage(acct || ''))
}

await useAsyncIDBKeyval<UserLogin[]>(STORAGE_KEY_USERS, defaultUsers, { deep: true }, users)
}

if (removeUsersOnLocalStorage)
globalThis.localStorage.removeItem(STORAGE_KEY_USERS)
return {
provide: {
notifyCredentialsChanged,
},
}
},
})

0 comments on commit db57722

Please sign in to comment.