This commit is contained in:
parent
3145d08c30
commit
0bdb3fb774
41
.env.example
41
.env.example
|
|
@ -1,19 +1,38 @@
|
||||||
# Required API Keys
|
# ===== API Keys =====
|
||||||
OPENAI_API_KEY=openai_api_key_placeholder
|
# Note: In production, users provide their own API keys via the web interface.
|
||||||
|
# These server-side keys are optional and only used for CLI or testing.
|
||||||
|
|
||||||
# Optional API Keys (for alternative LLM providers)
|
# Optional: Server-side API Keys (not required for web interface)
|
||||||
ANTHROPIC_API_KEY=anthropic_api_key_placeholder
|
OPENAI_API_KEY=
|
||||||
GEMINI_API_KEY=GEMINI_API_KEY_placeholder
|
ANTHROPIC_API_KEY=
|
||||||
XAI_API_KEY=XAI_API_KEY_placeholder
|
GEMINI_API_KEY=
|
||||||
DEEPSEEK_API_KEY=deepseek_api_key_placeholder
|
XAI_API_KEY=
|
||||||
DASHSCOPE_API_KEY=dashscope_api_key_placeholder
|
DEEPSEEK_API_KEY=
|
||||||
|
DASHSCOPE_API_KEY=
|
||||||
|
|
||||||
# Optional API Keys (for data sources)
|
# Optional API Keys (for data sources)
|
||||||
ALPHA_VANTAGE_API_KEY=alpha_vantage_api_key_placeholder
|
ALPHA_VANTAGE_API_KEY=
|
||||||
FINMIND_API_KEY=finmind_api_key_placeholder
|
FINMIND_API_KEY=
|
||||||
|
|
||||||
|
|
||||||
# Deployment Configuration
|
# ===== Security Configuration =====
|
||||||
|
|
||||||
|
# JWT Secret - IMPORTANT: Change this in production!
|
||||||
|
# Generate a secure secret: openssl rand -hex 32
|
||||||
|
JWT_SECRET=dev-secret-please-change-in-production
|
||||||
|
|
||||||
|
# CORS Origins - Comma-separated list of allowed frontend URLs
|
||||||
|
# Example: CORS_ORIGINS=https://your-app.railway.app,https://your-frontend.vercel.app
|
||||||
|
# Leave empty to use default wildcard origins (less secure)
|
||||||
|
CORS_ORIGINS=
|
||||||
|
|
||||||
|
# Google OAuth (optional - for user authentication and persistent storage)
|
||||||
|
GOOGLE_CLIENT_ID=
|
||||||
|
GOOGLE_CLIENT_SECRET=
|
||||||
|
FRONTEND_URL=http://localhost:3000
|
||||||
|
|
||||||
|
|
||||||
|
# ===== Deployment Configuration =====
|
||||||
TRADINGAGENTS_RESULTS_DIR=/app/results
|
TRADINGAGENTS_RESULTS_DIR=/app/results
|
||||||
PYTHON_VERSION=3.13
|
PYTHON_VERSION=3.13
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,70 @@
|
||||||
"""
|
"""
|
||||||
Shared dependencies for API routes
|
Shared dependencies for API routes
|
||||||
"""
|
"""
|
||||||
from fastapi import Depends
|
from typing import Optional, Dict, Any
|
||||||
|
from fastapi import Depends, HTTPException, Header
|
||||||
from backend.app.services.trading_service import TradingService, trading_service
|
from backend.app.services.trading_service import TradingService, trading_service
|
||||||
|
from backend.app.services.auth_utils import verify_access_token
|
||||||
|
|
||||||
|
|
||||||
def get_trading_service() -> TradingService:
|
def get_trading_service() -> TradingService:
|
||||||
"""Dependency to get trading service instance"""
|
"""Dependency to get trading service instance"""
|
||||||
return trading_service
|
return trading_service
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user_optional(
|
||||||
|
authorization: Optional[str] = Header(None)
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get current user from JWT token (optional - returns None if not authenticated)
|
||||||
|
|
||||||
|
Use this for endpoints that work both with and without authentication.
|
||||||
|
"""
|
||||||
|
if not authorization or not authorization.startswith("Bearer "):
|
||||||
|
return None
|
||||||
|
|
||||||
|
token = authorization.replace("Bearer ", "")
|
||||||
|
payload = verify_access_token(token)
|
||||||
|
|
||||||
|
if not payload:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": payload.get("sub"),
|
||||||
|
"email": payload.get("email"),
|
||||||
|
"name": payload.get("name"),
|
||||||
|
"avatar_url": payload.get("avatar_url"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user_required(
|
||||||
|
authorization: Optional[str] = Header(None)
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get current user from JWT token (required - raises 401 if not authenticated)
|
||||||
|
|
||||||
|
Use this for endpoints that require authentication.
|
||||||
|
"""
|
||||||
|
if not authorization or not authorization.startswith("Bearer "):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail="Authentication required. Please login to use this feature.",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
token = authorization.replace("Bearer ", "")
|
||||||
|
payload = verify_access_token(token)
|
||||||
|
|
||||||
|
if not payload:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail="Invalid or expired token. Please login again.",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": payload.get("sub"),
|
||||||
|
"email": payload.get("email"),
|
||||||
|
"name": payload.get("name"),
|
||||||
|
"avatar_url": payload.get("avatar_url"),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from backend.app.models.schemas import (
|
||||||
)
|
)
|
||||||
from backend.app.services.trading_service import TradingService
|
from backend.app.services.trading_service import TradingService
|
||||||
from backend.app.services.task_manager import task_manager
|
from backend.app.services.task_manager import task_manager
|
||||||
from backend.app.api.dependencies import get_trading_service
|
from backend.app.api.dependencies import get_trading_service, get_current_user_optional
|
||||||
from backend.app.core.config import settings
|
from backend.app.core.config import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -54,6 +54,7 @@ async def get_config(service: TradingService = Depends(get_trading_service)):
|
||||||
async def run_analysis(
|
async def run_analysis(
|
||||||
request: AnalysisRequest,
|
request: AnalysisRequest,
|
||||||
service: TradingService = Depends(get_trading_service),
|
service: TradingService = Depends(get_trading_service),
|
||||||
|
current_user: dict = Depends(get_current_user_optional),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Start an async trading analysis task.
|
Start an async trading analysis task.
|
||||||
|
|
@ -61,19 +62,42 @@ async def run_analysis(
|
||||||
This endpoint creates an async task and returns immediately with a task ID.
|
This endpoint creates an async task and returns immediately with a task ID.
|
||||||
Use the /api/task/{task_id} endpoint to check the status and get results.
|
Use the /api/task/{task_id} endpoint to check the status and get results.
|
||||||
|
|
||||||
|
Authentication: Required by default. Set REQUIRE_AUTH_FOR_ANALYZE=false to disable.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: Analysis request configuration
|
request: Analysis request configuration
|
||||||
service: Trading service instance (injected)
|
service: Trading service instance (injected)
|
||||||
|
current_user: Authenticated user (optional based on config)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
TaskCreatedResponse: Task ID and initial status
|
TaskCreatedResponse: Task ID and initial status
|
||||||
"""
|
"""
|
||||||
logger.info(f"Creating analysis task for {request.ticker} on {request.analysis_date}")
|
# Validate that user provides their own API key
|
||||||
|
# This allows both authenticated and anonymous users to use the service
|
||||||
|
# as long as they provide their own LLM API key
|
||||||
|
has_api_key = bool(
|
||||||
|
request.openai_api_key or
|
||||||
|
request.quick_think_api_key or
|
||||||
|
request.deep_think_api_key
|
||||||
|
)
|
||||||
|
|
||||||
# Create task in Redis
|
if not has_api_key:
|
||||||
|
from fastapi import HTTPException
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="API Key required. Please provide your own LLM API key (OpenAI, Anthropic, etc.) to use the analysis service.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log with user info for tracking
|
||||||
|
user_info = f"user={current_user['email']}" if current_user else "user=anonymous"
|
||||||
|
logger.info(f"Creating analysis task for {request.ticker} on {request.analysis_date} ({user_info})")
|
||||||
|
|
||||||
|
# Create task in Redis with user info
|
||||||
task_id = task_manager.create_task({
|
task_id = task_manager.create_task({
|
||||||
"ticker": request.ticker,
|
"ticker": request.ticker,
|
||||||
"analysis_date": request.analysis_date,
|
"analysis_date": request.analysis_date,
|
||||||
|
"user_id": current_user["id"] if current_user else None,
|
||||||
|
"user_email": current_user["email"] if current_user else None,
|
||||||
})
|
})
|
||||||
|
|
||||||
# Start background analysis
|
# Start background analysis
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,29 @@ class Settings(BaseSettings):
|
||||||
alpha_vantage_api_key: Optional[str] = None
|
alpha_vantage_api_key: Optional[str] = None
|
||||||
|
|
||||||
# CORS Configuration
|
# CORS Configuration
|
||||||
cors_origins: list = [
|
# Set CORS_ORIGINS environment variable to comma-separated list of allowed origins
|
||||||
"http://localhost:3000",
|
# Example: CORS_ORIGINS=https://your-app.railway.app,https://your-frontend.vercel.app
|
||||||
"http://frontend:3000",
|
cors_origins_str: Optional[str] = Field(default=None, alias="CORS_ORIGINS")
|
||||||
"https://*.vercel.app", # Vercel deployments
|
|
||||||
"https://*.onrender.com", # Render deployments
|
@property
|
||||||
"https://*.railway.app", # Railway deployments
|
def cors_origins(self) -> list:
|
||||||
]
|
"""Get CORS origins from environment or use defaults"""
|
||||||
|
if self.cors_origins_str:
|
||||||
|
# Parse comma-separated origins from environment
|
||||||
|
origins = [o.strip() for o in self.cors_origins_str.split(",") if o.strip()]
|
||||||
|
# Always include localhost for development
|
||||||
|
if "http://localhost:3000" not in origins:
|
||||||
|
origins.append("http://localhost:3000")
|
||||||
|
return origins
|
||||||
|
|
||||||
|
# Default origins (fallback - consider removing wildcards in production)
|
||||||
|
return [
|
||||||
|
"http://localhost:3000",
|
||||||
|
"http://frontend:3000",
|
||||||
|
"https://*.vercel.app", # Vercel deployments
|
||||||
|
"https://*.onrender.com", # Render deployments
|
||||||
|
"https://*.railway.app", # Railway deployments
|
||||||
|
]
|
||||||
|
|
||||||
# TradingAgentsX Configuration
|
# TradingAgentsX Configuration
|
||||||
results_dir: str = "./results"
|
results_dir: str = "./results"
|
||||||
|
|
@ -40,6 +56,7 @@ class Settings(BaseSettings):
|
||||||
env_file = ".env"
|
env_file = ".env"
|
||||||
case_sensitive = False
|
case_sensitive = False
|
||||||
extra = "ignore" # Ignore extra environment variables like ANTHROPIC_API_KEY, etc.
|
extra = "ignore" # Ignore extra environment variables like ANTHROPIC_API_KEY, etc.
|
||||||
|
populate_by_name = True # Allow using alias names
|
||||||
|
|
||||||
|
|
||||||
# Global settings instance
|
# Global settings instance
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
/**
|
/**
|
||||||
* localStorage utility for API settings with encryption
|
* localStorage/sessionStorage utility for API settings with encryption
|
||||||
* API keys are encrypted using AES-256-GCM before storage
|
*
|
||||||
|
* Storage Strategy:
|
||||||
|
* - Logged-in users: localStorage with encryption (persistent)
|
||||||
|
* - Anonymous users: sessionStorage (cleared on browser close)
|
||||||
|
*
|
||||||
|
* API keys are encrypted using AES-256-GCM before storage (localStorage only)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { encryptObject, decryptObject, isEncrypted, clearCryptoData } from "./crypto";
|
import { encryptObject, decryptObject, isEncrypted, clearCryptoData } from "./crypto";
|
||||||
|
|
@ -23,8 +28,13 @@ export interface ApiSettings {
|
||||||
custom_api_key: string;
|
custom_api_key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Storage keys
|
||||||
const STORAGE_KEY = "tradingagents_api_settings";
|
const STORAGE_KEY = "tradingagents_api_settings";
|
||||||
const ENCRYPTED_FLAG_KEY = "tradingagents_encrypted";
|
const ENCRYPTED_FLAG_KEY = "tradingagents_encrypted";
|
||||||
|
const AUTH_TOKEN_KEY = "tradingagents_auth_token";
|
||||||
|
|
||||||
|
// Storage mode type
|
||||||
|
export type StorageMode = "local" | "session";
|
||||||
|
|
||||||
export const DEFAULT_API_SETTINGS: ApiSettings = {
|
export const DEFAULT_API_SETTINGS: ApiSettings = {
|
||||||
openai_api_key: "",
|
openai_api_key: "",
|
||||||
|
|
@ -39,6 +49,52 @@ export const DEFAULT_API_SETTINGS: ApiSettings = {
|
||||||
custom_api_key: "",
|
custom_api_key: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is currently authenticated
|
||||||
|
*/
|
||||||
|
function isUserAuthenticated(): boolean {
|
||||||
|
if (typeof window === "undefined") return false;
|
||||||
|
const token = localStorage.getItem(AUTH_TOKEN_KEY);
|
||||||
|
if (!token) return false;
|
||||||
|
|
||||||
|
// Check if token is expired
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(atob(token.split(".")[1]));
|
||||||
|
const exp = payload.exp * 1000;
|
||||||
|
return Date.now() < exp;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the appropriate storage based on authentication status
|
||||||
|
* - Authenticated: localStorage (persistent)
|
||||||
|
* - Anonymous: sessionStorage (cleared on browser close)
|
||||||
|
*/
|
||||||
|
function getStorage(): Storage {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
// Return a mock storage for SSR
|
||||||
|
return {
|
||||||
|
getItem: () => null,
|
||||||
|
setItem: () => {},
|
||||||
|
removeItem: () => {},
|
||||||
|
clear: () => {},
|
||||||
|
length: 0,
|
||||||
|
key: () => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return isUserAuthenticated() ? localStorage : sessionStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current storage mode
|
||||||
|
*/
|
||||||
|
export function getCurrentStorageMode(): StorageMode {
|
||||||
|
return isUserAuthenticated() ? "local" : "session";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if stored data is using legacy (unencrypted) format
|
* Check if stored data is using legacy (unencrypted) format
|
||||||
*/
|
*/
|
||||||
|
|
@ -72,30 +128,55 @@ function isLegacyFormat(): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get API settings from localStorage (async due to decryption)
|
* Get API settings (async due to potential decryption)
|
||||||
|
*
|
||||||
|
* Storage strategy:
|
||||||
|
* - Authenticated users: Read from localStorage (encrypted)
|
||||||
|
* - Anonymous users: Read from sessionStorage (plaintext, cleared on browser close)
|
||||||
*/
|
*/
|
||||||
export async function getApiSettingsAsync(): Promise<ApiSettings> {
|
export async function getApiSettingsAsync(): Promise<ApiSettings> {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return DEFAULT_API_SETTINGS;
|
return DEFAULT_API_SETTINGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storage = getStorage();
|
||||||
|
const authenticated = isUserAuthenticated();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(STORAGE_KEY);
|
const stored = storage.getItem(STORAGE_KEY);
|
||||||
if (!stored) {
|
if (!stored) {
|
||||||
|
// If authenticated, also check sessionStorage for data to migrate
|
||||||
|
if (authenticated) {
|
||||||
|
const sessionData = sessionStorage.getItem(STORAGE_KEY);
|
||||||
|
if (sessionData) {
|
||||||
|
console.log("Migrating session data to localStorage after login");
|
||||||
|
const parsed = JSON.parse(sessionData);
|
||||||
|
const merged = { ...DEFAULT_API_SETTINGS, ...parsed };
|
||||||
|
await saveApiSettingsAsync(merged);
|
||||||
|
sessionStorage.removeItem(STORAGE_KEY);
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
}
|
||||||
return DEFAULT_API_SETTINGS;
|
return DEFAULT_API_SETTINGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = JSON.parse(stored);
|
const parsed = JSON.parse(stored);
|
||||||
|
|
||||||
// If legacy format detected, return as-is (will be encrypted on next save)
|
// For authenticated users, decrypt the data
|
||||||
if (isLegacyFormat()) {
|
if (authenticated) {
|
||||||
console.warn("Legacy unencrypted settings detected. Will encrypt on next save.");
|
// If legacy format detected, return as-is (will be encrypted on next save)
|
||||||
return { ...DEFAULT_API_SETTINGS, ...parsed };
|
if (isLegacyFormat()) {
|
||||||
|
console.warn("Legacy unencrypted settings detected. Will encrypt on next save.");
|
||||||
|
return { ...DEFAULT_API_SETTINGS, ...parsed };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the settings
|
||||||
|
const decrypted = await decryptObject(parsed);
|
||||||
|
return { ...DEFAULT_API_SETTINGS, ...decrypted };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt the settings
|
// For anonymous users, data is stored as plaintext in sessionStorage
|
||||||
const decrypted = await decryptObject(parsed);
|
return { ...DEFAULT_API_SETTINGS, ...parsed };
|
||||||
return { ...DEFAULT_API_SETTINGS, ...decrypted };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error reading API settings:", error);
|
console.error("Error reading API settings:", error);
|
||||||
return DEFAULT_API_SETTINGS;
|
return DEFAULT_API_SETTINGS;
|
||||||
|
|
@ -111,33 +192,49 @@ export function getApiSettings(): ApiSettings {
|
||||||
return DEFAULT_API_SETTINGS;
|
return DEFAULT_API_SETTINGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storage = getStorage();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(STORAGE_KEY);
|
const stored = storage.getItem(STORAGE_KEY);
|
||||||
if (stored) {
|
if (stored) {
|
||||||
const parsed = JSON.parse(stored);
|
const parsed = JSON.parse(stored);
|
||||||
return { ...DEFAULT_API_SETTINGS, ...parsed };
|
return { ...DEFAULT_API_SETTINGS, ...parsed };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error reading API settings from localStorage:", error);
|
console.error("Error reading API settings:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DEFAULT_API_SETTINGS;
|
return DEFAULT_API_SETTINGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save API settings to localStorage with encryption
|
* Save API settings (async)
|
||||||
|
*
|
||||||
|
* Storage strategy:
|
||||||
|
* - Authenticated users: Save to localStorage with encryption (persistent)
|
||||||
|
* - Anonymous users: Save to sessionStorage as plaintext (cleared on browser close)
|
||||||
*/
|
*/
|
||||||
export async function saveApiSettingsAsync(settings: ApiSettings): Promise<void> {
|
export async function saveApiSettingsAsync(settings: ApiSettings): Promise<void> {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const storage = getStorage();
|
||||||
// Encrypt sensitive fields
|
const authenticated = isUserAuthenticated();
|
||||||
const encrypted = await encryptObject(settings as unknown as Record<string, string>);
|
|
||||||
|
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(encrypted));
|
try {
|
||||||
localStorage.setItem(ENCRYPTED_FLAG_KEY, "true");
|
if (authenticated) {
|
||||||
|
// For authenticated users, encrypt and store in localStorage
|
||||||
|
const encrypted = await encryptObject(settings as unknown as Record<string, string>);
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(encrypted));
|
||||||
|
localStorage.setItem(ENCRYPTED_FLAG_KEY, "true");
|
||||||
|
console.log("API settings saved to localStorage (encrypted)");
|
||||||
|
} else {
|
||||||
|
// For anonymous users, store as plaintext in sessionStorage
|
||||||
|
// This will be automatically cleared when browser closes
|
||||||
|
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
||||||
|
console.log("API settings saved to sessionStorage (will clear on browser close)");
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving API settings:", error);
|
console.error("Error saving API settings:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -158,7 +255,7 @@ export function saveApiSettings(settings: ApiSettings): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear API settings from localStorage
|
* Clear API settings from both localStorage and sessionStorage
|
||||||
*/
|
*/
|
||||||
export function clearApiSettings(): void {
|
export function clearApiSettings(): void {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
|
|
@ -166,11 +263,19 @@ export function clearApiSettings(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Clear from localStorage
|
||||||
localStorage.removeItem(STORAGE_KEY);
|
localStorage.removeItem(STORAGE_KEY);
|
||||||
localStorage.removeItem(ENCRYPTED_FLAG_KEY);
|
localStorage.removeItem(ENCRYPTED_FLAG_KEY);
|
||||||
|
|
||||||
|
// Clear from sessionStorage
|
||||||
|
sessionStorage.removeItem(STORAGE_KEY);
|
||||||
|
|
||||||
|
// Clear crypto data
|
||||||
clearCryptoData();
|
clearCryptoData();
|
||||||
|
|
||||||
|
console.log("API settings cleared from all storage");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error clearing API settings from localStorage:", error);
|
console.error("Error clearing API settings:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue