starkon
Version:
Create a Next.js project with Starkon
415 lines (349 loc) • 11.4 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
export interface TokenData {
accessToken: string
refreshToken: string
expiresAt: number
}
export interface TokenInfo {
hasTokens: boolean
isAccessTokenExpired: boolean
isRefreshTokenExpired: boolean
accessToken: string | null
refreshToken: string | null
expiresAt: number | null
timeUntilExpiry: number | null
}
// Constants
const TOKEN_KEYS = {
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
EXPIRES_AT: 'token_expires_at',
} as const
const REFRESH_BUFFER_MS = 5 * 60 * 1000 // 5 minutes buffer
// Remember me storage keys
const REMEMBER_ME_KEY = 'remember_me'
const REMEMBERED_EMAIL_KEY = 'remembered_email'
const REMEMBER_ME_EXPIRY_KEY = 'remember_me_expiry'
// Remember me süresi (3 gün)
const REMEMBER_ME_DURATION = 3 * 24 * 60 * 60 * 1000 // 3 gün milisaniye
/**
* Kullanıcı email adresini kaydet (remember me ile birlikte)
* @param email - Kaydedilecek email adresi
*/
export const setRememberedEmail = (email: string): void => {
try {
const expiryTime = Date.now() + REMEMBER_ME_DURATION
localStorage.setItem(REMEMBERED_EMAIL_KEY, email)
localStorage.setItem(REMEMBER_ME_KEY, 'true')
localStorage.setItem(REMEMBER_ME_EXPIRY_KEY, expiryTime.toString())
console.log('✅ Email remembered for 3 days:', email, 'expires:', new Date(expiryTime).toLocaleDateString())
} catch (error) {
console.warn('❌ Error saving remembered email:', error)
}
}
/**
* Kaydedilmiş email adresini getir
* @returns string | null - Kaydedilmiş email adresi
*/
export const getRememberedEmail = (): string | null => {
try {
if (!isRememberMeValid()) {
clearRememberedEmail()
return null
}
const email = localStorage.getItem(REMEMBERED_EMAIL_KEY)
if (email) {
console.log('💾 Remembered email loaded:', email)
return email
}
return null
} catch (error) {
console.warn('❌ Error loading remembered email:', error)
return null
}
}
/**
* Kaydedilmiş email adresini temizle
*/
export const clearRememberedEmail = (): void => {
try {
localStorage.removeItem(REMEMBERED_EMAIL_KEY)
console.log('✅ Remembered email cleared')
} catch (error) {
console.warn('❌ Error clearing remembered email:', error)
}
}
/**
* Remember me durumunu kaydet (email ile birlikte)
* @param remember - Hatırlanacak mı
* @param email - Hatırlanacak email adresi
*/
export const setRememberMe = (remember: boolean, email?: string): void => {
try {
if (remember && email) {
setRememberedEmail(email)
console.log('✅ Remember me enabled with email for 3 days')
} else {
clearRememberMe()
}
} catch (error) {
console.warn('❌ Error setting remember me:', error)
}
}
/**
* Remember me durumunu temizle (email ile birlikte)
*/
export const clearRememberMe = (): void => {
try {
localStorage.removeItem(REMEMBER_ME_KEY)
localStorage.removeItem(REMEMBER_ME_EXPIRY_KEY)
clearRememberedEmail()
console.log('✅ Remember me and email cleared')
} catch (error) {
console.warn('❌ Error clearing remember me:', error)
}
}
/**
* Remember me durumunu kontrol et
* @returns boolean - Geçerli mi
*/
export const isRememberMeValid = (): boolean => {
try {
const rememberMe = localStorage.getItem(REMEMBER_ME_KEY)
const expiryTime = localStorage.getItem(REMEMBER_ME_EXPIRY_KEY)
if (!rememberMe || !expiryTime) return false
const now = Date.now()
const expiry = parseInt(expiryTime)
if (now > expiry) {
clearRememberMe()
console.log('⏰ Remember me expired after 3 days, clearing data')
return false
}
return true
} catch (error) {
console.warn('❌ Error checking remember me:', error)
return false
}
}
/**
* Remember me durumunu getir
* @returns boolean - Aktif mi
*/
export const getRememberMeStatus = (): boolean => {
return isRememberMeValid() && localStorage.getItem(REMEMBER_ME_KEY) === 'true'
}
/**
* Remember me süresinin ne kadar kaldığını getir
* @returns number - Kalan süre (saat cinsinden)
*/
export const getRememberMeTimeLeft = (): number => {
try {
const expiryTime = localStorage.getItem(REMEMBER_ME_EXPIRY_KEY)
if (!expiryTime) return 0
const now = Date.now()
const expiry = parseInt(expiryTime)
const timeLeft = expiry - now
return timeLeft > 0 ? Math.ceil(timeLeft / (60 * 60 * 1000)) : 0
} catch (error) {
console.warn('❌ Error calculating time left:', error)
return 0
}
}
// Utility functions
const isClientEnvironment = (): boolean => {
return typeof window !== 'undefined' && typeof sessionStorage !== 'undefined'
}
const isJWTFormat = (token: string): boolean => {
if (!token || typeof token !== 'string') return false
return token.split('.').length === 3
}
// Core token management functions
export const setTokens = (accessToken: string, refreshToken: string, expiresIn: number = 3600): void => {
if (!isClientEnvironment()) return
try {
const expiresAt = Date.now() + expiresIn * 1000
sessionStorage.setItem(TOKEN_KEYS.ACCESS_TOKEN, accessToken)
sessionStorage.setItem(TOKEN_KEYS.REFRESH_TOKEN, refreshToken)
sessionStorage.setItem(TOKEN_KEYS.EXPIRES_AT, expiresAt.toString())
console.log('✅ Tokens saved to sessionStorage')
} catch (error) {
console.error('❌ Error saving tokens:', error)
}
}
export const getAccessToken = (): string | null => {
if (!isClientEnvironment()) return null
try {
return sessionStorage.getItem(TOKEN_KEYS.ACCESS_TOKEN)
} catch (error) {
console.error('❌ Error getting access token:', error)
return null
}
}
export const getRefreshToken = (): string | null => {
if (!isClientEnvironment()) return null
try {
return sessionStorage.getItem(TOKEN_KEYS.REFRESH_TOKEN)
} catch (error) {
console.error('❌ Error getting refresh token:', error)
return null
}
}
export const isAccessTokenExpired = (): boolean => {
if (!isClientEnvironment()) return true
try {
const expiresAtStr = sessionStorage.getItem(TOKEN_KEYS.EXPIRES_AT)
if (!expiresAtStr) return true
const expiresAt = parseInt(expiresAtStr, 10)
if (isNaN(expiresAt)) return true
const now = Date.now()
return now >= expiresAt - REFRESH_BUFFER_MS
} catch (error) {
console.error('❌ Error checking token expiry:', error)
return true
}
}
const parseJWTPayload = (token: string): Record<string, any> | null => {
try {
const tokenParts = token.split('.')
const payload = tokenParts[1]
if (!payload || payload.length === 0) {
return null
}
// Validate base64 characters
const base64Regex = /^[A-Za-z0-9_-]+$/
if (!base64Regex.test(payload)) {
return null
}
// Add base64 padding
const paddedPayload = payload + '='.repeat((4 - (payload.length % 4)) % 4)
// Decode and parse
const decodedString = atob(paddedPayload)
const decodedPayload = JSON.parse(decodedString)
if (!decodedPayload || typeof decodedPayload !== 'object') {
return null
}
return decodedPayload
} catch {
return null
}
}
export const isRefreshTokenExpired = (): boolean => {
const refreshToken = getRefreshToken()
if (!refreshToken) return true
try {
// Basic token validation
if (typeof refreshToken !== 'string' || refreshToken.length === 0) {
return true
}
// Check if token is in JWT format
if (!isJWTFormat(refreshToken)) {
// Fallback to access token expiration logic for non-JWT tokens
return isAccessTokenExpired()
}
// Parse JWT payload
const payload = parseJWTPayload(refreshToken)
if (!payload) return true
// Validate expiration field
if (!payload.exp || typeof payload.exp !== 'number') {
return true
}
// Check expiration time
const currentTimeInSeconds = Math.floor(Date.now() / 1000)
return payload.exp < currentTimeInSeconds
} catch {
return true
}
}
export const clearTokens = (): void => {
if (!isClientEnvironment()) return
try {
sessionStorage.removeItem(TOKEN_KEYS.ACCESS_TOKEN)
sessionStorage.removeItem(TOKEN_KEYS.REFRESH_TOKEN)
sessionStorage.removeItem(TOKEN_KEYS.EXPIRES_AT)
console.log('✅ Tokens cleared from sessionStorage')
} catch (error) {
console.error('❌ Error clearing tokens:', error)
}
}
export const hasValidTokens = (): boolean => {
const accessToken = getAccessToken()
const refreshToken = getRefreshToken()
if (!accessToken || !refreshToken) return false
try {
return !isRefreshTokenExpired()
} catch {
return false
}
}
export const hasValidAccessToken = (): boolean => {
const accessToken = getAccessToken()
return Boolean(accessToken && !isAccessTokenExpired())
}
export const getTokenInfo = (): TokenInfo => {
const accessToken = getAccessToken()
const refreshToken = getRefreshToken()
const expiresAtStr = isClientEnvironment() ? sessionStorage.getItem(TOKEN_KEYS.EXPIRES_AT) : null
const expiresAt = expiresAtStr ? parseInt(expiresAtStr, 10) : null
const hasTokens = Boolean(accessToken && refreshToken)
const isAccessExpired = isAccessTokenExpired()
let isRefreshExpired: boolean
try {
isRefreshExpired = isRefreshTokenExpired()
} catch {
isRefreshExpired = true
}
const timeUntilExpiry = expiresAt && !isNaN(expiresAt) ? Math.max(0, expiresAt - Date.now()) : null
return {
hasTokens,
isAccessTokenExpired: isAccessExpired,
isRefreshTokenExpired: isRefreshExpired,
accessToken,
refreshToken,
expiresAt,
timeUntilExpiry,
}
}
export const debugTokenInfo = (): void => {
if (!isClientEnvironment()) {
console.log('🔍 Debug: Not in client environment')
return
}
try {
const info = getTokenInfo()
const timeUntilExpiryMinutes = info.timeUntilExpiry ? Math.round(info.timeUntilExpiry / (1000 * 60)) : null
console.log('🔍 Token Debug Info:', {
...info,
timeUntilExpiryMinutes,
})
} catch (error) {
console.log('🔍 Debug Info Error (non-critical):', error)
console.log('🔍 Basic Token Status:', {
hasAccessToken: Boolean(getAccessToken()),
hasRefreshToken: Boolean(getRefreshToken()),
accessTokenLength: getAccessToken()?.length || 0,
refreshTokenLength: getRefreshToken()?.length || 0,
})
}
}
// Legacy class compatibility wrapper (optional - can be removed if not needed)
export class SessionTokenManager {
static setTokens = setTokens
static clearTokens = clearTokens
static debugInfo = debugTokenInfo
static getTokenInfo = getTokenInfo
static setRememberMe = setRememberMe
static hasValidTokens = hasValidTokens
static getAccessToken = getAccessToken
static getRefreshToken = getRefreshToken
static clearRememberMe = clearRememberMe
static isRememberMeValid = isRememberMeValid
static setRememberedEmail = setRememberedEmail
static getRememberedEmail = getRememberedEmail
static getRememberMeStatus = getRememberMeStatus
static hasValidAccessToken = hasValidAccessToken
static clearRememberedEmail = clearRememberedEmail
static isAccessTokenExpired = isAccessTokenExpired
static isRefreshTokenExpired = isRefreshTokenExpired
static getRememberMeTimeLeft = getRememberMeTimeLeft
}