# Plan: Áp dụng `@wxt-dev/storage` vào dự án

## Bối cảnh

Dự án hiện có **hai lớp storage** cho settings và state:

1. **`localStorage`** (sync) — primary read/write trong `apiLocalSetting`
2. **`chrome.storage.local`** (async) — secondary, được write song song, dùng để persist khi localStorage không khả dụng (service worker context)

Cả hai được wrap qua:
- `src/app/browser/storage.js` — thin wrapper cho `chrome.storage.local`
- `src/app/plugins/setting/api.js` — `apiLocalSetting()` factory, dual-write vào cả localStorage và chrome.storage

**Vấn đề hiện tại:**
- Setting keys là plain string, không có type safety
- Không có versioning — khi schema thay đổi, data cũ gây bug âm thầm
- Không có watch/reactive trên `chrome.storage` (chỉ có `useStorage` từ VueUse cho localStorage)
- Background service worker không có `localStorage` → phải dùng async `getSync()` riêng, dễ quên

---

## `@wxt-dev/storage` là gì

Package chính thức của WXT, wrapper cho `chrome.storage` với:

- **Typed** `storage.defineItem<T>(key, options)` — biết chính xác kiểu data
- **Versioning + Migration** — tự động migrate data cũ khi schema thay đổi
- **`.watch()`** — subscribe thay đổi reactive, chạy được ở cả background lẫn content script
- **Fallback** — trả về default value thay vì `null`
- **Key prefix** — `local:`, `sync:`, `session:`, `managed:`

---

## Phạm vi áp dụng

### Có thể áp dụng: `chrome.storage.local` operations

`@wxt-dev/storage` thay thế trực tiếp `src/app/browser/storage.js` + phần chrome.storage trong `apiLocalSetting`.

### Không áp dụng: KV Store (`src/app/plugins/kv/`)

KV Store gọi API server bên ngoài — có TTL server-side, lock/unlock, encryption, cross-device. `@wxt-dev/storage` chỉ là `chrome.storage` — không thể thay thế.

### Không áp dụng: `localStorage` trực tiếp

`apiLocalSetting` dùng `localStorage.getItem` để đọc sync (tránh await). `@wxt-dev/storage` là async-only. Không thể thay thế phần sync read này.

---

## Hướng tiếp cận: Thêm versioning + watch — không rewrite toàn bộ

Thay vì rewrite `apiLocalSetting`, dùng `@wxt-dev/storage` song song để:
1. Định nghĩa typed storage items cho các setting quan trọng
2. Thêm versioning/migration khi schema cần thay đổi
3. Dùng `.watch()` để sync state giữa background ↔ popup/options

---

## Cài đặt

```bash
npm install @wxt-dev/storage
```

Không cần thêm module vào `wxt.config.ts` (đây là package độc lập, không phải WXT module).

---

## Implementation

### Bước 1 — Tạo file định nghĩa storage items

Tạo `src/app/plugins/storage/items.js`:

```js
import { storage } from 'wxt/storage';

// --- User ---
export const storageUserId = storage.defineItem('local:user_id', {
  fallback: '',
});

export const storageUserToken = storage.defineItem('local:user_token', {
  fallback: '',
});

export const storageUserPlan = storage.defineItem('local:user_extension_plan', {
  fallback: 'Free',
});

// --- App ---
export const storageToggleMenu = storage.defineItem('local:toggle_menu', {
  fallback: false,
});

export const storageExtensionInfo = storage.defineItem('local:config_extension_data', {
  fallback: {},
  version: 1,
});

export const storagePlatformShopConfig = storage.defineItem('local:config_platform_shop_data', {
  fallback: { shopee: {}, lazada: {}, tiktok: {} },
  version: 1,
});

// --- Shopee ---
export const storageShopeeShopId = storage.defineItem('local:shopee_shop_id', {
  fallback: '',
});

export const storageShopeeConfigProduct = storage.defineItem('local:shopee_config_product_v2', {
  fallback: {
    spyDetail: true,
    spySearch: true,
    spyAds: true,
    showShopName: false,
    addCloneBtn: false,
  },
  version: 1,
});

// --- Tiktok ---
export const storageTiktokShopId = storage.defineItem('local:tiktok_shop_id', {
  fallback: '',
});

// --- Lazada ---
export const storageLazadaShopId = storage.defineItem('local:lazada_shop_id', {
  fallback: '',
});
```

**Lưu ý key**: `local:user_id` map trực tiếp đến key `user_id` trong `chrome.storage.local` — **cùng key** với `apiLocalSetting`, nên đọc được data đã có sẵn.

---

### Bước 2 — Dùng `.watch()` để sync state background ↔ popup

Ví dụ: Background cần biết khi `shopeeShopId` thay đổi (user đổi shop):

```js
// src/background/index.js (hoặc service phù hợp)
import { storageShopeeShopId } from '@/plugins/storage/items';

storageShopeeShopId.watch((newShopId, oldShopId) => {
  if (newShopId !== oldShopId) {
    // reload shop data
    onShopChange(newShopId);
  }
});
```

Ví dụ: Popup component reactive với storage:

```js
// Trong Vue component (Options API)
import { storageToggleMenu } from '@/plugins/storage/items';

export default {
  data() {
    return { toggleMenu: false };
  },
  async created() {
    this.toggleMenu = await storageToggleMenu.getValue();

    this._unwatch = storageToggleMenu.watch((val) => {
      this.toggleMenu = val;
    });
  },
  beforeDestroy() {
    this._unwatch?.();
  },
};
```

---

### Bước 3 — Thêm migration khi schema thay đổi

Ví dụ: `shopee_config_product_v2` cần thêm field `showSoldCount`:

```js
export const storageShopeeConfigProduct = storage.defineItem('local:shopee_config_product_v2', {
  fallback: {
    spyDetail: true,
    spySearch: true,
    spyAds: true,
    showShopName: false,
    addCloneBtn: false,
    showSoldCount: false, // field mới
  },
  version: 2, // tăng version
  migrations: {
    2: (old) => ({
      ...old,
      showSoldCount: false, // thêm field mới với default
    }),
  },
});
```

WXT tự chạy migration khi extension load và phát hiện version cũ trong storage.

---

### Bước 4 — Dùng trong background service worker (không có localStorage)

Background hiện phải dùng `getSync()` async riêng. Với `@wxt-dev/storage`, API thống nhất:

```js
// Trước — phải nhớ dùng getSync() ở background
const shopId = await shopeeSetting.shopeeShopId.getSync();

// Sau — cùng API ở mọi context
import { storageShopeeShopId } from '@/plugins/storage/items';
const shopId = await storageShopeeShopId.getValue();
```

---

## Không thay đổi

- `apiLocalSetting` — giữ nguyên, vẫn là primary cho Settings page UI (sync read)
- `src/app/browser/storage.js` — giữ nguyên, vẫn được dùng bởi `apiLocalSetting`
- KV Store — không liên quan
- `vuex-persistedstate` / `pinia-plugin-persistedstate` — giữ nguyên

---

## Ưu tiên triển khai

| Item | Lý do | Effort |
|---|---|---|
| Watch `shopeeShopId`, `tiktokShopId`, `lazadaShopId` | Background cần detect đổi shop để reload data | Thấp |
| Watch `userToken` | Background cần biết login/logout | Thấp |
| Migration cho `storageShopeeConfigProduct` | Hay thêm field mới, data cũ thiếu field → undefined bug | Thấp |
| Migration cho `storagePlatformShopConfig` | Cấu trúc object hay thay đổi | Thấp |
| Thay `storageGet/storageSet` trong background bằng `@wxt-dev/storage` | Thống nhất API | Trung bình |

---

## Lưu ý key mapping

`@wxt-dev/storage` prefix `local:` map vào `chrome.storage.local`. Ví dụ:

| Code | Thực tế trong chrome.storage |
|---|---|
| `local:user_id` | `chrome.storage.local['user_id']` |
| `local:shopee_shop_id` | `chrome.storage.local['shopee_shop_id']` |

Key phải khớp chính xác với key trong `setting.js` để đọc được data cũ. Kiểm tra `setting.js` để lấy đúng key string trước khi định nghĩa item.
