1. Tool Check
Domain Tool
  • Tổng quan
  • Business | Logic
    • Admin Business Logic
    • Worker & Automation Business
    • Integration & Reliability Business
    • API INTERFACE & TASK MANAGEMENT
    • SYSTEM BUSINESS OVERVIEW
    • Phân tích, nhận diện Phishing, Mainsite, Site vệ tinh,...
  • Project
    • ADMIN
      • Tổng quan
      • Architecture
        • Database Schema
        • System Architecture
        • Code Structure
      • Deployment
        • Local
        • Staging
        • Product
      • Hướng dẫn sử dụng
        • Tổng quan
        • Đăng nhập
        • Kiểm tra tự động
        • Kiểm tra thủ công
        • Xếp hạng thủ công
        • Xác nhận kiểm tra
        • Xếp hạng tự động
        • Quản lý từ khóa xếp hạng
        • Kiểm tra whitelist
        • Quản lý từ khóa
        • Truy vết tên miền
        • Quản lý volume từ khóa
        • Kiểm tra domain sở hữu
        • Lịch sử check domain
        • Kiểm tra và view source
        • Kiểm tra thương hiệu tên miền
        • Quản lý người dùng
        • Server
    • API
      • Tổng quan
      • Architecture
        • Database Schema
        • System Architecture
        • Code Structure
      • Deployment
        • Staging
        • Product
        • Local
      • API Interface
        • Public API
          • Xác thực
            • login
            • refresh token
          • Thông tin đăng nhập
            • get current user
            • changePasswordUser
          • Quản lý người dùng
            • getUserList
            • createUser
            • getUserItem
            • updateUser
            • deleteUser
            • updateActive
            • Update FCM Token
          • Quản lý từ khóa
            • Get list of Keywords
            • createKeyword
            • getKeywordItem
            • updateKeyword
            • deleteKeyword
            • deleteKeyword
            • totalKeywordBranchGroup
          • Quản lý Proxy
            • Get all Proxies
            • createProxy
            • getProxyItem
            • updateProxy
            • deleteProxy
            • getActiveProxyList
          • Branch
            • getBranchList
            • createBranch
            • getBranchItem
            • updateBranch
            • deleteBranch
          • Check Brand Domain Histories
            • Get list of Check Brand Domain Histories
            • export Check Brand Domain Histories Data
            • checkBrandDomainHistoriesCopy
            • Import data from Excel
            • exportSampleImportFile
          • Check Confirm
            • List Check Confirms
            • Create Check Confirm
            • Bulk create Check Confirms
            • Show Check Confirm
            • Approve or update confirm detail
            • Delete Check Confirm
            • Approve Check Confirm
            • Approve or Reject Check Confirm
            • List approvers
          • Check Owned Domain
            • Check owned domain
          • Check View Source
            • List current user's check view source histories
            • Check view source for domains
            • Delete all histories of current user
          • Tên Miền Công Ty
            • Get a list of company domains
            • Create a new company domain
            • Get a specific company domain
            • Update a specific company domain with tags
            • Delete a specific company domain
            • Import Whitelist Domains from Excel
            • deleteBulkWhitelistDomain
            • Delete company domains by tag ID
          • Xuất dữ liệu excel
            • Export company domains
            • autoCheckDomainRdrExport
            • exportExcelHistoryCheck
            • exportExcelHistoryRecord
            • autoCheckHistoryDomainRecordExport
            • AutoCheckHistoryDomainCheckExport
            • export Keyword Data
            • autoCheckKeywordExport
            • exportKeywordVolumes
            • exportTrackingDomain
            • exportExcelManualRankingKeywordHistory
            • exportExcelManualSessionHistoryCheck
            • exportExcelRankingDomainHistory
            • exportCompareProxies
            • export Ranking Keyword Data
            • autoCheckRankingKeywordExport
            • exportSampleImportFile
            • exportExcelRankingKeywordHistory
            • exportExcelRankingHistory
            • exportExcelDomainRankingHistory
            • exportExcelWhiteListDomainBlock
            • WhitelistBlockExportSampleImportFile
            • exportSampleImportFile
            • ExportWhitelistByTag
          • Copy
            • Copy company domains
            • autoCheckDomainRdrCopy
            • autoCheckHistoryDomainRecordCopy
            • autoCheckHistoryDomainCheckCopy
            • autoCheckKeywordCopy
            • copyKeywordVolumes
            • copyTrackingDomain
            • copyManualRankingKeywordHistory
            • copySessionHistory
            • copySessionHistory
            • manualUploadFileCopy
            • copyRankingDomainHistory
            • autoCheckRankingKeywordCopy
            • copyRankingKeywordHistory
            • copyRankingHistory
            • copyWhiteListDomainBlock
            • copyWhitelistByTag
          • Nhà cung cấp proxy
            • getDataUsageHistory
            • getProxyProviderList
            • createProxyProvider
            • getProxyProviderItem
            • updateProxyProvider
            • deleteProxyProvider
            • updateSortProxyProvider
            • getProviderProxyCombine
            • createProxyProvider
            • detailProviderProxyCombine
            • updateProxyProvider
            • deleteProviderProxyCombine
          • Quản lý Domain
            • getDomainList
            • createDomain
            • getDomainItem
            • updateDomain
            • deleteDomain
          • Chạy thủ công
            • Run Manual Domain
            • Run Manual Check Domain
            • Run Retry Record Domain
            • Run Retry check Domain
            • Run Manual Domain By Type
            • Run Manual Keyword
          • Domain Check Redirect
            • createDomainCheckRDR
            • getDomainCheckRDRItem
            • updateDomainCheckRDR
            • deleteDomainCheckRDR
            • createHistoryDomainCheck
            • updateHistoryDomainCheck
            • deleteHistoryDomainCheck
            • getListWebByKeyword
            • getListAppByKeyword
            • getListAdsByKeyword
            • getDomainCheckRDRList
          • Quản lý summary
            • Get Domain Wiki Statistics
            • Get Domain Wiki Details
            • Export Domain Wiki Data
            • Copy Domain Wiki List
            • Get Domain Wiki Chart Data
            • Get Summaries
            • Get Auto Summaries
            • Get ranking Summaries
          • External API
            • Create external record domain task
          • Flow
            • stop current flow search domain
            • start current flow search domain
            • get metric flow
            • Get domain RDR processing metrics
            • Get keyword processing metrics
            • Get status auto
          • Group
            • getGroupList
            • createGroup
            • getGroupItem
            • updateGroup
            • deleteGroup
          • Lấy lịch sử check domain
            • getHistoryDomainCheckList
            • getHistoryDomainRecordList
            • getHistoryDomainCheckItem
            • getHistoryDomainRecordItem
            • getHistoryDomainChart
            • getHistoryDomainRecordListV2
          • Ranking Keyword
            • setTimeToRunAutoKeyword
            • postRankingDomainHistory
            • compareProxiesDomainHistory
            • compareProxiesDomainHistoryCopy
            • getChartRankingDomainHistory
            • restartRankingDomainHistory
            • Get list of Ranking Keywords
            • createRankingKeyword
            • getRankingKeywordItem
            • updateRankingKeyword
            • deleteRankingKeyword
            • deleteRankingKeyword
            • totalRankingKeywordBranchGroup
            • Get keyword ranking metric
            • retryRankingKeyword
            • Update Order
            • Import data from Excel
            • Update Order by filter
            • deleteAllRankingKeyword
            • getRankingKeywordHistory
            • setTimeToRunAutoRanking
            • getTimeRunAutoRanking
            • Get ranking group list
          • Quản lý Keyword volume
            • 8ceaf215c302596b08d25b1e12e7ab45
            • Get maximum search volume
          • Quản lý Activities
            • getLogs
            • getLogs
            • trackingDomain
          • Manual Ranking Keyword
            • Get manual ranking keyword histories
            • Get manual ranking keyword history metric
            • GetJsonFileRanking
            • Get ranking group list
          • Quản lý Manual
            • Get list of Manual session domain
            • create manual session domain
            • Get Manual session domain
            • Update list manual session history by manual_sessions_id
            • deleteSession
            • Get list session history
            • Update manual session history by id
            • deleteSessionHistory
            • Get process of Process Manual session domain
            • Get session histories
            • Get statistics session history records
            • Get detailed session history records
            • Get Manual upload files
            • Create Manual upload files
            • Delete Manual upload files
            • deleteBulkManualUploadFiles
          • Ranking Branch
            • getRankingBranchList
            • createRankingBranch
            • getRankingBranchItem
            • updateRankingBranch
            • deleteRankingBranch
          • Ranking Group
            • getRankingGroupList
            • createRankingGroup
            • detailRankingGroup
            • updateRankingGroup
            • deleteRankingGroup
          • Reported Domains
            • getReportedDomainList
            • createReportedDomain
            • getReportedDomainItem
            • updateReportedDomain
            • deleteReportedDomain
            • deleteReportedDomain
            • deleteBulkReportedDomain
          • SearchEngine
            • getSearchEngineList
            • createSearchEngine
            • getSearchEngineItem
            • updateSearchEngine
            • deleteSearchEngine
          • Quản lý cụm Server
            • getServerList
            • createServer
            • getServerItem
            • updateServer
            • deleteServer
          • Statistics
            • getStatistics
          • Tag
            • getTagList
            • createTag
            • getTagItem
            • updateTag
            • deleteTag
            • deleteBulkTag
          • Webhook
            • Execute updateAfterRecordDomain action
            • Execute updateAfterRecordDomain action
            • Execute updateAfterRecordDomain action
            • Handle check confirm capture webhook
            • Execute updateAfterRecordDomain action
            • Execute externalRecordDomainSuccess action
            • Execute updateAfterRecordDomain action
            • Execute updateAfterRecordDomain action
            • Execute updateAfterCheckRankingDomain action
            • Execute updateDomainSearchRanking action
            • Execute updateDomainSearchRankingManual action
          • Quản lý whitelist domain block từ hệ thống
            • getWhiteListDomainBlockApiList
            • createWhiteListDomainBlockApi
            • getWhiteListDomainBlockApiItem
            • updateWhiteListDomainBlockApi
            • deleteWhiteListDomainBlockApi
            • deleteBulkWhiteListDomainBlockApi
          • Quản lý whitelist domain
            • getWhitelistDomainList
            • createWhitelistDomain
            • getWhitelistDomainList
            • getWhitelistDomainItem
            • updateWhitelistDomain
            • deleteWhitelistDomain
            • deleteWhitelistDomain
            • Import Whitelist Domains from Excel
            • deleteBulkWhitelistDomain
      • Hidden, automatic features
        • Gửi Ranking Result hàng ngày qua email
        • Nhận diện Mainsite/Satellite
    • Tool Check
      • Tổng quan
      • Logic Code check_domain_type.ts (Check mainsite/satellite)
      • Architecture
        • Database Schema
        • System Architecture
        • Code Structure
      • Deployment
        • Local
        • Staging
        • Product
      • API Interface
        • Public API
          • Public API
    • Tool Record
      • Tổng quan
      • Architecture
        • Database Schema
        • System Architecture
        • Code Structure
      • Deployment
        • Local
        • Staging
        • Product
      • API Interface
        • Public API
          • Task System
            • Get All Tasks
            • Create Task (List)
            • Create Task (Single)
            • Update Task
            • Delete Tasks
            • Count Processing Tasks
            • Get System Logs
            • Send single Task to Webhook
            • Send multiple Tasks to Webhooks
          • System Info
            • Root Details
            • Health Check
  1. Tool Check

