Ctrl K
ComunidadGuías…

src/lib/account-storage.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import {
  isEncryptionAvailable,
  encryptString,
  decryptString,
  storeSecureBlob,
  fetchSecureBlob,
  clearSecureBlob,
} from '@/lib/native/security';

const SESSION_BLOB_KEY = 'beaverAccountSession';
const DEVICE_BLOB_KEY = 'beaverAccountDeviceId';
const PROFILE_BLOB_KEY = 'beaverAccountProfile';

function safeStorageAvailable() {
  return isEncryptionAvailable().catch(() => false);
}

function toBase64(input) {
  if (input == null) return '';
  if (typeof input !== 'string') {
    input = JSON.stringify(input);
  }
  if (typeof btoa === 'function') {
    try {
      return btoa(unescape(encodeURIComponent(input)));
    } catch {
      return input;
    }
  }
  return input;
}

function fromBase64(base64) {
  if (!base64) return '';
  if (typeof atob !== 'function') return base64;
  try {
    return decodeURIComponent(escape(atob(base64)));
  } catch {
    return base64;
  }
}

export async function saveSessionToken(token) {
  if (!token) {
    await clearSessionToken();
    return;
  }
  // Always save to localStorage as backup
  try {
    const cipher = await encryptString(toBase64(token));
    localStorage.setItem(SESSION_BLOB_KEY, cipher);
    localStorage.setItem(SESSION_BLOB_KEY + '_plain', token);
  } catch {
    // encryption failed, store plain text
    localStorage.setItem(SESSION_BLOB_KEY + '_plain', token);
  }
  // Also save to secure storage if available
  if (await safeStorageAvailable()) {
    try {
      const cipher = await encryptString(toBase64(token));
      await storeSecureBlob(SESSION_BLOB_KEY, cipher);
    } catch {
      // secure storage failed, localStorage backup is already saved
    }
  }
}

export async function loadSessionToken() {
  try {
    // Try secure storage first
    if (await safeStorageAvailable()) {
      try {
        const cipher = await fetchSecureBlob(SESSION_BLOB_KEY);
        if (cipher) {
          const plain = await decryptString(cipher);
          return fromBase64(plain) || null;
        }
      } catch {
        // Fall through to localStorage
      }
    }
    // Try localStorage with decryption
    const cipher = localStorage.getItem(SESSION_BLOB_KEY);
    if (cipher) {
      try {
        const plain = await decryptString(cipher);
        return fromBase64(plain) || null;
      } catch {
        // Decryption failed, try plain text
      }
    }
    // Last resort: plain text fallback
    const plain = localStorage.getItem(SESSION_BLOB_KEY + '_plain');
    return plain || null;
  } catch (err) {
    console.error('[accountStorage] loadSessionToken failed:', err);
    return null;
  }
}

export async function clearSessionToken() {
  try {
    await clearSecureBlob(SESSION_BLOB_KEY);
  } catch (err) {
    console.error('[accountStorage] clearSessionToken failed:', err);
  }
}

export async function saveCachedProfile(profile) {
  if (!profile) {
    await clearCachedProfile();
    return;
  }
  try {
    if (!(await safeStorageAvailable())) return;
    const cipher = await encryptString(toBase64(profile));
    await storeSecureBlob(PROFILE_BLOB_KEY, cipher);
  } catch (err) {
    console.error('[accountStorage] saveCachedProfile failed:', err);
  }
}

export async function loadCachedProfile() {
  try {
    if (!(await safeStorageAvailable())) return null;
    const cipher = await fetchSecureBlob(PROFILE_BLOB_KEY);
    if (!cipher) return null;
    const plain = await decryptString(cipher);
    return JSON.parse(fromBase64(plain) || 'null');
  } catch (err) {
    console.error('[accountStorage] loadCachedProfile failed:', err);
    return null;
  }
}

export async function clearCachedProfile() {
  try {
    await clearSecureBlob(PROFILE_BLOB_KEY);
  } catch (err) {
    console.error('[accountStorage] clearCachedProfile failed:', err);
  }
}

export async function saveAccountDeviceId(deviceId) {
  if (!deviceId) {
    await clearAccountDeviceId();
    return;
  }
  try {
    if (!(await safeStorageAvailable())) return;
    const cipher = await encryptString(toBase64(deviceId));
    await storeSecureBlob(DEVICE_BLOB_KEY, cipher);
  } catch (err) {
    console.error('[accountStorage] saveAccountDeviceId failed:', err);
  }
}

export async function loadAccountDeviceId() {
  try {
    if (!(await safeStorageAvailable())) return null;
    const cipher = await fetchSecureBlob(DEVICE_BLOB_KEY);
    if (!cipher) return null;
    const plain = await decryptString(cipher);
    return fromBase64(plain) || null;
  } catch (err) {
    console.error('[accountStorage] loadAccountDeviceId failed:', err);
    return null;
  }
}

export async function clearAccountDeviceId() {
  try {
    await clearSecureBlob(DEVICE_BLOB_KEY);
  } catch (err) {
    console.error('[accountStorage] clearAccountDeviceId failed:', err);
  }
}

export async function clearAllAccountStorage() {
  await Promise.all([
    clearSessionToken(),
    clearCachedProfile(),
    clearAccountDeviceId(),
  ]);
}

export function useAccountStorage() {
  return {
    saveSessionToken,
    loadSessionToken,
    clearSessionToken,
    saveCachedProfile,
    loadCachedProfile,
    clearCachedProfile,
    saveAccountDeviceId,
    loadAccountDeviceId,
    clearAccountDeviceId,
    clearAllAccountStorage,
  };
}