# Codebase Review & Improvement Backlog

> Review date: 2026-04-06 | Version: 3.4.3 | Branch: dev

---

## A. CRITICAL — Memory Leaks

### A1. Content scripts dùng native timers không cleanup

Khi extension update hoặc user navigate, content script bị invalidated nhưng `setInterval` vẫn chạy → leak CPU, memory, port connections.

| File | Line | Vấn đề |
|---|---|---|
| `src/content/shopee/client/loader.js` | 73-75 | `setInterval(() => btnImageDownloader(), 500)` — chạy vô hạn |
| `src/content/shopee/client/product/search.js` | 866 | `setInterval` trong `observeUrlChange()` — không track, không clear |
| `src/content/all/ping.js` | 8 | `setInterval(serviceWorkerKeepAlive, 5000)` — tạo port mới mỗi 5s |
| `src/content/shopee/client/product/detail.js` | — | `addEventListener` trên DOM không remove |

**Fix:** Thay bằng `ctx.setInterval()`, `ctx.setTimeout()`, `ctx.addEventListener()` từ WXT ContentScriptContext. Xem `docs/plan/wxt-migration-overview.md` mục 3.

---

### A2. Vue components dùng `setInterval` không clear trong `beforeDestroy`

Khi route change, component unmount nhưng interval tiếp tục chạy → accumulate.

| File | Line | Vấn đề |
|---|---|---|
| `src/app/vue/components/Shopee/Product/Clone/mixin.js` | 741 | `setInterval(usageStore.fetchUsage, 1000)` — **mỗi giây**, không clear |
| `src/app/vue/components/Shopee/Seller/FlashSale/mixin/List.js` | — | `this.fetchInterval = setInterval(...)` — không clear |
| `src/app/vue/store/modules/shopee/seller.js` | 416-422 | `setInterval(captchaCheck, 10000)` — chạy mãi trong store action |

**Fix:** Lưu interval ID vào `data()`, clear trong `beforeDestroy()`.

```js
data() {
  return { _usageInterval: null };
},
mounted() {
  this._usageInterval = setInterval(usageStore.fetchUsage, 1000);
},
beforeDestroy() {
  clearInterval(this._usageInterval);
},
```

---

### A3. PouchDB sync `live: true` không cancel

| File | Line | Vấn đề |
|---|---|---|
| `src/app/api/model.js` | 410 | `PouchDB.sync()` với `live: true` — handle không lưu để cancel |
| `src/app/api/model.js` | 434 | `changesListener(callback, live: true)` — không có mechanism remove |

Gọi 14+ lần từ Vue components, mỗi lần tạo sync mới không dừng cái cũ → bandwidth, CPU.

**Fix:** Lưu sync handle trong Map, cancel trước khi tạo mới.

```js
const activeSyncs = new Map();

syncRemote(remoteUrl) {
  if (activeSyncs.has(this.name)) {
    activeSyncs.get(this.name).cancel();
  }
  const sync = this.db.sync(remoteUrl, SYNC_OPTIONS);
  activeSyncs.set(this.name, sync);
  return sync;
}
```

---

### A4. Socket.io listeners accumulate

| File | Line | Vấn đề |
|---|---|---|
| `src/background/socket/worker.js` | 242-268 | `.on()` listeners không remove, `startService()` gọi nhiều lần → duplicate |
| `src/background/socket/app.js` | 4-22 | `socketConnect()` không close socket cũ |
| `src/background/bridge/global.js` | 22 + 340 | Duplicate `onConnect` listener cho cùng port type |

**Fix:** Remove listeners trước khi add mới, hoặc dùng `.once()`, hoặc check flag.

```js
// Trước khi add listeners mới
socket.removeAllListeners(workerEvent.fetchUrlJobRecived);
socket.on(workerEvent.fetchUrlJobRecived, handler);
```

---

## B. HIGH — Bugs & Security

### B0. Runtime null/undefined crashes (FIXED 2026-04-06)

Các lỗi đã fix:

| File | Vấn đề | Fix |
|---|---|---|
| `src/app/vue/store/modules/shopee/flashsale.js:44` | `SET_SHOPEE_FLASHSALE_CAMPAIGN_LOG` ghi log vào `schedulerJob.log` thay vì `campaignJob.log` | Sửa thành `campaignJob.log` |
| `src/background/bridge/global.js:259` | `allSetting[data.key].getSync()` crash khi key không tồn tại | Thêm validation trước khi access |
| `src/content/shopee/seller/product/utils.js:26` | `querySelector().value` crash khi element không tồn tại | Thêm null check |
| `src/content/shopee/seller/ads/campaign.js:12` | `querySelector().cloneNode()` crash + reference `elementAdsCampaignId.parent` không tồn tại | Thêm null check, fix reference thành `btnBase` |
| `src/content/shopee/seller/chat/index.js:30-32` | `querySelector().getAttribute()` crash khi element không có | Tách biến, thêm null guard |
| `src/content/shopee/seller/marketing/service.js:52,146-152` | `querySelector().cloneNode()` và chained querySelector crash | Thêm null checks, cache parent element |
| `src/app/api/shopee/client/product/get.js:172` | `model.extinfo.tier_index` crash khi `extinfo` undefined | Dùng `lodash.get()` với default `[]` |
| `src/app/vue/store/modules/shopee/product.js:104` | `item.extinfo.tier_index` crash khi `extinfo` undefined | Thêm fallback `(item.extinfo && item.extinfo.tier_index) \|\| []` |
| `src/background/shopee/task.js:34,56` | `getShopeeSellerFallbackInfo()` throw Error nhưng caller không catch | Thêm `.catch(() => null)` + null guard |
| `src/background/index.js` | webext-bridge race condition: `r.fingerprint` crash khi endpoint bị unload | Thêm global error handler suppress |
| `src/background/socket/worker.js:166,178,190` | `socketWorker.emit()` crash khi socket chưa connect (null) | Thêm `if (socketWorker)` guard |
| `src/background/fetch/metric/shopee.js:19-25` | `orderStats.order_info.unpaid` crash khi orderStats null | Thêm `if (orderStats && orderStats.order_info)` guard |
| `src/background/bridge/global.js:52` | `getConfigService().then()` không có `.catch()` | Thêm `.catch(() => {})` |
| `src/app/api/gpt.js:16` | `await await` double await + `result.data.content` unsafe | Bỏ double await, thêm null check |
| `src/app/api/shopee/seller/order/list.js:106` | Typo `pyaload` thay vì `payload` | Sửa tên biến |
| `src/app/api/worker/shopee.js:4,8,23,38` | `(await apiWorker.get(...)).data` crash khi response null | Tách biến, thêm null check |
| `src/content/all/listener.js:6` | `match(/:(.*?);/)[1]` crash khi regex không match | Thêm null check với fallback mime type |

---

### B1. Throw string thay vì Error object

| File | Line | Code |
|---|---|---|
| `src/app/api/shopee/fetch.js` | 40 | `throw 'MISSING_FETCH_URL'` |
| `src/app/api/shopee/fetch.js` | 15 | `reject('WAIT_ELEMENT_TIME_OUT')` |
| `src/app/vue/components/Shopee/Product/Clone/mixin.js` | 208 | `throw 'WARTERMARK_ERROR'` |

**Vấn đề:** Mất stack trace, `catch(error)` không có `.message`, `.stack`. ESLint rule `no-throw-literal` bị disable.

**Fix:** `throw new Error('MISSING_FETCH_URL')`. Enable ESLint `no-throw-literal: 'error'`.

---

### B2. Silent error swallowing

| File | Line | Code |
|---|---|---|
| `src/app/api/shopee/seller/order/list.js` | 91 | `catch (error) { return []; }` |
| `src/app/api/shopee/seller/order/list.js` | 114 | `catch (error) { return []; }` |
| `src/app/vue/components/Shopee/Product/Clone/mixin.js` | 253 | `catch (error) {}` |
| `src/app/vue/components/Shopee/Product/Clone/mixin.js` | 162 | `catch (error) {}` |

**Vấn đề:** API fail im lặng, không log, không thể debug.

**Fix:** Ít nhất log error:

```js
catch (error) {
  loggerShopee.error({ msg: 'GET_ORDER_LIST_ERROR', error });
  return [];
}
```

---

### B3. `v-html` XSS risk — 12 instances, ESLint rule disabled

| File | Line |
|---|---|
| `src/app/vue/components/Plugin/NotifyApp.vue` | 10 |
| `src/app/vue/components/Widget/BannerPopup.vue` | 38 |
| `src/app/vue/components/Shopee/Product/Clone/Index.vue` | 6, 185 |
| `src/app/vue/components/Lazada/Shopee/Upload.vue` | 85 |
| + 7 instances nữa | — |