Logic Code check_domain_type.ts (Check mainsite/satellite)

Tài liệu logic code: check_domain_type.ts#


1. Mục tiêu của hệ thống#

Dùng Puppeteer (trình duyệt tự động) để mở một domain, phân tích trang web và phân loại nó thành một trong 3 loại:
LoạiÝ nghĩaVí dụ thực tế
mainsiteWebsite chính — có form đăng ký / đăng nhập thật, hoặc là game canvas thậtgame88.com (có form login), game-pixi.com (canvas game)
satelliteWebsite vệ tinh — landing page có nút "mồi" dẫn người dùng sang mainsitelanding123.com (chỉ có nút "Đăng ký ngay")
unknownKhông xác định được — nước ngoài, social, lỗifacebook.com, casino-en.eu
Hàm entry point duy nhất:

2. Toàn bộ luồng xử lý — 6 bước#

URL đầu vào
    │
    ▼
[BƯỚC 1] Kiểm tra blacklist nhanh
    │   Domain thuộc facebook/google/cloudflare/... → return "unknown" NGAY (không mở browser)
    │
    ▼
[BƯỚC 2] Mở browser Puppeteer + visitPage (retry tối đa 2 lần)
    │   Thất bại hoàn toàn → return Error
    │
    ▼
[BƯỚC 3] Dynamic Polling — chờ trang render (tối đa 15s, poll mỗi 500ms)
    │   Dừng sớm khi đạt "Double-Gate": Keywords + (Inputs hoặc Canvas)
    │   Thu thập localRoots từ iframe (dùng để phân biệt redirect nội bộ sau này)
    │
    ▼
