This repository has been archived on 2024-10-29. You can view files and clone it, but cannot push or open issues or pull requests.
website/tools/authentication/Authentication.ts

132 lines
3.8 KiB
TypeScript

import type { AxiosInstance } from "axios"
import axios from "axios"
import type { Socket } from "socket.io-client"
import { io } from "socket.io-client"
import { isUnauthorizedError } from "@thream/socketio-jwt"
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 (isUnauthorizedError(error)) {
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.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<void> {
try {
await this.api.post("/users/signout", {
refreshToken: this.tokens.refreshToken,
})
} catch {}
this.signout()
}
public async signoutAllDevicesServerSide(): Promise<void> {
try {
await this.api.delete("/users/signout")
} catch {}
this.signout()
}
public signin(): void {
clearCache()
cookies.set("refreshToken", this.tokens.refreshToken)
}
}