perf: optimize load of pagination items with caching
This commit is contained in:
parent
19fc29ad47
commit
ad64f1c571
@ -22,12 +22,15 @@ export const JoinGuildsPublic: React.FC = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
authentication.socket.on('guilds', (data: SocketData<GuildPublicType>) => {
|
authentication?.socket?.on(
|
||||||
handleSocketData({ data, setItems })
|
'guilds',
|
||||||
})
|
(data: SocketData<GuildPublicType>) => {
|
||||||
|
handleSocketData({ data, setItems })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
authentication.socket.off('guilds')
|
authentication?.socket?.off('guilds')
|
||||||
}
|
}
|
||||||
}, [authentication.socket, setItems])
|
}, [authentication.socket, setItems])
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ export const Authentication: React.FC<AuthenticationProps> = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.post<Tokens>('/users/signin', formData)
|
const { data } = await api.post<Tokens>('/users/signin', formData)
|
||||||
const authentication = new AuthenticationClass(data)
|
const authentication = new AuthenticationClass(data, true)
|
||||||
authentication.signin()
|
authentication.signin()
|
||||||
await router.push('/application')
|
await router.push('/application')
|
||||||
return null
|
return null
|
||||||
|
@ -24,7 +24,7 @@ export const AuthenticationSocialMedia: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data = router.query
|
const data = router.query
|
||||||
if (isTokens(data)) {
|
if (isTokens(data)) {
|
||||||
const authentication = new Authentication(data)
|
const authentication = new Authentication(data, true)
|
||||||
authentication.signin()
|
authentication.signin()
|
||||||
router.push('/application').catch(() => {})
|
router.push('/application').catch(() => {})
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { useAuthentication } from '../tools/authentication'
|
|||||||
import { Channel, ChannelWithDefaultChannelId } from '../models/Channel'
|
import { Channel, ChannelWithDefaultChannelId } from '../models/Channel'
|
||||||
import { GuildsChannelsPath } from '../components/Application'
|
import { GuildsChannelsPath } from '../components/Application'
|
||||||
import { handleSocketData, SocketData } from '../tools/handleSocketData'
|
import { handleSocketData, SocketData } from '../tools/handleSocketData'
|
||||||
|
import { CacheKey, CHANNELS_CACHE_KEY } from '../tools/cache'
|
||||||
|
|
||||||
export interface Channels {
|
export interface Channels {
|
||||||
channels: Channel[]
|
channels: Channel[]
|
||||||
@ -27,6 +28,8 @@ export const ChannelsProvider: React.FC<
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { authentication } = useAuthentication()
|
const { authentication } = useAuthentication()
|
||||||
|
|
||||||
|
const cacheKey: CacheKey = `${path.guildId}-${CHANNELS_CACHE_KEY}`
|
||||||
|
|
||||||
const {
|
const {
|
||||||
items: channels,
|
items: channels,
|
||||||
hasMore,
|
hasMore,
|
||||||
@ -35,14 +38,15 @@ export const ChannelsProvider: React.FC<
|
|||||||
setItems
|
setItems
|
||||||
} = usePagination<Channel>({
|
} = usePagination<Channel>({
|
||||||
api: authentication.api,
|
api: authentication.api,
|
||||||
url: `/guilds/${path.guildId}/channels`
|
url: `/guilds/${path.guildId}/channels`,
|
||||||
|
cacheKey
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
authentication.socket.on(
|
authentication?.socket?.on(
|
||||||
'channels',
|
'channels',
|
||||||
async (data: SocketData<ChannelWithDefaultChannelId>) => {
|
async (data: SocketData<ChannelWithDefaultChannelId>) => {
|
||||||
handleSocketData({ data, setItems })
|
handleSocketData({ data, setItems, cacheKey })
|
||||||
if (data.action === 'delete') {
|
if (data.action === 'delete') {
|
||||||
await router.push(
|
await router.push(
|
||||||
`/application/${path.guildId}/${data.item.defaultChannelId}`
|
`/application/${path.guildId}/${data.item.defaultChannelId}`
|
||||||
@ -52,9 +56,9 @@ export const ChannelsProvider: React.FC<
|
|||||||
)
|
)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
authentication.socket.off('channels')
|
authentication?.socket?.off('channels')
|
||||||
}
|
}
|
||||||
}, [authentication.socket, path.guildId, router, setItems])
|
}, [authentication.socket, path.guildId, router, setItems, cacheKey])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resetPagination()
|
resetPagination()
|
||||||
|
@ -47,7 +47,7 @@ export const GuildMemberProvider: React.FC<
|
|||||||
}, [path, authentication.api])
|
}, [path, authentication.api])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
authentication.socket.on(
|
authentication?.socket?.on(
|
||||||
'guilds',
|
'guilds',
|
||||||
async (data: SocketData<GuildWithDefaultChannelId>) => {
|
async (data: SocketData<GuildWithDefaultChannelId>) => {
|
||||||
if (data.item.id === path.guildId) {
|
if (data.item.id === path.guildId) {
|
||||||
@ -72,7 +72,7 @@ export const GuildMemberProvider: React.FC<
|
|||||||
)
|
)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
authentication.socket.off('guilds')
|
authentication?.socket?.off('guilds')
|
||||||
}
|
}
|
||||||
}, [authentication.socket, path.guildId, router])
|
}, [authentication.socket, path.guildId, router])
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { NextPage, usePagination } from '../hooks/usePagination'
|
|||||||
import { useAuthentication } from '../tools/authentication'
|
import { useAuthentication } from '../tools/authentication'
|
||||||
import { GuildWithDefaultChannelId } from '../models/Guild'
|
import { GuildWithDefaultChannelId } from '../models/Guild'
|
||||||
import { handleSocketData, SocketData } from '../tools/handleSocketData'
|
import { handleSocketData, SocketData } from '../tools/handleSocketData'
|
||||||
|
import { GUILDS_CACHE_KEY } from '../tools/cache'
|
||||||
|
|
||||||
export interface Guilds {
|
export interface Guilds {
|
||||||
guilds: GuildWithDefaultChannelId[]
|
guilds: GuildWithDefaultChannelId[]
|
||||||
@ -29,19 +30,20 @@ export const GuildsProvider: React.FC<React.PropsWithChildren<{}>> = (
|
|||||||
setItems
|
setItems
|
||||||
} = usePagination<GuildWithDefaultChannelId>({
|
} = usePagination<GuildWithDefaultChannelId>({
|
||||||
api: authentication.api,
|
api: authentication.api,
|
||||||
url: '/guilds'
|
url: '/guilds',
|
||||||
|
cacheKey: GUILDS_CACHE_KEY
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
authentication.socket.on(
|
authentication?.socket?.on(
|
||||||
'guilds',
|
'guilds',
|
||||||
(data: SocketData<GuildWithDefaultChannelId>) => {
|
(data: SocketData<GuildWithDefaultChannelId>) => {
|
||||||
handleSocketData({ data, setItems })
|
handleSocketData({ data, setItems, cacheKey: GUILDS_CACHE_KEY })
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
authentication.socket.off('guilds')
|
authentication?.socket?.off('guilds')
|
||||||
}
|
}
|
||||||
}, [authentication.socket, setItems])
|
}, [authentication.socket, setItems])
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { MemberWithPublicUser } from '../models/Member'
|
|||||||
import { GuildsChannelsPath } from '../components/Application'
|
import { GuildsChannelsPath } from '../components/Application'
|
||||||
import { handleSocketData, SocketData } from '../tools/handleSocketData'
|
import { handleSocketData, SocketData } from '../tools/handleSocketData'
|
||||||
import { User } from '../models/User'
|
import { User } from '../models/User'
|
||||||
|
import { CacheKey, MEMBERS_CACHE_KEY } from '../tools/cache'
|
||||||
|
|
||||||
export interface Members {
|
export interface Members {
|
||||||
members: MemberWithPublicUser[]
|
members: MemberWithPublicUser[]
|
||||||
@ -27,6 +28,8 @@ export const MembersProviders: React.FC<
|
|||||||
|
|
||||||
const { authentication } = useAuthentication()
|
const { authentication } = useAuthentication()
|
||||||
|
|
||||||
|
const cacheKey: CacheKey = `${path.guildId}-${MEMBERS_CACHE_KEY}`
|
||||||
|
|
||||||
const {
|
const {
|
||||||
items: members,
|
items: members,
|
||||||
hasMore,
|
hasMore,
|
||||||
@ -35,18 +38,19 @@ export const MembersProviders: React.FC<
|
|||||||
setItems
|
setItems
|
||||||
} = usePagination<MemberWithPublicUser>({
|
} = usePagination<MemberWithPublicUser>({
|
||||||
api: authentication.api,
|
api: authentication.api,
|
||||||
url: `/guilds/${path.guildId}/members`
|
url: `/guilds/${path.guildId}/members`,
|
||||||
|
cacheKey
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
authentication.socket.on(
|
authentication?.socket?.on(
|
||||||
'members',
|
'members',
|
||||||
(data: SocketData<MemberWithPublicUser>) => {
|
(data: SocketData<MemberWithPublicUser>) => {
|
||||||
handleSocketData({ data, setItems })
|
handleSocketData({ data, setItems, cacheKey })
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
authentication.socket.on('users', (data: SocketData<User>) => {
|
authentication?.socket?.on('users', (data: SocketData<User>) => {
|
||||||
setItems((oldItems) => {
|
setItems((oldItems) => {
|
||||||
const newItems = [...oldItems]
|
const newItems = [...oldItems]
|
||||||
switch (data.action) {
|
switch (data.action) {
|
||||||
@ -65,10 +69,10 @@ export const MembersProviders: React.FC<
|
|||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
authentication.socket.off('members')
|
authentication?.socket?.off('members')
|
||||||
authentication.socket.off('users')
|
authentication?.socket?.off('users')
|
||||||
}
|
}
|
||||||
}, [authentication.socket, setItems])
|
}, [authentication.socket, setItems, cacheKey])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resetPagination()
|
resetPagination()
|
||||||
|
@ -5,6 +5,7 @@ import { useAuthentication } from '../tools/authentication'
|
|||||||
import { MessageWithMember } from '../models/Message'
|
import { MessageWithMember } from '../models/Message'
|
||||||
import { GuildsChannelsPath } from '../components/Application'
|
import { GuildsChannelsPath } from '../components/Application'
|
||||||
import { handleSocketData, SocketData } from '../tools/handleSocketData'
|
import { handleSocketData, SocketData } from '../tools/handleSocketData'
|
||||||
|
import { CacheKey, MESSAGES_CACHE_KEY } from '../tools/cache'
|
||||||
|
|
||||||
export interface Messages {
|
export interface Messages {
|
||||||
messages: MessageWithMember[]
|
messages: MessageWithMember[]
|
||||||
@ -25,6 +26,8 @@ export const MessagesProvider: React.FC<
|
|||||||
const { path, children } = props
|
const { path, children } = props
|
||||||
const { authentication, user } = useAuthentication()
|
const { authentication, user } = useAuthentication()
|
||||||
|
|
||||||
|
const cacheKey: CacheKey = `${path.channelId}-${MESSAGES_CACHE_KEY}`
|
||||||
|
|
||||||
const {
|
const {
|
||||||
items: messages,
|
items: messages,
|
||||||
hasMore,
|
hasMore,
|
||||||
@ -34,11 +37,12 @@ export const MessagesProvider: React.FC<
|
|||||||
} = usePagination<MessageWithMember>({
|
} = usePagination<MessageWithMember>({
|
||||||
api: authentication.api,
|
api: authentication.api,
|
||||||
url: `/channels/${path.channelId}/messages`,
|
url: `/channels/${path.channelId}/messages`,
|
||||||
inverse: true
|
inverse: true,
|
||||||
|
cacheKey
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
authentication.socket.on(
|
authentication?.socket?.on(
|
||||||
'messages',
|
'messages',
|
||||||
(data: SocketData<MessageWithMember>) => {
|
(data: SocketData<MessageWithMember>) => {
|
||||||
if (data.item.channelId === path.channelId) {
|
if (data.item.channelId === path.channelId) {
|
||||||
@ -48,7 +52,7 @@ export const MessagesProvider: React.FC<
|
|||||||
const isAtBottom =
|
const isAtBottom =
|
||||||
messagesDiv.scrollHeight - messagesDiv.scrollTop <=
|
messagesDiv.scrollHeight - messagesDiv.scrollTop <=
|
||||||
messagesDiv.clientHeight
|
messagesDiv.clientHeight
|
||||||
handleSocketData({ data, setItems })
|
handleSocketData({ data, setItems, cacheKey })
|
||||||
if (
|
if (
|
||||||
data.action === 'create' &&
|
data.action === 'create' &&
|
||||||
(isAtBottom || data.item.member.userId === user.id)
|
(isAtBottom || data.item.member.userId === user.id)
|
||||||
@ -60,9 +64,9 @@ export const MessagesProvider: React.FC<
|
|||||||
)
|
)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
authentication.socket.off('messages')
|
authentication?.socket?.off('messages')
|
||||||
}
|
}
|
||||||
}, [authentication.socket, setItems, path, user.id])
|
}, [authentication.socket, setItems, path, user.id, cacheKey])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resetPagination()
|
resetPagination()
|
||||||
|
@ -2,6 +2,11 @@ import { useState, useRef, useCallback } from 'react'
|
|||||||
import { AxiosInstance } from 'axios'
|
import { AxiosInstance } from 'axios'
|
||||||
|
|
||||||
import { FetchState } from './useFetchState'
|
import { FetchState } from './useFetchState'
|
||||||
|
import {
|
||||||
|
CacheKey,
|
||||||
|
getPaginationCache,
|
||||||
|
savePaginationCache
|
||||||
|
} from '../tools/cache'
|
||||||
|
|
||||||
export interface Query {
|
export interface Query {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
@ -13,6 +18,11 @@ export interface UsePaginationOptions {
|
|||||||
api: AxiosInstance
|
api: AxiosInstance
|
||||||
url: string
|
url: string
|
||||||
inverse?: boolean
|
inverse?: boolean
|
||||||
|
cacheKey?: CacheKey
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginationItem {
|
||||||
|
id: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SetItems<T> = React.Dispatch<React.SetStateAction<T[]>>
|
export type SetItems<T> = React.Dispatch<React.SetStateAction<T[]>>
|
||||||
@ -25,16 +35,25 @@ export interface UsePaginationResult<T> {
|
|||||||
setItems: SetItems<T>
|
setItems: SetItems<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePagination = <T extends { id: number }>(
|
export const usePagination = <T extends PaginationItem>(
|
||||||
options: UsePaginationOptions
|
options: UsePaginationOptions
|
||||||
): UsePaginationResult<T> => {
|
): UsePaginationResult<T> => {
|
||||||
const { api, url, inverse = false } = options
|
const { api, url, inverse = false, cacheKey } = options
|
||||||
|
|
||||||
const [items, setItems] = useState<T[]>([])
|
const [items, setItems] = useState<T[]>([])
|
||||||
const [hasMore, setHasMore] = useState(true)
|
const [hasMore, setHasMore] = useState(true)
|
||||||
const fetchState = useRef<FetchState>('idle')
|
const fetchState = useRef<FetchState>('idle')
|
||||||
const afterId = useRef<number | null>(null)
|
const afterId = useRef<number | null>(null)
|
||||||
|
|
||||||
|
const updateAfterId = (newItems: T[]): void => {
|
||||||
|
if (!inverse) {
|
||||||
|
afterId.current =
|
||||||
|
newItems.length > 0 ? newItems[newItems.length - 1].id : null
|
||||||
|
} else {
|
||||||
|
afterId.current = newItems.length > 0 ? newItems[0].id : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const nextPageAsync: NextPageAsync = useCallback(
|
const nextPageAsync: NextPageAsync = useCallback(
|
||||||
async (query) => {
|
async (query) => {
|
||||||
if (fetchState.current !== 'idle') {
|
if (fetchState.current !== 'idle') {
|
||||||
@ -49,19 +68,22 @@ export const usePagination = <T extends { id: number }>(
|
|||||||
const { data: newItems } = await api.get<T[]>(
|
const { data: newItems } = await api.get<T[]>(
|
||||||
`${url}?${searchParameters.toString()}`
|
`${url}?${searchParameters.toString()}`
|
||||||
)
|
)
|
||||||
if (!inverse) {
|
updateAfterId(newItems)
|
||||||
afterId.current =
|
|
||||||
newItems.length > 0 ? newItems[newItems.length - 1].id : null
|
|
||||||
} else {
|
|
||||||
afterId.current = newItems.length > 0 ? newItems[0].id : null
|
|
||||||
}
|
|
||||||
setItems((oldItems) => {
|
setItems((oldItems) => {
|
||||||
return inverse ? [...newItems, ...oldItems] : [...oldItems, ...newItems]
|
const updatedItems = inverse
|
||||||
|
? [...newItems, ...oldItems]
|
||||||
|
: [...oldItems, ...newItems]
|
||||||
|
if (cacheKey != null) {
|
||||||
|
savePaginationCache(cacheKey, updatedItems)
|
||||||
|
}
|
||||||
|
return updatedItems
|
||||||
})
|
})
|
||||||
setHasMore(newItems.length > 0)
|
setHasMore(newItems.length > 0)
|
||||||
fetchState.current = 'idle'
|
fetchState.current = 'idle'
|
||||||
},
|
},
|
||||||
[api, url, inverse]
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- We don't want infinite loops with updateAfterId
|
||||||
|
[api, url, inverse, cacheKey]
|
||||||
)
|
)
|
||||||
|
|
||||||
const nextPage: NextPage = useCallback(
|
const nextPage: NextPage = useCallback(
|
||||||
@ -80,9 +102,17 @@ export const usePagination = <T extends { id: number }>(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const resetPagination = useCallback((): void => {
|
const resetPagination = useCallback((): void => {
|
||||||
afterId.current = null
|
if (cacheKey == null) {
|
||||||
setItems([])
|
afterId.current = null
|
||||||
}, [])
|
setItems([])
|
||||||
|
} else {
|
||||||
|
const newItems = getPaginationCache<T>(cacheKey)
|
||||||
|
setItems(newItems)
|
||||||
|
updateAfterId(newItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- We don't want infinite loops with updateAfterId
|
||||||
|
}, [cacheKey])
|
||||||
|
|
||||||
return { items, hasMore, nextPage, resetPagination, setItems }
|
return { items, hasMore, nextPage, resetPagination, setItems }
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ const nextTranslate = require('next-translate')
|
|||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
module.exports = nextTranslate(
|
module.exports = nextTranslate(
|
||||||
nextPWA({
|
nextPWA({
|
||||||
reactStrictMode: true,
|
reactStrictMode: false,
|
||||||
images: {
|
images: {
|
||||||
domains: [
|
domains: [
|
||||||
'api.thream.divlo.fr',
|
'api.thream.divlo.fr',
|
||||||
|
@ -28,6 +28,9 @@ const Application = ({ Component, pageProps }: AppProps): JSX.Element => {
|
|||||||
}
|
}
|
||||||
window.addEventListener('resize', appHeight)
|
window.addEventListener('resize', appHeight)
|
||||||
appHeight()
|
appHeight()
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', appHeight)
|
||||||
|
}
|
||||||
}, [lang])
|
}, [lang])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -9,27 +9,36 @@ import { fetchRefreshToken } from './authenticationFromServerSide'
|
|||||||
export class Authentication {
|
export class Authentication {
|
||||||
public tokens: Tokens
|
public tokens: Tokens
|
||||||
public accessTokenAge: number
|
public accessTokenAge: number
|
||||||
public socket: Socket
|
public socket?: Socket
|
||||||
public api: AxiosInstance
|
public api: AxiosInstance
|
||||||
|
|
||||||
constructor(tokens: Tokens) {
|
constructor(tokens: Tokens, disableSocketIO: boolean = false) {
|
||||||
this.tokens = tokens
|
this.tokens = tokens
|
||||||
this.accessTokenAge = Date.now()
|
this.accessTokenAge = Date.now()
|
||||||
this.socket = io(API_URL, {
|
if (disableSocketIO || typeof window === 'undefined') {
|
||||||
auth: { token: `Bearer ${tokens.accessToken}` }
|
this.socket = undefined
|
||||||
})
|
} else {
|
||||||
this.socket.on('connect_error', (error) => {
|
this.socket = io(API_URL, {
|
||||||
if (error.message.startsWith('Unauthorized')) {
|
auth: { token: `Bearer ${tokens.accessToken}` }
|
||||||
fetchRefreshToken(this.tokens.refreshToken)
|
})
|
||||||
.then(({ accessToken }) => {
|
this.socket.on('connect', () => {
|
||||||
this.setAccessToken(accessToken)
|
console.log(
|
||||||
})
|
`Connected to socket with clientId: ${this.socket?.id ?? 'undefined'}`
|
||||||
.catch(async () => {
|
)
|
||||||
this.signout()
|
})
|
||||||
return await Promise.reject(error)
|
this.socket.on('connect_error', (error) => {
|
||||||
})
|
if (error.message.startsWith('Unauthorized')) {
|
||||||
}
|
fetchRefreshToken(this.tokens.refreshToken)
|
||||||
})
|
.then(({ accessToken }) => {
|
||||||
|
this.setAccessToken(accessToken)
|
||||||
|
})
|
||||||
|
.catch(async () => {
|
||||||
|
this.signout()
|
||||||
|
return await Promise.reject(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
this.api = axios.create({
|
this.api = axios.create({
|
||||||
baseURL: API_URL,
|
baseURL: API_URL,
|
||||||
headers: {
|
headers: {
|
||||||
@ -83,13 +92,14 @@ export class Authentication {
|
|||||||
this.tokens.accessToken = accessToken
|
this.tokens.accessToken = accessToken
|
||||||
this.accessTokenAge = Date.now()
|
this.accessTokenAge = Date.now()
|
||||||
const token = `${this.tokens.type} ${this.tokens.accessToken}`
|
const token = `${this.tokens.type} ${this.tokens.accessToken}`
|
||||||
if (typeof this.socket.auth !== 'function') {
|
if (typeof this?.socket?.auth !== 'function' && this.socket != null) {
|
||||||
this.socket.auth.token = token
|
this.socket.auth.token = token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public signout(): void {
|
public signout(): void {
|
||||||
cookies.remove('refreshToken')
|
cookies.remove('refreshToken')
|
||||||
|
window.localStorage.clear()
|
||||||
window.location.href = '/authentication/signin'
|
window.location.href = '/authentication/signin'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,8 @@ export const AuthenticationProvider: React.FC<
|
|||||||
const [user, setUser] = useState<UserCurrent>(props.authentication.user)
|
const [user, setUser] = useState<UserCurrent>(props.authentication.user)
|
||||||
|
|
||||||
const authentication = useMemo(() => {
|
const authentication = useMemo(() => {
|
||||||
return new Authentication(props.authentication.tokens)
|
const disableSocketIO = typeof window === 'undefined'
|
||||||
|
return new Authentication(props.authentication.tokens, disableSocketIO)
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this memo once
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this memo once
|
||||||
}, [])
|
}, [])
|
||||||
@ -33,7 +34,9 @@ export const AuthenticationProvider: React.FC<
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLanguage(props.authentication.user.settings.language).catch(() => {})
|
setLanguage(props.authentication.user.settings.language).catch(() => {})
|
||||||
setTheme(props.authentication.user.settings.theme)
|
setTheme(props.authentication.user.settings.theme)
|
||||||
|
return () => {
|
||||||
|
authentication?.socket?.disconnect()
|
||||||
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this effect once
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this effect once
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ export const authenticationFromServerSide = (
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
let data: any = {}
|
let data: any = {}
|
||||||
const authentication = new Authentication(tokens)
|
const authentication = new Authentication(tokens, true)
|
||||||
const { data: currentUser } = await authentication.api.get<
|
const { data: currentUser } = await authentication.api.get<
|
||||||
unknown,
|
unknown,
|
||||||
AxiosResponse<UserCurrent>
|
AxiosResponse<UserCurrent>
|
||||||
|
34
tools/cache.ts
Normal file
34
tools/cache.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { PaginationItem } from '../hooks/usePagination'
|
||||||
|
|
||||||
|
export const GUILDS_CACHE_KEY = 'guilds' as const
|
||||||
|
export const CHANNELS_CACHE_KEY = 'channels' as const
|
||||||
|
export const MEMBERS_CACHE_KEY = 'members' as const
|
||||||
|
export const MESSAGES_CACHE_KEY = 'messages' as const
|
||||||
|
|
||||||
|
export type CacheKey =
|
||||||
|
| typeof GUILDS_CACHE_KEY
|
||||||
|
| `${number}-${typeof CHANNELS_CACHE_KEY}`
|
||||||
|
| `${number}-${typeof MEMBERS_CACHE_KEY}`
|
||||||
|
| `${number}-${typeof MESSAGES_CACHE_KEY}`
|
||||||
|
|
||||||
|
export const getPaginationCache = <T extends PaginationItem>(
|
||||||
|
key: CacheKey
|
||||||
|
): T[] => {
|
||||||
|
const cache = localStorage.getItem(key)
|
||||||
|
if (cache != null) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(cache)
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
export const savePaginationCache = <T extends PaginationItem>(
|
||||||
|
key: CacheKey,
|
||||||
|
data: T[]
|
||||||
|
): void => {
|
||||||
|
localStorage.setItem(key, JSON.stringify(data))
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { SetItems } from '../hooks/usePagination'
|
import { SetItems } from '../hooks/usePagination'
|
||||||
|
import { CacheKey, savePaginationCache } from './cache'
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
id: number
|
id: number
|
||||||
@ -13,6 +14,7 @@ export interface SocketData<T extends Item = Item> {
|
|||||||
export interface HandleSocketDataOptions<T extends Item = Item> {
|
export interface HandleSocketDataOptions<T extends Item = Item> {
|
||||||
setItems: SetItems<T>
|
setItems: SetItems<T>
|
||||||
data: SocketData<T>
|
data: SocketData<T>
|
||||||
|
cacheKey?: CacheKey
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SocketListener = (data: SocketData) => void
|
export type SocketListener = (data: SocketData) => void
|
||||||
@ -20,7 +22,8 @@ export type SocketListener = (data: SocketData) => void
|
|||||||
export const handleSocketData = <T extends Item = Item>(
|
export const handleSocketData = <T extends Item = Item>(
|
||||||
options: HandleSocketDataOptions<T>
|
options: HandleSocketDataOptions<T>
|
||||||
): void => {
|
): void => {
|
||||||
const { data, setItems } = options
|
const { data, setItems, cacheKey } = options
|
||||||
|
console.log('socket.io data received: ', data)
|
||||||
|
|
||||||
setItems((oldItems) => {
|
setItems((oldItems) => {
|
||||||
const newItems = [...oldItems]
|
const newItems = [...oldItems]
|
||||||
@ -47,6 +50,9 @@ export const handleSocketData = <T extends Item = Item>(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cacheKey != null) {
|
||||||
|
savePaginationCache(cacheKey, newItems)
|
||||||
|
}
|
||||||
return newItems
|
return newItems
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user