package helps import ( "crypto/sha256" "encoding/hex" "sync" "time" "github.com/google/uuid") type sessionIDCacheEntry struct { value string expire time.Time} var ( sessionIDCache = make(map[string]sessionIDCacheEntry) sessionIDCacheMu sync.RWMutex sessionIDCacheCleanupOnce sync.Once) const ( sessionIDTTL = time.Hour sessionIDCacheCleanupPeriod = 15 * time.Minute) func startSessionIDCacheCleanup() { go func() { ticker := time.NewTicker(sessionIDCacheCleanupPeriod) defer ticker.Stop() for range ticker.C { purgeExpiredSessionIDs() } }()} func purgeExpiredSessionIDs() { now := time.Now() sessionIDCacheMu.Lock() for key, entry := range sessionIDCache { if !entry.expire.After(now) { delete(sessionIDCache, key) } } sessionIDCacheMu.Unlock()} func sessionIDCacheKey(apiKey string) string { sum := sha256.Sum256([]byte(apiKey)) return hex.EncodeToString(sum[:])} // CachedSessionID returns a stable session UUID per apiKey, refreshing the TTL on each access.func CachedSessionID(apiKey string) string { if apiKey == "" { return uuid.New().String() } sessionIDCacheCleanupOnce.Do(startSessionIDCacheCleanup) key := sessionIDCacheKey(apiKey) now := time.Now() sessionIDCacheMu.RLock() entry, ok := sessionIDCache[key] valid := ok && entry.value != "" && entry.expire.After(now) sessionIDCacheMu.RUnlock() if valid { sessionIDCacheMu.Lock() entry = sessionIDCache[key] if entry.value != "" && entry.expire.After(now) { entry.expire = now.Add(sessionIDTTL) sessionIDCache[key] = entry sessionIDCacheMu.Unlock() return entry.value } sessionIDCacheMu.Unlock() } newID := uuid.New().String() sessionIDCacheMu.Lock() entry, ok = sessionIDCache[key] if !ok || entry.value == "" || !entry.expire.After(now) { entry.value = newID } entry.expire = now.Add(sessionIDTTL) sessionIDCache[key] = entry sessionIDCacheMu.Unlock() return entry.value}
package helps
import (
"crypto/sha256"
"encoding/hex"
"sync"
"time"
"github.com/google/uuid"
)
type sessionIDCacheEntry struct {
value string
expire time.Time
}
var (
sessionIDCache = make(map[string]sessionIDCacheEntry)
sessionIDCacheMu sync.RWMutex
sessionIDCacheCleanupOnce sync.Once
)
const (
sessionIDTTL = time.Hour
sessionIDCacheCleanupPeriod = 15 * time.Minute
)
func startSessionIDCacheCleanup() {
go func() {
ticker := time.NewTicker(sessionIDCacheCleanupPeriod)
defer ticker.Stop()
for range ticker.C {
purgeExpiredSessionIDs()
}
}()
}
func purgeExpiredSessionIDs() {
now := time.Now()
sessionIDCacheMu.Lock()
for key, entry := range sessionIDCache {
if !entry.expire.After(now) {
delete(sessionIDCache, key)
}
}
sessionIDCacheMu.Unlock()
}
func sessionIDCacheKey(apiKey string) string {
sum := sha256.Sum256([]byte(apiKey))
return hex.EncodeToString(sum[:])
}
// CachedSessionID returns a stable session UUID per apiKey, refreshing the TTL on each access.
func CachedSessionID(apiKey string) string {
if apiKey == "" {
return uuid.New().String()
}
sessionIDCacheCleanupOnce.Do(startSessionIDCacheCleanup)
key := sessionIDCacheKey(apiKey)
now := time.Now()
sessionIDCacheMu.RLock()
entry, ok := sessionIDCache[key]
valid := ok && entry.value != "" && entry.expire.After(now)
sessionIDCacheMu.RUnlock()
if valid {
sessionIDCacheMu.Lock()
entry = sessionIDCache[key]
if entry.value != "" && entry.expire.After(now) {
entry.expire = now.Add(sessionIDTTL)
sessionIDCache[key] = entry
sessionIDCacheMu.Unlock()
return entry.value
}
sessionIDCacheMu.Unlock()
}
newID := uuid.New().String()
sessionIDCacheMu.Lock()
entry, ok = sessionIDCache[key]
if !ok || entry.value == "" || !entry.expire.After(now) {
entry.value = newID
}
entry.expire = now.Add(sessionIDTTL)
sessionIDCache[key] = entry
sessionIDCacheMu.Unlock()
return entry.value
}