[BƯỚC 4] Kiểm tra tiếng Việt (3 tín hiệu)
    │   Không phải tiếng Việt → return "unknown" NGAY
    │
    ▼
[BƯỚC 5] CTA Scan Loop (vòng lặp chính, tối đa 4 lần nhảy domain)
    │   ├─ Đánh giá trang A ban đầu (aStatus)
    │   ├─ Đóng popup tự động
    │   ├─ Tìm Auth CTA ưu tiên → click → theo dõi redirect
    │   ├─ Nếu không có Auth CTA → quét tất cả CTA → click lần lượt
    │   └─ Nếu redirect sang domain mới → nhảy sang domain đó (hopCount++)
    │
    ▼
[BƯỚC 6] Rule A/B — phán loại cuối cùng
    │
    ▼
return { domainType, domainRedirect, statusCode }

3. Bước 1 — Kiểm tra blacklist nhanh#

Trước khi tốn tài nguyên mở browser, kiểm tra ngay xem domain có thuộc danh sách bị bỏ qua không.
Hai danh sách cần biết:
CTA_EXTERNAL_IGNORE_HOSTS — IP/hostname bị ignore hoàn toàn:
1.1.1.1, one.one.one.one
CTA_EXTERNAL_IGNORE_ROOTS — Root domain bị ignore:
facebook.com, fb.com, instagram.com, twitter.com, x.com,
youtube.com, youtu.be, t.me, telegram.org, zalo.me,
line.me, linkedin.com, tiktok.com, discord.com, discord.gg,
whatsapp.com, wa.me, google.com, apple.com, cloudflare.com,
lc.chat, snapchat.com
Muốn thêm domain mới vào blacklist: Bổ sung vào CTA_EXTERNAL_IGNORE_ROOTS trong file.
Helper functions liên quan:
normalizeDomainForType(input) — Làm sạch URL về hostname thuần:
"https://www.GAME88.com/path?q=1"  →  "game88.com"
Loại bỏ: https://, www., /path, ?query, #hash, :port → lowercase
getRegistrableDomain(host) — Lấy "root domain" để so sánh:
"sub.game88.com"    →  "game88.com"    (lấy 2 phần cuối)
"sub.game.com.vn"   →  "game.com.vn"   (com.vn là ccSLD → lấy 3 phần)
"192.168.1.1"       →  "192.168.1.1"   (IP giữ nguyên)
Lưu ý COMMON_SLD_SUFFIXES: Set chứa các ccSLD như com.vn, co.uk, com.au... Nếu thiếu một ccSLD, root sẽ bị tính sai và phán loại nhầm. Muốn thêm quốc gia mới → thêm vào Set này.