ESLint `vue/no-v-html: 0` (disabled) tại `.eslintrc.js:68`.

**Fix:** Audit từng instance:
- Nếu data từ API/user → sanitize với DOMPurify hoặc dùng `v-text`
- Nếu data tĩnh (hardcode HTML) → chấp nhận được, thêm comment `// eslint-disable-next-line`
- Enable rule: `'vue/no-v-html': 'warn'`

---

### B4. `window.postMessage(payload, '*')` — wildcard origin

| File | Line |
|---|---|
| `src/app/bridge/content/postMessage.js` | 97 |

**Fix:** `window.postMessage(payload, window.location.origin)`

---

### B5. Double await

| File | Line | Code |
|---|---|---|
| `src/app/api/gpt.js` | 16 | `await await gptApi.post(...)` |

**Fix:** Bỏ 1 `await`.

---

### B6. Typo biến

| File | Line | Code |
|---|---|---|
| `src/app/api/shopee/seller/order/list.js` | 106 | `const pyaload = {...}` (phải là `payload`) |

---

## C. HIGH — Dependency Updates

### C1. Axios 0.20.0 — released 11/2020

5+ năm, nhiều CVE. Current: `^1.7.x`.

**Breaking changes cần check:**
- Interceptor API thay đổi nhẹ
- `transformResponse` behavior
- `@vespaiach/axios-fetch-adapter` compat

**Effort:** Trung bình (test toàn bộ API layer)

---

### C2. Tailwind 1.9.6 — released 12/2020

5+ năm, thiếu nhiều utility classes. Current: `^3.4.x`.

**Breaking changes:**
- Config format thay đổi (`purge` → `content`)
- Một số class names đổi
- Plugin API thay đổi

**Effort:** Cao (review toàn bộ template classes)

---

### C3. Moment.js — deprecated

Đang dùng 3 packages: `moment`, `moment-mini`, `moment-timezone`.

**Alternative:** `dayjs` — API tương tự, 2KB thay vì 70KB.

**Effort:** Trung bình (find-replace + test date formatting)

---

### C4. Vue 2.7.10 — LTS ended 12/2024

Vue 2 không còn nhận security patches.

**Lưu ý:** Migration sang Vue 3 là effort rất lớn (Options API → vẫn compat, nhưng ecosystem: vuex 3→4, vue-router 3→4, view-design không có Vue 3 version). Đánh giá riêng.

---

## D. MEDIUM — Architecture

### D1. Dead code: `if (false)` cache logic

| File | Line |
|---|---|
| `src/app/api/shopee/seller/index.js` | 23 |
| `src/app/api/shopee/seller/index.js` | 47 |

Cache bị disable nhưng code vẫn còn. Xóa hoặc fix cache.

---

### D2. Giant store mixin — 60+ mapGetters/mapActions

| File | Vấn đề |
|---|---|
| `src/app/vue/mixin/store.js` | Map 60+ getters và actions vào MỌI component |

**Vấn đề:**
- Tight coupling — mọi component depend 60+ store items
- Không biết component nào dùng gì
- Duplicate: `getScoreClone` xuất hiện 2 lần (line 55 & 61)

**Fix:** Mỗi component tự `mapGetters`/`mapActions` chỉ những gì nó cần. Xóa global mixin dần.

---

### D3. Vuex + Pinia song song cho User store

| File | Role |
|---|---|
| `src/app/vue/pinia/user.js` | Pinia user store |
| `src/app/vue/store/modules/user/index.js` | Vuex user store |

Vuex sync sang Pinia tại `UserStore()` action. Duplicate source of truth.

**Fix dài hạn:** Chọn một, migrate dần. Pinia là hướng đi cho Vue 3.

---

### D4. Direct Vuex state mutation ngoài mutations

| File | Line | Code |
|---|---|---|
| `src/app/vue/store/modules/shopee/seller.js` | 217 | `state.ref = ''` trực tiếp trong action |

**Fix:** Tạo mutation `SET_SHOP_REF` và commit qua đó.

---

### D5. Unbounded data structures trong background

| File | Line | Vấn đề |
|---|---|---|
| `src/background/bridge/listener/shopee/product.js` | 22-86 | `pushData()` không giới hạn size |
| `src/background/store/index.js` | — | `GetListPayload` grow không limit |

Browsing 8h = 8,000-26,000 entries tích tụ trong memory.

