import type { AxiosInstance } from 'axios' import axios from 'axios' import type { Socket } from 'socket.io-client' import { io } from 'socket.io-client' import { API_URL } from '../api' import { cookies } from '../cookies' import type { Tokens } from '.' import { fetchRefreshToken } from './authenticationFromServerSide' import { clearCache } from '../cache' export class Authentication { public tokens: Tokens public accessTokenAge: number public socket?: Socket public api: AxiosInstance constructor(tokens: Tokens, disableSocketIO: boolean = false) { this.tokens = tokens this.accessTokenAge = Date.now() if (typeof window === 'undefined' || disableSocketIO) { this.socket = undefined } else { this.socket = io(API_URL, { auth: { token: `Bearer ${tokens.accessToken}` } }) this.socket.on('connect', () => { console.log( `Connected to socket with clientId: ${this.socket?.id ?? 'undefined'}` ) }) 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({ baseURL: API_URL, headers: { 'Content-Type': 'application/json' } }) this.api.interceptors.request.use( async (config) => { const isValidAccessToken = this.accessTokenAge + tokens.expiresIn > Date.now() if (!isValidAccessToken) { const { accessToken } = await fetchRefreshToken( this.tokens.refreshToken ) this.setAccessToken(accessToken) } config.headers = config.headers == null ? {} : config.headers config.headers.Authorization = `${this.tokens.type} ${this.tokens.accessToken}` return config }, async (error) => { this.signout() return await Promise.reject(error) } ) this.api.interceptors.response.use( (response) => { return response }, async (error) => { if (error.response.status !== 403 || (error.config._retry as boolean)) { return await Promise.reject(error) } error.config._retry = true try { const { accessToken } = await fetchRefreshToken( this.tokens.refreshToken ) this.setAccessToken(accessToken) error.response.config.headers.Authorization = `${this.tokens.type} ${this.tokens.accessToken}` return await this.api.request(error.response.config) } catch { this.signout() return await Promise.reject(error) } } ) } public setAccessToken(accessToken: string): void { this.tokens.accessToken = accessToken this.accessTokenAge = Date.now() const token = `${this.tokens.type} ${this.tokens.accessToken}` if (typeof this?.socket?.auth !== 'function' && this.socket != null) { this.socket.auth.token = token } } public signout(): void { cookies.remove('refreshToken') clearCache() window.location.href = '/authentication/signin' } public async signoutServerSide(): Promise { try { await this.api.post('/users/signout', { refreshToken: this.tokens.refreshToken }) } catch {} this.signout() } public async signoutAllDevicesServerSide(): Promise { try { await this.api.delete('/users/signout') } catch {} this.signout() } public signin(): void { clearCache() cookies.set('refreshToken', this.tokens.refreshToken) } }