4. Bước 2 — Mở browser#

Retry tối đa 2 lần nếu visitPage() bị lỗi
Nếu URL sau visit là about:blank hoặc chrome-error:// → coi là thất bại, retry
Nếu visitPage() có throw exception nhưng URL đã load được (timeout nhẹ) → vẫn tiếp tục

5. Bước 3 — Dynamic Polling (chờ trang render)#

Sau khi trang load, đợi thêm 3 giây cơ bản rồi bắt đầu poll mỗi 500ms, tối đa 15 giây.
Mỗi lần poll, đọc từ TOÀN BỘ frames (main + iframes):
SignalCách lấy
hasKeywordsBody text chứa "dang ky" / "dang ki" / "dang nhap" (đã normalize tiếng Việt)
visibleInputCountSố <input> thực sự hiển thị (loại hidden, submit, button, radio, checkbox)
hasCanvasCó <canvas> hoặc #Cocos2dGameContainer / #GameCanvas
Điều kiện dừng sớm (Double-Gate):
CÓ keywords VÀ (inputs > 0 HOẶC canvas) → BREAK ngay lập tức
CÓ keywords VÀ đã chờ >= 4s (Grace Period) → BREAK (SPA có thể chậm)
Hết 15s → BREAK dù chưa thoả điều kiện
Thu thập localRoots: Trong lúc poll, nếu iframe nào có domain khác root chính → add vào localRoots. Dùng sau này để biết redirect sang domain đó có phải "nội bộ" không.

6. Bước 4 — Kiểm tra tiếng Việt#

Chạy trong browser context, 3 tín hiệu theo thứ tự từ nhanh đến chậm:
Tín hiệu 1 (O(1), nhanh nhất):
  html[lang="vi"] HOẶC <meta http-equiv="content-language" content="vi">
  → Có → tiếng Việt, tiếp tục

Tín hiệu 2 (metadata):
  document.title + meta[description] + meta[keywords]
  → Có ký tự tiếng Việt (àáảã...) HOẶC chứa "dang ky"/"dang nhap"
  → Có → tiếng Việt, tiếp tục

