更新外观模块

This commit is contained in:
super
2026-05-06 22:41:26 +08:00
parent a00ff1804c
commit 8cd1a4e2b1
24 changed files with 1378 additions and 175 deletions

View File

@@ -0,0 +1,156 @@
export type ApiSecretModuleKey = 'appearance-patent' | 'similar-asin'
export type ApiSecretRetention = 'session' | '1d' | '7d' | '30d' | 'forever'
type ApiSecretRecord = {
value: string
retention: ApiSecretRetention
expiresAt: number | null
updatedAt: number
}
export type ApiSecretSnapshot = {
value: string
retention: ApiSecretRetention
expiresAt: number | null
updatedAt: number | null
exists: boolean
}
const STORAGE_PREFIX = 'brand:api-secret'
function currentUserStorageId() {
if (typeof window === 'undefined') return '0'
return window.localStorage.getItem('uid') || '0'
}
function buildStorageKey(moduleKey: ApiSecretModuleKey) {
return `${STORAGE_PREFIX}:${currentUserStorageId()}:${moduleKey}`
}
function readStorageRecord(storage: Storage, moduleKey: ApiSecretModuleKey): ApiSecretRecord | null {
const raw = storage.getItem(buildStorageKey(moduleKey))
if (!raw) return null
try {
const parsed = JSON.parse(raw) as Partial<ApiSecretRecord>
if (typeof parsed.value !== 'string') return null
const retention = normalizeRetention(parsed.retention)
const expiresAt = typeof parsed.expiresAt === 'number' ? parsed.expiresAt : null
const updatedAt = typeof parsed.updatedAt === 'number' ? parsed.updatedAt : Date.now()
return {
value: parsed.value,
retention,
expiresAt,
updatedAt,
}
} catch {
return null
}
}
function normalizeRetention(value: unknown): ApiSecretRetention {
switch (value) {
case '1d':
case '7d':
case '30d':
case 'forever':
case 'session':
return value
default:
return 'session'
}
}
function retentionToExpiresAt(retention: ApiSecretRetention, now: number) {
switch (retention) {
case '1d':
return now + 24 * 60 * 60 * 1000
case '7d':
return now + 7 * 24 * 60 * 60 * 1000
case '30d':
return now + 30 * 24 * 60 * 60 * 1000
case 'forever':
return null
case 'session':
default:
return null
}
}
function isExpired(record: ApiSecretRecord) {
return record.expiresAt != null && record.expiresAt <= Date.now()
}
function clearStorageRecord(storage: Storage, moduleKey: ApiSecretModuleKey) {
storage.removeItem(buildStorageKey(moduleKey))
}
function getLiveRecord(moduleKey: ApiSecretModuleKey): ApiSecretRecord | null {
if (typeof window === 'undefined') return null
const sessionRecord = readStorageRecord(window.sessionStorage, moduleKey)
if (sessionRecord) {
return sessionRecord
}
const localRecord = readStorageRecord(window.localStorage, moduleKey)
if (!localRecord) return null
if (isExpired(localRecord)) {
clearStorageRecord(window.localStorage, moduleKey)
return null
}
return localRecord
}
export function getStoredApiSecret(moduleKey: ApiSecretModuleKey) {
return getLiveRecord(moduleKey)?.value || ''
}
export function getStoredApiSecretSnapshot(moduleKey: ApiSecretModuleKey): ApiSecretSnapshot {
const record = getLiveRecord(moduleKey)
if (!record) {
return {
value: '',
retention: 'session',
expiresAt: null,
updatedAt: null,
exists: false,
}
}
return {
value: record.value,
retention: record.retention,
expiresAt: record.expiresAt,
updatedAt: record.updatedAt,
exists: true,
}
}
export function saveStoredApiSecret(
moduleKey: ApiSecretModuleKey,
value: string,
retention: ApiSecretRetention,
) {
if (typeof window === 'undefined') return
const trimmedValue = value.trim()
clearStoredApiSecret(moduleKey)
if (!trimmedValue) return
const now = Date.now()
const record: ApiSecretRecord = {
value: trimmedValue,
retention,
expiresAt: retentionToExpiresAt(retention, now),
updatedAt: now,
}
const storage = retention === 'session' ? window.sessionStorage : window.localStorage
storage.setItem(buildStorageKey(moduleKey), JSON.stringify(record))
}
export function clearStoredApiSecret(moduleKey: ApiSecretModuleKey) {
if (typeof window === 'undefined') return
clearStorageRecord(window.sessionStorage, moduleKey)
clearStorageRecord(window.localStorage, moduleKey)
}