Lompat ke konten
dokumentasi.id
Commit bd74801
v2
Star10 rb

7

Feature Check dan Perbandingan Hasil

withFeatureCheck() adalah langkah penyaring terakhir pada rantai deteksi. Method ini tidak membaca ulang user-agent string; ia memeriksa objek navigator milik browser yang sedang berjalan, lalu menimpa atau melengkapi field hasil deteksi yang sudah ada. Implementasinya hanya meneruskan panggilan ke item.detectFeature().get() (src/main/ua-parser.js:1148-1150). Karena itu withFeatureCheck() selalu sinkron, berbeda dengan withClientHints() yang dapat mengembalikan Promise saat navigator.userAgentData tersedia (src/main/ua-parser.js:1128-1146). Deklarasi tipe mencerminkan keduanya sebagai PromiseLike<T> | T (src/main/ua-parser.d.ts:14-19).

Guard: feature check hanya untuk UA milik browser sendiri

Seluruh isi detectFeature() dibungkus satu syarat: NAVIGATOR && NAVIGATOR.userAgent == this.ua (src/main/ua-parser.js:1252-1253). Kedua variabel global itu diambil sekali saat modul dimuat dari window.navigator dan window.navigator.userAgentData, dan bernilai undefined di luar browser (src/main/ua-parser.js:118-124).

Saat UAParser() dipanggil tanpa argumen, userAgent berasal dari NAVIGATOR.userAgent, sehingga guard lolos secara alami dan feature check aktif (src/main/ua-parser.js:1463-1466). Sebaliknya, bila Anda memberi string UA sendiri atau headers berisi user-agent lain, this.ua tidak lagi sama dengan navigator.userAgent dan seluruh blok dilewati tanpa error.

⚠️ Di Node.js atau di sisi server, NAVIGATOR bernilai undefined. withFeatureCheck() menjadi no-op: hasil parsing dari headers tidak berubah sama sekali, termasuk untuk Brave dan iPad. Jangan mengandalkannya sebagai penyaring di backend.

Mesin keadaan detectFeature()

Setelah guard lolos, detectFeature() bercabang menurut itemType. Tiap cabang hanya menyentuh field yang menjadi tanggung jawabnya, lalu mengembalikan this agar bisa dirantai (src/main/ua-parser.js:1255-1277). Untuk itemType == RESULT, method ini justru memanggil detectFeature() pada kelima sub-item browser, cpu, device, engine, dan os, lalu menyalin hasilnya ke data milik result (src/main/ua-parser.js:1278-1290).

stateDiagram-v2
    [*] --> Guard
    Guard --> Passthrough: "navigator.userAgent != ua"
    Guard --> Dispatch: "navigator.userAgent == ua"
    Dispatch --> BrowserBranch: "itemType == BROWSER"
    Dispatch --> DeviceBranch: "itemType == DEVICE"
    Dispatch --> OSBranch: "itemType == OS"
    Dispatch --> ResultBranch: "itemType == RESULT"
    BrowserBranch --> BraveName: "navigator.brave.isBrave is function"
    BrowserBranch --> Passthrough: "otherwise"
    DeviceBranch --> MobileType: "no type and userAgentData.mobile"
    DeviceBranch --> IPad: "model Macintosh and maxTouchPoints > 2"
    DeviceBranch --> Passthrough: "otherwise"
    OSBranch --> PlatformName: "no name and userAgentData.platform"
    OSBranch --> Passthrough: "otherwise"
    ResultBranch --> Dispatch: "recurse over 5 sub-items"
    BraveName --> [*]
    MobileType --> [*]
    IPad --> [*]
    PlatformName --> [*]
    Passthrough --> [*]
    style Guard fill:#f59e0b,color:#000
    style Dispatch fill:#f59e0b,color:#000
    style Passthrough fill:#8b5cf6,color:#fff
    style BraveName fill:#2d7d46,color:#fff
    style MobileType fill:#2d7d46,color:#fff
    style IPad fill:#2d7d46,color:#fff
    style PlatformName fill:#2d7d46,color:#fff
%% fig: Mesin keadaan detectFeature
%% desc: detectFeature melewati semua item bila UA bukan milik browser, lalu mengubah field tertentu per itemType atau memanggil ulang dirinya untuk RESULT.
Fig. 1: Mesin keadaan detectFeaturedetectFeature melewati semua item bila UA bukan milik browser, lalu mengubah field tertentu per itemType atau memanggil ulang dirinya untuk RESULT.
itemTypeKondisi di dalam detectFeature()Field yang diubahRujukan
BROWSERNAVIGATOR.brave.isBrave berupa functionname menjadi Bravesrc/main/ua-parser.js:1256-1260
DEVICEtype masih kosong dan NAVIGATOR_UADATA.mobile bernilai benartype menjadi mobilesrc/main/ua-parser.js:1261-1265
DEVICEmodel == 'Macintosh', standalone terdefinisi, maxTouchPoints > 2model menjadi iPad, type menjadi tabletsrc/main/ua-parser.js:1266-1270
OSname masih kosong dan NAVIGATOR_UADATA.platform adaname menjadi nilai platformsrc/main/ua-parser.js:1272-1277
RESULTselalu, setelah guard lolosbrowser, cpu, device, engine, os diisi hasil detectFeature() masing-masing itemsrc/main/ua-parser.js:1278-1290