Tín hiệu 3 (body text):
  body.textContent < 50 ký tự → coi là tiếng Việt (page chưa load đủ)
  body có "dang ky"/"dang nhap"/"tai khoan" → tiếng Việt
  body có >= 3 ký tự tiếng Việt (pattern regex) → tiếng Việt
  Không đủ → return "unknown", kết thúc

7. Bước 5 — CTA Scan Loop (phần phức tạp nhất)#

7a. Biến trạng thái quan trọng#

BiếnKiểuÝ nghĩa
originalRootstringRoot domain của URL input ban đầu
postRedirectRootstringRoot domain sau khi trang tự redirect (nếu có)
activeRootstringRoot domain của trang đang xem hiện tại
aStatus"mainsite"|"satellite"Đánh giá ban đầu trang A trước khi click
resolvedDomainTypeDomainTypeKết quả tạm thời sau khi click
jumpedRootstring|nullRoot domain đích nếu CTA dẫn sang domain khác
jumpedUrlstring|nullURL đích đầy đủ
hopCountnumberSố lần đã nhảy domain (tối đa 4)
authCheckCompletedbooleanFlag để thoát vòng lặp
skippedExternalCtaSignaturesSet<string>Signature các CTA đã bỏ qua (dẫn đến social)

7b. Phát hiện game canvas (getCanvasLoaderInfo)#

Hàm này quét main frame rồi các iframe, trả về thông tin game engine:
Cocos2D → window.cc / window.CocosEngine / #Cocos2dGameContainer / #GameCanvas
Unity   → window.UnityLoader / canvas#unity-canvas
Pixi.js → window.PIXI
Script  → <script src> chứa "cocos2d*.js" / "unity*.js" / "pixi*.js" / "phaser*.js"
Canvas  → <canvas> width > 300 AND height > 300
Splash  → #splash element đang hiển thị
waitForLoaderProgress: Nếu loader đang ở "0%":
Chờ thêm tối đa 25s (poll 1s)
→ splash biến mất / text đổi / hết 0%  → dừng
→ Vẫn 0% sau 25s → set isCanvasLoaderStuck = true
isCanvasLoaderStuck = true → isCanvasLikelyMainsite() sẽ trả về true → coi là mainsite.

7c. Đánh giá trang A ban đầu (evaluateCurrentPage)#

Có Auth CTA VÀ inputs > 0          → mainsite
Có game engine mạnh (Cocos/Unity/Pixi) VÀ Auth CTA → mainsite
Có game engine mạnh VÀ KHÔNG có Auth CTA:
    Body KHÔNG có auth keywords → "Pure Game Canvas" → mainsite
    Body CÓ auth keywords       → Hybrid site → để CTA scan quyết định
isCanvasLoaderStuck = true          → mainsite
Không đủ điều kiện                  → satellite (tạm thời, chưa phán cuối)
Kết quả lưu vào aStatus — dùng sau ở Rule A/B.
Pure Game Canvas: Cocos2D/Unity/Pixi render toàn bộ UI trong canvas. Không có DOM auth, không có body keywords. Phán mainsite ngay, không cần click CTA.

7d. Đóng popup tự động (autoClosePopups)#

Chạy một lần trước vòng lặp:
1.
Nhấn Escape
2.
Tìm button Close theo CSS selector (.close-btn, .btn-close, .modal-close, [aria-label="Close"], .ant-modal-close...)
3.
Tìm theo text: "đóng", "dong", "close", "bỏ qua", "x"
4.
Tìm theo class .close trên button, a, i, img
5.
Chỉ click nếu element hoàn toàn nằm trong viewport

7e. Vòng lặp CTA chính#

Ưu tiên 1: Tìm Auth CTA chính xác (getExactAuthHandle)
Tìm element có text khớp chính xác (sau normalize NFD) với:
"dang ky" / "dang ki" / "dang nhap" / "choi ban web" / "choi ngay" / "vao game" / "web" / "choi"
Scoring để chọn element tốt nhất trong nhiều candidate:
+12  isTopMost: element không bị che phủ tại tọa độ trung tâm
 -5  bị element khác che phủ
 +5  cursor: pointer
 +4  tag là <a>, <button>, hoặc <input>
 +3  class chứa "btn" hoặc "button"
 +6  role="button"
 +6  text khớp chính xác với auth keywords
Hỗ trợ Shadow DOM: Scan đệ quy vào shadowRoot của mọi element (Web Components, LitElement...).
Hỗ trợ iframe: Kiểm tra từng iframe, bỏ qua iframe không visible trên viewport.
skippedSignatures: Bỏ qua các Auth CTA đã từng bị skip (signature = tag|text|x|y|href).