**Fix:** Thêm size limit, flush/clear khi đạt threshold (đã có pattern `FetchListenerBucket` với limit, apply tương tự).

---

### D6. `.findAll()` / `.findMany({})` không pagination

| File | Line | Vấn đề |
|---|---|---|
| `src/app/vue/components/Shopee/Seller/FlashSale/mixin/List.js` | 501 | Fetch ALL records |
| `src/app/vue/views/PdfLog/mixin/filter.js` | 113 | Load all PDF logs |

10K+ records → memory spike.

**Fix:** Thêm `limit` + pagination, hoặc virtual scroll cho UI.

---

### D7. Oversized components (1000+ lines)

| File | Lines |
|---|---|
| `src/app/vue/views/Shopee/Seller/Auto/SendMessage/Send.vue` | 1523 |
| `src/app/vue/components/Shopee/Seller/SendMessage/ContentBlock.vue` | 1360 |
| `src/app/vue/components/Shopee/Seller/FlashSale/mixin/Create.js` | 1235 |
| `src/app/vue/views/Shopee/Seller/Auto/SendMessage/Collect.vue` | 1204 |
| `src/app/vue/components/Shopee/Product/Clone/mixin.js` | 812 |

Khó maintain, test, review. Xem xét tách thành sub-components khi có dịp sửa.

---

### D8. Inconsistent error handling patterns

3 patterns khác nhau across codebase:

| Pattern | Ví dụ | Nơi dùng |
|---|---|---|
| Silent return fallback | `catch { return text; }` | `src/app/api/gpt.js` |
| Set error flag | `catch { returnIndex.isError = true; }` | `src/app/api/shopee/seller/order/list.js` |
| Log error | `catch(error) { logger.error(...) }` | `src/app/vue/store/modules/shopee/seller.js` |

**Hướng chuẩn hóa:** Tất cả catch blocks nên log error. Return fallback value nếu cần, nhưng luôn log.

---

## E. MEDIUM — ESLint Rules nên enable

| Rule | Hiện tại | Nên đổi | Lý do |
|---|---|---|---|
| `vue/no-v-html` | `0` (off) | `'warn'` | XSS prevention |
| `no-throw-literal` | `0` (off) | `'error'` | Giữ stack trace |
| `no-console` | `0` (off) | `'warn'` | Tránh console.log trong production |
| `import/no-cycle` | `0` (off) | `'warn'` | Detect circular dependencies |

---

## F. LOW — Nice-to-have

### F1. Background 60s polling intervals không conditional

| File | Line | Code |
|---|---|---|
| `src/background/index.js` | 94 | `setInterval(shopeeShopIdentify, 60_000)` |
| `src/background/index.js` | 103 | `setInterval(tiktokShopIdentify, 60_000)` |
| `src/background/index.js` | 111 | `setInterval(lazadaShopIdentify, 60_000)` |

Chạy cả khi user idle → drain battery.

**Fix:** Dùng `chrome.alarms` (đã có permission) thay `setInterval`, hoặc check idle state.

---

### F2. Content script Vue UI không dùng Shadow DOM

UI inject trực tiếp → CSS conflict với page. Xem `docs/plan/wxt-migration-overview.md` mục 4.

---

### F3. API request deduplication

Multiple simultaneous requests tới cùng endpoint không deduplicate. Cache tồn tại nhưng bị disable (`if (false)`).

---

## Thứ tự thực hiện

| Phase | Items | Focus | Effort |
|---|---|---|---|
| **1** | A1, A2, A3, A4 | Memory leaks | 2-3 ngày |
| **2** | B1, B2, B4, B5, B6 | Bugs | 1 ngày |
| **3** | B3, E | Security + ESLint | 1-2 ngày |
| **4** | C1 | Axios update | 2-3 ngày |
| **5** | D1, D4, D5, D6 | Quick architecture wins | 2 ngày |
| **6** | C2, C3 | Tailwind + Moment update | 3-5 ngày |
| **7** | D2, D3, D7, D8 | Architecture (ongoing) | ongoing |

---

## Liên quan

- `docs/plan/wxt-migration-overview.md` — WXT API nên migrate
- `docs/plan/wxt-storage.md` — Chi tiết migrate storage
- `docs/plan/wxt-messaging.md` — Chi tiết migrate messaging + browser API
- `docs/plan/wxt-scripting.md` — Chi tiết `browser.scripting.executeScript`
