@npm-pipl/device-intelligence 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Device Intelligence SDK v1.1.1
2
+ * Device Intelligence SDK v1.1.3
3
3
  * (c) 2026 Your Company
4
4
  * Released under the MIT License
5
5
  */
@@ -5519,7 +5519,19 @@ const automationDetected = {
5519
5519
  // Check webdriver property
5520
5520
  if (nav.webdriver === true)
5521
5521
  return true;
5522
- // Check for automation-related properties
5522
+ // Detect navigator.webdriver tampering (stealth plugins redefine the getter)
5523
+ try {
5524
+ const desc = Object.getOwnPropertyDescriptor(Navigator.prototype, "webdriver");
5525
+ if (desc?.get) {
5526
+ const src = Function.prototype.toString.call(desc.get);
5527
+ if (!/\[native code\]/.test(src))
5528
+ return true;
5529
+ }
5530
+ }
5531
+ catch {
5532
+ /* ignore */
5533
+ }
5534
+ // Check for automation-related properties on window / document
5523
5535
  const automationProps = [
5524
5536
  "__selenium_unwrapped",
5525
5537
  "__webdriver_evaluate",
@@ -5538,12 +5550,22 @@ const automationDetected = {
5538
5550
  "$chrome_asyncScriptInfo",
5539
5551
  "__nightmare",
5540
5552
  "__puppeteer_evaluation_script__",
5541
- "__playwright_evaluation_script__"
5553
+ "__playwright_evaluation_script__",
5554
+ "__playwright",
5555
+ "__pw_manual"
5542
5556
  ];
5543
5557
  for (const prop of automationProps) {
5544
5558
  if (prop in win || prop in document)
5545
5559
  return true;
5546
5560
  }
5561
+ // Dynamic $cdc_ scan — ChromeDriver injects keys with varying suffixes
5562
+ try {
5563
+ if (Object.keys(win).some((k) => k.startsWith("$cdc_")))
5564
+ return true;
5565
+ }
5566
+ catch {
5567
+ /* ignore */
5568
+ }
5547
5569
  // Check for PhantomJS
5548
5570
  if ("callPhantom" in win || "_phantom" in win || "phantom" in win) {
5549
5571
  return true;
@@ -5553,8 +5575,44 @@ const automationDetected = {
5553
5575
  if (ua.includes("headless") || ua.includes("phantomjs")) {
5554
5576
  return true;
5555
5577
  }
5556
- // Check for CriOS (Chrome on iOS), which is legitimate
5557
- // but check for other weird UA patterns
5578
+ // WebGL renderer headless Chromium uses SwiftShader (software renderer)
5579
+ try {
5580
+ const canvas = document.createElement("canvas");
5581
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
5582
+ if (gl) {
5583
+ const dbg = gl.getExtension("WEBGL_debug_renderer_info");
5584
+ if (dbg) {
5585
+ const renderer = gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL);
5586
+ if (typeof renderer === "string" && /swiftshader/i.test(renderer)) {
5587
+ return true;
5588
+ }
5589
+ }
5590
+ }
5591
+ }
5592
+ catch {
5593
+ /* ignore */
5594
+ }
5595
+ // Plugins — headless browsers report 0 plugins; real Chrome always has >=1
5596
+ try {
5597
+ if (nav.plugins && nav.plugins.length === 0 && !ua.includes("firefox")) {
5598
+ return true;
5599
+ }
5600
+ }
5601
+ catch {
5602
+ /* ignore */
5603
+ }
5604
+ // Chrome object inconsistency — Chrome UA but missing window.chrome
5605
+ try {
5606
+ if (ua.includes("chrome") && !("chrome" in win)) {
5607
+ return true;
5608
+ }
5609
+ if (win.chrome && !win.chrome.runtime && !win.chrome.csi) {
5610
+ return true;
5611
+ }
5612
+ }
5613
+ catch {
5614
+ /* ignore */
5615
+ }
5558
5616
  return false;
5559
5617
  }
5560
5618
  };
@@ -5716,67 +5774,103 @@ const debuggerDetected = {
5716
5774
  const incognitoMode = {
5717
5775
  category: "risk",
5718
5776
  key: "incognito_mode",
5719
- version: 1,
5777
+ version: 2,
5720
5778
  cacheTtlMs: 300000,
5721
- timeoutMs: 2000,
5779
+ timeoutMs: 3000,
5722
5780
  collect: async () => {
5723
- // Safari: Check storage quota (with timeout to prevent hanging)
5781
+ const ua = (navigator.userAgent || "").toLowerCase();
5782
+ const isChromium = ua.includes("chrome") || ua.includes("chromium");
5783
+ const isFirefox = ua.includes("firefox");
5784
+ // ── Storage quota ──────────────────────────────────────────────────
5785
+ // Normal mode: quota ≈ 50–60 % of disk (tens to hundreds of GB).
5786
+ // Incognito: quota is RAM-based and much smaller.
5787
+ // • Safari / Firefox private: typically < 200 MB
5788
+ // • Chrome incognito: scales with device RAM (1–16 GB)
5724
5789
  try {
5725
- const storage = await withTimeout(navigator.storage.estimate(), 1000);
5726
- if (storage &&
5727
- storage.quota !== undefined &&
5728
- storage.quota < 120000000) {
5729
- return {
5730
- is_private: true,
5731
- confidence: "high",
5732
- method: "storage_quota"
5733
- };
5790
+ const est = await withTimeout(navigator.storage.estimate(), 1500);
5791
+ if (est && est.quota !== undefined) {
5792
+ const quota = est.quota;
5793
+ // Tier 1: very small quota catches Safari & Firefox private
5794
+ if (quota < 200000000) {
5795
+ return {
5796
+ is_private: true,
5797
+ confidence: "high",
5798
+ method: "storage_quota"
5799
+ };
5800
+ }
5801
+ if (isChromium) {
5802
+ // Tier 2: compare quota to device memory (most reliable for Chrome).
5803
+ // In incognito, quota is RAM-based → quota ≤ ~RAM.
5804
+ // In normal mode, quota is disk-based → quota >> RAM.
5805
+ // navigator.deviceMemory is capped at 8 (GB) and coarsened,
5806
+ // so we use a 3× multiplier to cover the gap between the
5807
+ // capped value and real RAM. Even on a 64 GB RAM machine
5808
+ // (deviceMemory=8), incognito quota (~16 GB) < 3×8 GB = 24 GB,
5809
+ // while normal quota on a 128 GB SSD (~64 GB) > 24 GB.
5810
+ const deviceMemGB = navigator.deviceMemory;
5811
+ if (deviceMemGB && quota < deviceMemGB * 3 * 1024 * 1024 * 1024) {
5812
+ return {
5813
+ is_private: true,
5814
+ confidence: "medium",
5815
+ method: "storage_quota_vs_memory"
5816
+ };
5817
+ }
5818
+ // Tier 3: absolute fallback when deviceMemory is unavailable.
5819
+ // 16 GB catches incognito on most machines (up to ~32 GB RAM)
5820
+ // while staying below normal-mode quota on any disk ≥ 64 GB.
5821
+ if (!deviceMemGB && quota < 16000000000) {
5822
+ return {
5823
+ is_private: true,
5824
+ confidence: "medium",
5825
+ method: "storage_quota_chrome"
5826
+ };
5827
+ }
5828
+ }
5734
5829
  }
5735
5830
  }
5736
5831
  catch {
5737
- // Not supported
5832
+ // API not supported
5738
5833
  }
5739
- // Firefox: Check IndexedDB behavior (with timeout to prevent hanging)
5834
+ // ── webkitTemporaryStorage (Chromium legacy) ───────────────────────
5835
+ // Still present in some Chromium builds; incognito returns a small
5836
+ // quota (often ≈ 120 MB) vs normal (multiple GB).
5740
5837
  try {
5741
- const dbResult = await withTimeout(new Promise((resolve, reject) => {
5742
- const db = indexedDB.open("test");
5743
- db.onerror = () => reject(new Error("IndexedDB error"));
5744
- db.onsuccess = () => {
5745
- db.result.close();
5746
- resolve(true);
5747
- };
5748
- }), 1000);
5749
- // If timeout occurred (null), treat as inconclusive, not private
5750
- if (dbResult === null) {
5751
- // Timeout - can't determine, continue to next check
5838
+ const tmpStorage = navigator.webkitTemporaryStorage;
5839
+ if (tmpStorage?.queryUsageAndQuota) {
5840
+ const quota = await withTimeout(new Promise((resolve, reject) => {
5841
+ tmpStorage.queryUsageAndQuota((_usage, q) => resolve(q), () => reject(new Error("quota query failed")));
5842
+ }), 1000);
5843
+ if (quota !== null && quota < 200000000) {
5844
+ return {
5845
+ is_private: true,
5846
+ confidence: "medium",
5847
+ method: "webkit_temporary_storage"
5848
+ };
5849
+ }
5752
5850
  }
5753
5851
  }
5754
5852
  catch {
5755
- return {
5756
- is_private: true,
5757
- confidence: "medium",
5758
- method: "indexeddb"
5759
- };
5853
+ // Not available
5760
5854
  }
5761
- // Chrome: Check filesystem API (deprecated but still works)
5855
+ // ── IndexedDB ────────────────────────────────────────────────────────
5856
+ // Firefox private mode rejects indexedDB.open(). Some Chrome/Edge
5857
+ // incognito edge-cases (enterprise policies, older builds) also fail
5858
+ // here, so we keep this as a universal late fallback.
5762
5859
  try {
5763
- const fs = window.webkitRequestFileSystem;
5764
- if (fs) {
5765
- const fsResult = await withTimeout(new Promise((resolve, reject) => {
5766
- fs(0, // TEMPORARY
5767
- 1, () => resolve(true), () => reject(new Error("FileSystem error")));
5768
- }), 1000);
5769
- // If timeout occurred, continue
5770
- if (fsResult === null) {
5771
- // Timeout - can't determine
5772
- }
5773
- }
5860
+ await withTimeout(new Promise((resolve, reject) => {
5861
+ const req = indexedDB.open("_di_priv_check");
5862
+ req.onerror = () => reject(new Error("blocked"));
5863
+ req.onsuccess = () => {
5864
+ req.result.close();
5865
+ resolve(true);
5866
+ };
5867
+ }), 1000);
5774
5868
  }
5775
5869
  catch {
5776
5870
  return {
5777
5871
  is_private: true,
5778
- confidence: "medium",
5779
- method: "filesystem"
5872
+ confidence: isFirefox ? "medium" : "low",
5873
+ method: "indexeddb"
5780
5874
  };
5781
5875
  }
5782
5876
  return {
@@ -5930,7 +6024,7 @@ function getDefaultCollectors() {
5930
6024
  return getAllCollectors();
5931
6025
  }
5932
6026
 
5933
- var version = "1.1.1";
6027
+ var version = "1.1.3";
5934
6028
  var packageJson = {
5935
6029
  version: version};
5936
6030