Ưu tiên 2: Quét tất cả CTA trong viewport (nếu không có Auth CTA)
Lấy tối đa 10 candidates, sort theo:
1.
CTA có "play" / "playonweb" trong text/href → lên đầu
2.
Theo vị trí: từ trên xuống, từ trái sang phải
Điều kiện một element được coi là CTA:
Tag: a, button, ion-button, input
HOẶC role="button"
HOẶC có onclick / ng-click / @click / v-on:click / ui-sref / routerlink
HOẶC class chứa "btn" / "button"
HOẶC cursor: pointer
VÀ phải visible (display≠none, visibility≠hidden, opacity>0, không disabled)
VÀ phải nằm trong viewport
VÀ href không phải mailto: / tel: / dẫn đến social

7f. Sau khi click một CTA — theo dõi redirect#

Trước khi click: Patch window.open, location.assign, location.replace để capture URL:
Click element (triggerElementClick) — 3 tầng fallback:
Tầng 1: JS dispatchEvent (mousedown → mouseup → click) với composed:true
         → Bypass z-index overlay, xuyên Shadow DOM
Tầng 2: elementHandle.click({ delay: 40ms })
         → Puppeteer chuẩn, có delay giả lập human
Tầng 3: Mouse coordinates tại bounding box center
         → Fallback cuối cùng
Thu thập URL sau click từ 3 nguồn (ưu tiên theo thứ tự):
1. window.__ctaRedirect.url  → JS redirect (assign/replace/open)
2. browser.getLastPopupUrl() → Tab/popup mới (target="_blank", window.open)
3. waitForStableUrl()        → URL tab hiện tại sau khi ổn định
waitForStableUrl(initialUrl) — Chờ URL ổn định sau redirect:
Tối đa 20s, poll 400ms
Phải quan sát ít nhất 4.5s (minObserveMs) trước khi kết luận
URL không đổi trong 1.2s liên tiếp → coi là stable
Kiểm tra popup URL mỗi lần poll (popup fires async, có thể đến trễ)

7g. Phán loại sau khi click — Decision Tree đầy đủ#

SAU KHI CLICK CTA:

CapturedRoot hoặc newUrlRoot KHÁC activeRoot?
├── Có
│   ├── Thuộc social/ignored? (isIgnoredExternalCtaDestination)
│   │   ├── Có → Add vào skippedExternalCtaSignatures
│   │   │        → restorePageIfNeeded (quay lại trang cũ)
│   │   │        → shouldRescanAfterRestore = true → tiếp tục scan
│   │   └── Không → set jumpedRoot, jumpedUrl → break khỏi CTA for-loop
│   │                → sau đó navigate sang jumpedRoot (xem mục 7h)
└── Không (cùng root hoặc không đổi)

    URL tab thay đổi trong cùng root?
    ├── Có
    │   ├── Redirect đến localRoots? → mainsite ngay
    │   ├── Đợi 1.2s → đếm input và canvas sau navigate
    │   │   ├── inputs tăng HOẶC canvas mới xuất hiện → MAINSITE
    │   │   └── Không thay đổi → tiếp tục (Fake UI, thử CTA kế)
    └── Không (URL không đổi)

        Có popup url mới?
        ├── popup root khác activeRoot và không phải social → jumpedRoot
        ├── popup root khác activeRoot nhưng là social → skip, tiếp tục
        ├── popup root = activeRoot (cùng root) → MAINSITE
        └── Không có popup

            So sánh DOM trước và sau click:
            ├── inputs tăng VÀ >= 2 HOẶC canvas mới xuất hiện → MAINSITE
            └── Không thay đổi → dead button → tiếp tục CTA kế tiếp

Nếu đã thử hết tất cả CTA, không có gì thay đổi:
→ totalCtasTested > 0: tất cả là fake/dead → SATELLITE
→ totalCtasTested = 0: kiểm tra lại Auth CTA + inputs
    → Có Auth CTA VÀ inputs > 0 → MAINSITE
    → Không đủ → SATELLITE

7h. Điều hướng sang domain mới (jumpedRoot)#

Khi jumpedRoot được set:
jumpedRoot đã từng visit? (visitedRoots)
├── Có → Phát hiện vòng lặp → SATELLITE, dừng lại
└── Không
    → Navigate đến jumpedUrl
    → Chờ Dynamic Polling thêm 15s (chờ trang mới render)
    → waitForLoaderProgress() (nếu có game loading)
    → activeRoot = jumpedRoot, hopCount++
    → autoClosePopups()
    → tiếp tục vòng lặp while (quét lại CTA trang mới)