Dua test e2e memperlihatkan efek tabel di atas secara langsung. Pada test pertama, navigator.brave.isBrave di-mock menjadi fungsi dan browser.name berubah dari nama browser asli menjadi Brave hanya setelah withFeatureCheck() dipanggil (test/e2e/browser.spec.mjs:95-112). Pada test kedua, UA yang dipaksa menjadi Macintosh menghasilkan device.model Macintosh tanpa device.type; setelah withFeatureCheck(), nilainya menjadi iPad dengan device.type tablet (test/e2e/browser.spec.mjs:115-137).

Chaining dengan withClientHints()

Urutan rantai menentukan siapa yang menang, karena keduanya menulis ke field yang sama. withFeatureCheck() mengembalikan IData biasa, jadi withClientHints() dapat dirantai langsung setelahnya. Arah sebaliknya memerlukan .then ketika withClientHints() menghasilkan Promise; IData.prototype.then memastikan objek hasil resolve tetap membawa is, toString, withClientHints, dan withFeatureCheck (src/main/ua-parser.js:1177-1195).

graph LR
    R["UAParser().getResult()"] --> FC["withFeatureCheck()"]
    R --> CH["withClientHints()"]
    FC --> SyncIData["IData sinkron"]
    SyncIData --> CH2["withClientHints()"]
    CH --> HasUAData{"NAVIGATOR_UADATA ada?"}
    HasUAData -->|"tidak"| SyncIData2["IData sinkron"]
    HasUAData -->|"ya"| PromiseCH["Promise getHighEntropyValues"]
    SyncIData2 --> ThenFC[".then" ]
    PromiseCH --> ThenFC
    ThenFC --> FC3["res.withFeatureCheck()"]
    style FC fill:#f59e0b,color:#000
    style FC3 fill:#f59e0b,color:#000
    style ThenFC fill:#8b5cf6,color:#fff
    style SyncIData fill:#2d7d46,color:#fff
    style SyncIData2 fill:#2d7d46,color:#fff
%% fig: Dua urutan chaining withFeatureCheck dan withClientHints
%% desc: withFeatureCheck selalu sinkron sehingga bisa diikuti withClientHints langsung, sedangkan withClientHints yang mengembalikan Promise harus dibuka lewat then sebelum withFeatureCheck.
Fig. 2: Dua urutan chaining withFeatureCheck dan withClientHintswithFeatureCheck selalu sinkron sehingga bisa diikuti withClientHints langsung, sedangkan withClientHints yang mengembalikan Promise harus dibuka lewat then sebelum withFeatureCheck.

Di sisi server, kedua urutan memberi hasil yang sama karena tidak ada navigator: device dari headers sec-ch-ua-form-factors: "VR" berakhir dengan type xr baik pada rantai withFeatureCheck().withClientHints() maupun withClientHints().withFeatureCheck() (test/unit/ua-ch.js:418-432). Di browser, hasilnya bergantung pada urutan karena masing-masing method menimpa device.type milik yang lain (test/e2e/browser.spec.mjs:156-195).

RantaiChromium dengan userAgentDataBrowser tanpa client hints
withFeatureCheck().withClientHints()xr — ditimpa client hintstablet — tidak ada client hints
withClientHints().withFeatureCheck()tablet — ditimpa feature checktablet

Angka pada tabel berasal dari assert di test e2e: fc2ch diharapkan xr dan ch2fc diharapkan tablet pada Chromium, sedangkan di browser lain keduanya tablet (test/e2e/browser.spec.mjs:186-194). Skenario mock di test itu memasang navigator.standalone dan maxTouchPoints 3, sehingga cabang iPadOS di detectFeature() aktif (test/e2e/browser.spec.mjs:160-183).

Membandingkan hasil setelah penyaringan

Karena withFeatureCheck() mengembalikan IData, hasilnya dibandingkan dengan cara yang sama seperti hasil deteksi lain: is() mencocokkan setiap properti secara case-insensitive dan melewati properti yang terdaftar di isIgnore, yaitu version/major untuk browser, version untuk engine, dan version untuk os (src/main/ua-parser.js:1152-1165). Detail perilaku is() dan toString() ada di API Utama dan Bentuk Hasil.

Untuk membandingkan dua urutan rantai, jalankan keduanya pada input yang sama lalu bandingkan field yang berbeda. Pada contoh di atas hanya device.type yang berubah, karena hanya cabang DEVICE dari detectFeature() yang bersinggungan dengan formFactors pada client hints. Topik cara client hints mengisi device.type dibahas di Deteksi Client Hints, sedangkan daftar nilai device.type yang sah ada di Enums dan Konstanta Deteksi.