Nếu hopCount >= maxHopCount (4) → vượt quá giới hạn → SATELLITE
Quan trọng: Trang B (domain mới) sẽ được scan theo đúng cùng quy trình — auth handle, CTA candidates, click & theo dõi — và cho ra resolvedDomainType (bStatus).

8. Bước 6 — Rule A/B (Phán loại cuối cùng)#

Sau khi vòng lặp kết thúc (authCheckCompleted = true):
bStatus = resolvedDomainType  (kết quả của trang B / trang cuối)
finalRoot = activeRoot         (root domain của trang đang xem cuối cùng)
aStatus = đánh giá ban đầu của trang A trước khi click
originalRoot = root của URL input
postRedirectRoot = root sau khi trang A tự động redirect (nếu có)

─────────────────────────────────────────────────────────
RULE 1: bStatus = "mainsite"
─────────────────────────────────────────────────────────
finalRoot == originalRoot HOẶC finalRoot == postRedirectRoot
    → A là MAINSITE  ("B cùng nhà với A, B là mainsite, vậy A cũng là mainsite")

finalRoot != originalRoot VÀ finalRoot != postRedirectRoot
    → A là SATELLITE  ("B là mainsite nhưng ở nhà khác → A chỉ là vệ tinh dẫn đến B")

─────────────────────────────────────────────────────────
RULE 2: bStatus = "satellite"
─────────────────────────────────────────────────────────
finalRoot == originalRoot HOẶC finalRoot == postRedirectRoot:
    aStatus == "mainsite" → A là MAINSITE  ("B cùng nhà nhưng là satellite, A ban đầu là mainsite → tin aStatus")
    aStatus == "satellite" → A là SATELLITE

finalRoot != originalRoot:
    → A là SATELLITE  ("B là satellite ở nhà khác → A cũng là satellite")
Ví dụ minh họa Rule A/B:
Tình huốngA.rootB.rootbStatusKết quả A
A dẫn đến chính mình (route nội bộ)game88.comgame88.commainsitemainsite
A là landing dẫn sang gameland.comgame88.commainsitesatellite
A nghi là game, trả về trang phụgame88.comgame88.comsatellitemainsite (dùng aStatus)
A → B → B cũng là landingland.comanother.comsatellitesatellite

9. Các hàm helper — Tóm tắt#

HàmVị tríMục đích
normalizeDomainForType(input)Top-levelLàm sạch URL → hostname thuần
isIpAddress(value)Top-levelKiểm tra IPv4/IPv6
getRegistrableDomain(host)Top-levelLấy root domain (xử lý ccSLD)
isIgnoredExternalCtaDestination(url)Top-levelKiểm tra có phải social/CDN không
getCanvasLoaderInfo()Trong runDetect game engine (Cocos/Unity/Pixi)
waitForLoaderProgress()Trong runChờ loader 0% tiến triển, tối đa 25s
getExactAuthHandle(skipped)Trong loopTìm Auth CTA text chính xác + scoring
getVisibleInputCount()Trong loopĐếm input field thực sự hiển thị
isCanvasLikelyMainsite()Trong loopQuyết định nhanh canvas có phải mainsite
evaluateCurrentPage()Trong loopĐánh giá trang A trước khi click
triggerElementClick(handle)Trong loopClick với 3 tầng fallback
waitForStableUrl(initial)Trong loopChờ URL ổn định sau redirect (tối đa 20s)
waitForPopupUrl()Trong loopChờ popup tab mới (tối đa 20s)
findViewportReplacementCta(x, y)Trong loopTìm lại CTA sau re-render SPA
autoClosePopups()Trong loopĐóng popup/quảng cáo tự động
shouldSkipExternalHop(url, root)Trong loopCó nên bỏ qua redirect này không
restorePageIfNeeded(before, after)Trong loopQuay lại trang cũ sau khi bị redirect sang social

10. Output và xử lý lỗi#

Browser luôn được đóng trong finally block dù có lỗi hay không
try/catch bao toàn bộ → lỗi không unhandled, luôn trả về object

11. Các điểm cần chú ý khi maintain#

[!IMPORTANT]
localRoots có 2 instance: Một ở scope ngoài (dùng trong Dynamic Polling), một ở scope trong CTA loop. Nếu debug redirect bị nhận nhầm là "nội bộ", kiểm tra cả 2 instance này.
[!WARNING]
skippedExternalCtaSignatures dùng signature tag|text|x|y|href. Nếu SPA re-render làm element dịch chuyển tọa độ, signature sẽ không khớp → CTA có thể bị click lại. Chỉ là rủi ro nhỏ vì đã có restorePageIfNeeded xử lý.
[!CAUTION]
maxHopCount = 4: Tăng lên sẽ tăng thời gian xử lý rất đáng kể (mỗi hop có thể tốn 20-40s).
[!NOTE]
shouldUseDesktopForDomainType = false: Flag tắt, chưa dùng. Khi bật, browser switch sang desktop viewport trước khi scan. Dùng cho trang chỉ show đúng trên desktop.
[!TIP]
Thêm từ khoá Auth CTA mới: Sửa hàm isAuthText() trong getExactAuthHandle. Thêm domain mới vào blacklist: sửa CTA_EXTERNAL_IGNORE_ROOTS. Thêm ccSLD mới: sửa COMMON_SLD_SUFFIXES.

12. Ví dụ thực tế — 6 case#

Case 1: Satellite điển hình#

URL: landing123.com
Bước 3: poll → thấy "Đăng ký ngay", không có input form
Bước 5: getExactAuthHandle → tìm thấy "dang ky"
         Click → redirect sang game88.com (khác root)
         jumpedRoot = "game88.com"
         Navigate sang game88.com
         game88.com có form đăng nhập thật, inputs > 0
         resolvedDomainType = "mainsite"
Bước 6: bStatus=mainsite, finalRoot=game88.com ≠ originalRoot=landing123.com
         → A = SATELLITE ✓

Case 2: Mainsite game canvas Cocos2D#

URL: game88.com
Bước 3: poll → thấy canvas, hasCocos
Bước 5: getCanvasLoaderInfo → hasCocos=true, splashVisible=true
         waitForLoaderProgress → loader tiến từ 0% → 45% → load xong
         evaluateCurrentPage → Pure Game Canvas, không có DOM auth
         → aStatus = "mainsite", authCheckCompleted = true
Bước 6: bStatus=mainsite, finalRoot=game88.com = originalRoot
         → A = MAINSITE ✓

Case 3: Domain nước ngoài#

URL: casino-eu.com
Bước 4: check tiếng Việt
         html[lang] = "en", no meta vi
         title/keywords không có ký tự VI
         body text < 3 ký tự VI
         → isVietnamesePage = false
         → return "unknown" NGAY ✓

Case 4: Fake UI (Satellite ngụy trang)#

URL: fakesite.com
Bước 5: getExactAuthHandle → tìm "Đăng nhập"
         preClickInputCount = 0
         Click → URL đổi sang fakesite.com/login (cùng root)
         Đợi 1.2s → đếm inputs tại /login → 0
         → Fake UI (URL đổi nhưng không có form thật)
         resolvedDomainType = "satellite"
Bước 6: finalRoot = originalRoot, bStatus = satellite, aStatus = satellite
         → A = SATELLITE ✓

Case 5: CTA dẫn sang Telegram — skip và tiếp tục#

URL: mixsite.com
Bước 5: getExactAuthHandle → tìm "Đăng ký"
         Click → capturedUrl = "https://t.me/game88channel"
         capturedRoot = "t.me" → isIgnoredExternalCtaDestination = true
         shouldSkipExternalHop = true
         → Add signature vào skippedExternalCtaSignatures
         → restorePageIfNeeded (browser đã chuyển sang t.me → quay về mixsite.com)
         → shouldRescanAfterRestore = true → continue vòng lặp

         Quét lại CTA (lần 2) → không có Auth CTA nào khác
         → Không có jumpedRoot, không có authCheckCompleted
         → totalCtasTested = 0 → fallback check: Auth + inputs → cả 2 = 0
         → resolvedDomainType = "satellite"
         → A = SATELLITE ✓

Case 6: Loader stuck ở 0%#

URL: gamestuck.com
Bước 5: getCanvasLoaderInfo → hasCanvas=true, splashVisible=true, rawText="0%"
         waitForLoaderProgress → chờ 25s, text vẫn "0%"
         → isCanvasLoaderStuck = true
         isCanvasLikelyMainsite() = true (vì isCanvasLoaderStuck)
         evaluateCurrentPage → canvasLikely=true, nhưng không có auth CTA
         hasStrongGameSignal = true (hasCocos)
         → không có DOM auth, không có body keywords → Pure Game Canvas
         → aStatus = "mainsite"
Bước 6: authCheckCompleted=true từ sớm
         → A = MAINSITE ✓
Modified at 2026-04-21 07:51:13
Previous
Tổng quan
Next
Database Schema
Built with