jumpy-lion 0.1.6-beta.9 → 1.0.0-beta.4

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.
Files changed (450) hide show
  1. package/README.md +1 -607
  2. package/browser/fonts/macos-bundle/41-macos-remaining.conf +157 -0
  3. package/browser/fonts/macos-bundle/README.md +24 -0
  4. package/browser/fonts/macos-bundle/build-metric-fonts.py +1010 -0
  5. package/browser/fonts/macos-bundle/generated/AmericanTypewriter-Bold.ttf +0 -0
  6. package/browser/fonts/macos-bundle/generated/AmericanTypewriter-Regular.ttf +0 -0
  7. package/browser/fonts/macos-bundle/generated/AndaleMono-Bold.ttf +0 -0
  8. package/browser/fonts/macos-bundle/generated/AndaleMono-BoldItalic.ttf +0 -0
  9. package/browser/fonts/macos-bundle/generated/AndaleMono-Italic.ttf +0 -0
  10. package/browser/fonts/macos-bundle/generated/AndaleMono-Regular.ttf +0 -0
  11. package/browser/fonts/macos-bundle/generated/AppleChancery-Bold.ttf +0 -0
  12. package/browser/fonts/macos-bundle/generated/AppleChancery-BoldItalic.ttf +0 -0
  13. package/browser/fonts/macos-bundle/generated/AppleChancery-Italic.ttf +0 -0
  14. package/browser/fonts/macos-bundle/generated/AppleChancery-Regular.ttf +0 -0
  15. package/browser/fonts/macos-bundle/generated/Arial-Bold.ttf +0 -0
  16. package/browser/fonts/macos-bundle/generated/Arial-BoldItalic.ttf +0 -0
  17. package/browser/fonts/macos-bundle/generated/Arial-Italic.ttf +0 -0
  18. package/browser/fonts/macos-bundle/generated/Arial-Regular.ttf +0 -0
  19. package/browser/fonts/macos-bundle/generated/ArialBlack-Bold.ttf +0 -0
  20. package/browser/fonts/macos-bundle/generated/ArialBlack-BoldItalic.ttf +0 -0
  21. package/browser/fonts/macos-bundle/generated/ArialBlack-Italic.ttf +0 -0
  22. package/browser/fonts/macos-bundle/generated/ArialBlack-Regular.ttf +0 -0
  23. package/browser/fonts/macos-bundle/generated/ArialNarrow-Bold.ttf +0 -0
  24. package/browser/fonts/macos-bundle/generated/ArialNarrow-BoldItalic.ttf +0 -0
  25. package/browser/fonts/macos-bundle/generated/ArialNarrow-Italic.ttf +0 -0
  26. package/browser/fonts/macos-bundle/generated/ArialNarrow-Regular.ttf +0 -0
  27. package/browser/fonts/macos-bundle/generated/Avenir-Bold.ttf +0 -0
  28. package/browser/fonts/macos-bundle/generated/Avenir-BoldItalic.ttf +0 -0
  29. package/browser/fonts/macos-bundle/generated/Avenir-Italic.ttf +0 -0
  30. package/browser/fonts/macos-bundle/generated/Avenir-Regular.ttf +0 -0
  31. package/browser/fonts/macos-bundle/generated/AvenirNext-Bold.ttf +0 -0
  32. package/browser/fonts/macos-bundle/generated/AvenirNext-BoldItalic.ttf +0 -0
  33. package/browser/fonts/macos-bundle/generated/AvenirNext-Italic.ttf +0 -0
  34. package/browser/fonts/macos-bundle/generated/AvenirNext-Regular.ttf +0 -0
  35. package/browser/fonts/macos-bundle/generated/AvenirNextCondensed-Bold.ttf +0 -0
  36. package/browser/fonts/macos-bundle/generated/AvenirNextCondensed-BoldItalic.ttf +0 -0
  37. package/browser/fonts/macos-bundle/generated/AvenirNextCondensed-Italic.ttf +0 -0
  38. package/browser/fonts/macos-bundle/generated/AvenirNextCondensed-Regular.ttf +0 -0
  39. package/browser/fonts/macos-bundle/generated/Baskerville-Bold.ttf +0 -0
  40. package/browser/fonts/macos-bundle/generated/Baskerville-BoldItalic.ttf +0 -0
  41. package/browser/fonts/macos-bundle/generated/Baskerville-Italic.ttf +0 -0
  42. package/browser/fonts/macos-bundle/generated/Baskerville-Regular.ttf +0 -0
  43. package/browser/fonts/macos-bundle/generated/BigCaslon-Bold.ttf +0 -0
  44. package/browser/fonts/macos-bundle/generated/BigCaslon-BoldItalic.ttf +0 -0
  45. package/browser/fonts/macos-bundle/generated/BigCaslon-Italic.ttf +0 -0
  46. package/browser/fonts/macos-bundle/generated/BigCaslon-Regular.ttf +0 -0
  47. package/browser/fonts/macos-bundle/generated/BlinkMacSystemFont-Bold.ttf +0 -0
  48. package/browser/fonts/macos-bundle/generated/BlinkMacSystemFont-BoldItalic.ttf +0 -0
  49. package/browser/fonts/macos-bundle/generated/BlinkMacSystemFont-Italic.ttf +0 -0
  50. package/browser/fonts/macos-bundle/generated/BlinkMacSystemFont-Regular.ttf +0 -0
  51. package/browser/fonts/macos-bundle/generated/Bodoni72-Bold.ttf +0 -0
  52. package/browser/fonts/macos-bundle/generated/Bodoni72-BoldItalic.ttf +0 -0
  53. package/browser/fonts/macos-bundle/generated/Bodoni72-Italic.ttf +0 -0
  54. package/browser/fonts/macos-bundle/generated/Bodoni72-Regular.ttf +0 -0
  55. package/browser/fonts/macos-bundle/generated/Bodoni72Oldstyle-Bold.ttf +0 -0
  56. package/browser/fonts/macos-bundle/generated/Bodoni72Oldstyle-BoldItalic.ttf +0 -0
  57. package/browser/fonts/macos-bundle/generated/Bodoni72Oldstyle-Italic.ttf +0 -0
  58. package/browser/fonts/macos-bundle/generated/Bodoni72Oldstyle-Regular.ttf +0 -0
  59. package/browser/fonts/macos-bundle/generated/Bodoni72Smallcaps-Bold.ttf +0 -0
  60. package/browser/fonts/macos-bundle/generated/Bodoni72Smallcaps-BoldItalic.ttf +0 -0
  61. package/browser/fonts/macos-bundle/generated/Bodoni72Smallcaps-Italic.ttf +0 -0
  62. package/browser/fonts/macos-bundle/generated/Bodoni72Smallcaps-Regular.ttf +0 -0
  63. package/browser/fonts/macos-bundle/generated/BradleyHand-Regular.ttf +0 -0
  64. package/browser/fonts/macos-bundle/generated/BrushScriptMT-Regular.ttf +0 -0
  65. package/browser/fonts/macos-bundle/generated/Chalkboard-Bold.ttf +0 -0
  66. package/browser/fonts/macos-bundle/generated/Chalkboard-BoldItalic.ttf +0 -0
  67. package/browser/fonts/macos-bundle/generated/Chalkboard-Italic.ttf +0 -0
  68. package/browser/fonts/macos-bundle/generated/Chalkboard-Regular.ttf +0 -0
  69. package/browser/fonts/macos-bundle/generated/ChalkboardSE-Bold.ttf +0 -0
  70. package/browser/fonts/macos-bundle/generated/ChalkboardSE-BoldItalic.ttf +0 -0
  71. package/browser/fonts/macos-bundle/generated/ChalkboardSE-Italic.ttf +0 -0
  72. package/browser/fonts/macos-bundle/generated/ChalkboardSE-Regular.ttf +0 -0
  73. package/browser/fonts/macos-bundle/generated/Chalkduster-Bold.ttf +0 -0
  74. package/browser/fonts/macos-bundle/generated/Chalkduster-BoldItalic.ttf +0 -0
  75. package/browser/fonts/macos-bundle/generated/Chalkduster-Italic.ttf +0 -0
  76. package/browser/fonts/macos-bundle/generated/Chalkduster-Regular.ttf +0 -0
  77. package/browser/fonts/macos-bundle/generated/Charter-Bold.ttf +0 -0
  78. package/browser/fonts/macos-bundle/generated/Charter-BoldItalic.ttf +0 -0
  79. package/browser/fonts/macos-bundle/generated/Charter-Italic.ttf +0 -0
  80. package/browser/fonts/macos-bundle/generated/Charter-Regular.ttf +0 -0
  81. package/browser/fonts/macos-bundle/generated/Cochin-Bold.ttf +0 -0
  82. package/browser/fonts/macos-bundle/generated/Cochin-BoldItalic.ttf +0 -0
  83. package/browser/fonts/macos-bundle/generated/Cochin-Italic.ttf +0 -0
  84. package/browser/fonts/macos-bundle/generated/Cochin-Regular.ttf +0 -0
  85. package/browser/fonts/macos-bundle/generated/ComicSansMS-Bold.ttf +0 -0
  86. package/browser/fonts/macos-bundle/generated/ComicSansMS-BoldItalic.ttf +0 -0
  87. package/browser/fonts/macos-bundle/generated/ComicSansMS-Italic.ttf +0 -0
  88. package/browser/fonts/macos-bundle/generated/ComicSansMS-Regular.ttf +0 -0
  89. package/browser/fonts/macos-bundle/generated/Copperplate-Bold.ttf +0 -0
  90. package/browser/fonts/macos-bundle/generated/Copperplate-Regular.ttf +0 -0
  91. package/browser/fonts/macos-bundle/generated/Courier-Bold.ttf +0 -0
  92. package/browser/fonts/macos-bundle/generated/Courier-BoldItalic.ttf +0 -0
  93. package/browser/fonts/macos-bundle/generated/Courier-Italic.ttf +0 -0
  94. package/browser/fonts/macos-bundle/generated/Courier-Regular.ttf +0 -0
  95. package/browser/fonts/macos-bundle/generated/CourierNew-Regular.ttf +0 -0
  96. package/browser/fonts/macos-bundle/generated/Didot-Bold.ttf +0 -0
  97. package/browser/fonts/macos-bundle/generated/Didot-BoldItalic.ttf +0 -0
  98. package/browser/fonts/macos-bundle/generated/Didot-Italic.ttf +0 -0
  99. package/browser/fonts/macos-bundle/generated/Didot-Regular.ttf +0 -0
  100. package/browser/fonts/macos-bundle/generated/Futura-Bold.ttf +0 -0
  101. package/browser/fonts/macos-bundle/generated/Futura-BoldItalic.ttf +0 -0
  102. package/browser/fonts/macos-bundle/generated/Futura-Italic.ttf +0 -0
  103. package/browser/fonts/macos-bundle/generated/Futura-Regular.ttf +0 -0
  104. package/browser/fonts/macos-bundle/generated/Geneva-Regular.ttf +0 -0
  105. package/browser/fonts/macos-bundle/generated/Georgia-Bold.ttf +0 -0
  106. package/browser/fonts/macos-bundle/generated/Georgia-BoldItalic.ttf +0 -0
  107. package/browser/fonts/macos-bundle/generated/Georgia-Italic.ttf +0 -0
  108. package/browser/fonts/macos-bundle/generated/Georgia-Regular.ttf +0 -0
  109. package/browser/fonts/macos-bundle/generated/GillSans-Bold.ttf +0 -0
  110. package/browser/fonts/macos-bundle/generated/GillSans-BoldItalic.ttf +0 -0
  111. package/browser/fonts/macos-bundle/generated/GillSans-Italic.ttf +0 -0
  112. package/browser/fonts/macos-bundle/generated/GillSans-Regular.ttf +0 -0
  113. package/browser/fonts/macos-bundle/generated/Helvetica-Bold.ttf +0 -0
  114. package/browser/fonts/macos-bundle/generated/Helvetica-BoldItalic.ttf +0 -0
  115. package/browser/fonts/macos-bundle/generated/Helvetica-Italic.ttf +0 -0
  116. package/browser/fonts/macos-bundle/generated/Helvetica-Regular.ttf +0 -0
  117. package/browser/fonts/macos-bundle/generated/HelveticaNeue-Bold.ttf +0 -0
  118. package/browser/fonts/macos-bundle/generated/HelveticaNeue-BoldItalic.ttf +0 -0
  119. package/browser/fonts/macos-bundle/generated/HelveticaNeue-Italic.ttf +0 -0
  120. package/browser/fonts/macos-bundle/generated/HelveticaNeue-Regular.ttf +0 -0
  121. package/browser/fonts/macos-bundle/generated/Herculanum-Bold.ttf +0 -0
  122. package/browser/fonts/macos-bundle/generated/Herculanum-BoldItalic.ttf +0 -0
  123. package/browser/fonts/macos-bundle/generated/Herculanum-Italic.ttf +0 -0
  124. package/browser/fonts/macos-bundle/generated/Herculanum-Regular.ttf +0 -0
  125. package/browser/fonts/macos-bundle/generated/HoeflerText-Bold.ttf +0 -0
  126. package/browser/fonts/macos-bundle/generated/HoeflerText-BoldItalic.ttf +0 -0
  127. package/browser/fonts/macos-bundle/generated/HoeflerText-Italic.ttf +0 -0
  128. package/browser/fonts/macos-bundle/generated/HoeflerText-Regular.ttf +0 -0
  129. package/browser/fonts/macos-bundle/generated/Impact-Bold.ttf +0 -0
  130. package/browser/fonts/macos-bundle/generated/Impact-BoldItalic.ttf +0 -0
  131. package/browser/fonts/macos-bundle/generated/Impact-Italic.ttf +0 -0
  132. package/browser/fonts/macos-bundle/generated/Impact-Regular.ttf +0 -0
  133. package/browser/fonts/macos-bundle/generated/LucidaGrande-Bold.ttf +0 -0
  134. package/browser/fonts/macos-bundle/generated/LucidaGrande-Regular.ttf +0 -0
  135. package/browser/fonts/macos-bundle/generated/MarkerFelt-Bold.ttf +0 -0
  136. package/browser/fonts/macos-bundle/generated/MarkerFelt-BoldItalic.ttf +0 -0
  137. package/browser/fonts/macos-bundle/generated/MarkerFelt-Italic.ttf +0 -0
  138. package/browser/fonts/macos-bundle/generated/MarkerFelt-Regular.ttf +0 -0
  139. package/browser/fonts/macos-bundle/generated/Menlo-Regular.ttf +0 -0
  140. package/browser/fonts/macos-bundle/generated/Monaco-Bold.ttf +0 -0
  141. package/browser/fonts/macos-bundle/generated/Monaco-BoldItalic.ttf +0 -0
  142. package/browser/fonts/macos-bundle/generated/Monaco-Italic.ttf +0 -0
  143. package/browser/fonts/macos-bundle/generated/Monaco-Regular.ttf +0 -0
  144. package/browser/fonts/macos-bundle/generated/Noteworthy-Bold.ttf +0 -0
  145. package/browser/fonts/macos-bundle/generated/Noteworthy-Regular.ttf +0 -0
  146. package/browser/fonts/macos-bundle/generated/Optima-Bold.ttf +0 -0
  147. package/browser/fonts/macos-bundle/generated/Optima-BoldItalic.ttf +0 -0
  148. package/browser/fonts/macos-bundle/generated/Optima-Italic.ttf +0 -0
  149. package/browser/fonts/macos-bundle/generated/Optima-Regular.ttf +0 -0
  150. package/browser/fonts/macos-bundle/generated/Palatino-Bold.ttf +0 -0
  151. package/browser/fonts/macos-bundle/generated/Palatino-BoldItalic.ttf +0 -0
  152. package/browser/fonts/macos-bundle/generated/Palatino-Italic.ttf +0 -0
  153. package/browser/fonts/macos-bundle/generated/Palatino-Regular.ttf +0 -0
  154. package/browser/fonts/macos-bundle/generated/Papyrus-Bold.ttf +0 -0
  155. package/browser/fonts/macos-bundle/generated/Papyrus-BoldItalic.ttf +0 -0
  156. package/browser/fonts/macos-bundle/generated/Papyrus-Italic.ttf +0 -0
  157. package/browser/fonts/macos-bundle/generated/Papyrus-Regular.ttf +0 -0
  158. package/browser/fonts/macos-bundle/generated/PartyLET-Bold.ttf +0 -0
  159. package/browser/fonts/macos-bundle/generated/PartyLET-BoldItalic.ttf +0 -0
  160. package/browser/fonts/macos-bundle/generated/PartyLET-Italic.ttf +0 -0
  161. package/browser/fonts/macos-bundle/generated/PartyLET-Regular.ttf +0 -0
  162. package/browser/fonts/macos-bundle/generated/Phosphate-Regular.ttf +0 -0
  163. package/browser/fonts/macos-bundle/generated/README.md +132 -0
  164. package/browser/fonts/macos-bundle/generated/Rockwell-Bold.ttf +0 -0
  165. package/browser/fonts/macos-bundle/generated/Rockwell-BoldItalic.ttf +0 -0
  166. package/browser/fonts/macos-bundle/generated/Rockwell-Italic.ttf +0 -0
  167. package/browser/fonts/macos-bundle/generated/Rockwell-Regular.ttf +0 -0
  168. package/browser/fonts/macos-bundle/generated/SavoyeLET-Bold.ttf +0 -0
  169. package/browser/fonts/macos-bundle/generated/SavoyeLET-BoldItalic.ttf +0 -0
  170. package/browser/fonts/macos-bundle/generated/SavoyeLET-Italic.ttf +0 -0
  171. package/browser/fonts/macos-bundle/generated/SavoyeLET-Regular.ttf +0 -0
  172. package/browser/fonts/macos-bundle/generated/Seravek-Bold.ttf +0 -0
  173. package/browser/fonts/macos-bundle/generated/Seravek-BoldItalic.ttf +0 -0
  174. package/browser/fonts/macos-bundle/generated/Seravek-Italic.ttf +0 -0
  175. package/browser/fonts/macos-bundle/generated/Seravek-Regular.ttf +0 -0
  176. package/browser/fonts/macos-bundle/generated/SignPainter-Bold.ttf +0 -0
  177. package/browser/fonts/macos-bundle/generated/SignPainter-Regular.ttf +0 -0
  178. package/browser/fonts/macos-bundle/generated/Skia-Bold.ttf +0 -0
  179. package/browser/fonts/macos-bundle/generated/Skia-BoldItalic.ttf +0 -0
  180. package/browser/fonts/macos-bundle/generated/Skia-Italic.ttf +0 -0
  181. package/browser/fonts/macos-bundle/generated/Skia-Regular.ttf +0 -0
  182. package/browser/fonts/macos-bundle/generated/SnellRoundhand-Bold.ttf +0 -0
  183. package/browser/fonts/macos-bundle/generated/SnellRoundhand-Regular.ttf +0 -0
  184. package/browser/fonts/macos-bundle/generated/Tahoma-Bold.ttf +0 -0
  185. package/browser/fonts/macos-bundle/generated/Tahoma-Regular.ttf +0 -0
  186. package/browser/fonts/macos-bundle/generated/Times-Bold.ttf +0 -0
  187. package/browser/fonts/macos-bundle/generated/Times-BoldItalic.ttf +0 -0
  188. package/browser/fonts/macos-bundle/generated/Times-Italic.ttf +0 -0
  189. package/browser/fonts/macos-bundle/generated/Times-Regular.ttf +0 -0
  190. package/browser/fonts/macos-bundle/generated/TimesNewRoman-Bold.ttf +0 -0
  191. package/browser/fonts/macos-bundle/generated/TimesNewRoman-BoldItalic.ttf +0 -0
  192. package/browser/fonts/macos-bundle/generated/TimesNewRoman-Italic.ttf +0 -0
  193. package/browser/fonts/macos-bundle/generated/TimesNewRoman-Regular.ttf +0 -0
  194. package/browser/fonts/macos-bundle/generated/Trattatello-Regular.ttf +0 -0
  195. package/browser/fonts/macos-bundle/generated/TrebuchetMS-Bold.ttf +0 -0
  196. package/browser/fonts/macos-bundle/generated/TrebuchetMS-BoldItalic.ttf +0 -0
  197. package/browser/fonts/macos-bundle/generated/TrebuchetMS-Italic.ttf +0 -0
  198. package/browser/fonts/macos-bundle/generated/TrebuchetMS-Regular.ttf +0 -0
  199. package/browser/fonts/macos-bundle/generated/Verdana-Bold.ttf +0 -0
  200. package/browser/fonts/macos-bundle/generated/Verdana-BoldItalic.ttf +0 -0
  201. package/browser/fonts/macos-bundle/generated/Verdana-Italic.ttf +0 -0
  202. package/browser/fonts/macos-bundle/generated/Verdana-Regular.ttf +0 -0
  203. package/browser/fonts/macos-bundle/generated/Webdings-Bold.ttf +0 -0
  204. package/browser/fonts/macos-bundle/generated/Webdings-BoldItalic.ttf +0 -0
  205. package/browser/fonts/macos-bundle/generated/Webdings-Italic.ttf +0 -0
  206. package/browser/fonts/macos-bundle/generated/Webdings-Regular.ttf +0 -0
  207. package/browser/fonts/macos-bundle/generated/Zapfino-Bold.ttf +0 -0
  208. package/browser/fonts/macos-bundle/generated/Zapfino-BoldItalic.ttf +0 -0
  209. package/browser/fonts/macos-bundle/generated/Zapfino-Italic.ttf +0 -0
  210. package/browser/fonts/macos-bundle/generated/Zapfino-Regular.ttf +0 -0
  211. package/browser/fonts/macos-bundle/generated/manifest.json +2928 -0
  212. package/browser/fonts/macos-bundle/generated/systemui-Bold.ttf +0 -0
  213. package/browser/fonts/macos-bundle/generated/systemui-BoldItalic.ttf +0 -0
  214. package/browser/fonts/macos-bundle/generated/systemui-Italic.ttf +0 -0
  215. package/browser/fonts/macos-bundle/generated/systemui-Regular.ttf +0 -0
  216. package/browser/fonts/macos-bundle/install.sh +136 -0
  217. package/browser/fonts/macos-bundle/vendor/README.md +30 -0
  218. package/browser/fonts/macos-bundle/vendor/anton/Anton-Regular.ttf +0 -0
  219. package/browser/fonts/macos-bundle/vendor/anton/OFL.txt +93 -0
  220. package/browser/fonts/macos-bundle/vendor/gelasio/Gelasio-Italic-VariableFont_wght.ttf +0 -0
  221. package/browser/fonts/macos-bundle/vendor/gelasio/Gelasio-VariableFont_wght.ttf +0 -0
  222. package/browser/fonts/macos-bundle/vendor/gelasio/OFL.txt +93 -0
  223. package/browser/fonts/macos-bundle/verify-metric-fonts.py +171 -0
  224. package/dist/browser-controller.d.ts +47 -4
  225. package/dist/browser-controller.d.ts.map +1 -1
  226. package/dist/browser-controller.js +219 -278
  227. package/dist/browser-controller.js.map +1 -1
  228. package/dist/browser-plugin.d.ts +82 -110
  229. package/dist/browser-plugin.d.ts.map +1 -1
  230. package/dist/browser-plugin.js +405 -379
  231. package/dist/browser-plugin.js.map +1 -1
  232. package/dist/browser-process/align-fingerprint-config.d.ts +36 -0
  233. package/dist/browser-process/align-fingerprint-config.d.ts.map +1 -0
  234. package/dist/browser-process/align-fingerprint-config.js +74 -0
  235. package/dist/browser-process/align-fingerprint-config.js.map +1 -0
  236. package/dist/browser-process/anti-detect-browser.d.ts +2 -2
  237. package/dist/browser-process/anti-detect-browser.js +7 -7
  238. package/dist/browser-process/anti-detect-config.d.ts +146 -2
  239. package/dist/browser-process/anti-detect-config.d.ts.map +1 -1
  240. package/dist/browser-process/anti-detect-config.js +432 -127
  241. package/dist/browser-process/anti-detect-config.js.map +1 -1
  242. package/dist/browser-process/browser.d.ts +25 -92
  243. package/dist/browser-process/browser.d.ts.map +1 -1
  244. package/dist/browser-process/browser.js +467 -79
  245. package/dist/browser-process/browser.js.map +1 -1
  246. package/dist/browser-process/fingerprint-config.d.ts +146 -0
  247. package/dist/browser-process/fingerprint-config.d.ts.map +1 -0
  248. package/dist/browser-process/fingerprint-config.js +351 -0
  249. package/dist/browser-process/fingerprint-config.js.map +1 -0
  250. package/dist/browser-process/get-chrome-executable.d.ts +6 -0
  251. package/dist/browser-process/get-chrome-executable.d.ts.map +1 -1
  252. package/dist/browser-process/get-chrome-executable.js +79 -4
  253. package/dist/browser-process/get-chrome-executable.js.map +1 -1
  254. package/dist/browser-process/gpu-family-profiles.d.ts +58 -0
  255. package/dist/browser-process/gpu-family-profiles.d.ts.map +1 -0
  256. package/dist/browser-process/gpu-family-profiles.js +480 -0
  257. package/dist/browser-process/gpu-family-profiles.js.map +1 -0
  258. package/dist/browser-process/launch-options.d.ts +301 -0
  259. package/dist/browser-process/launch-options.d.ts.map +1 -0
  260. package/dist/browser-process/launch-options.js +2 -0
  261. package/dist/browser-process/launch-options.js.map +1 -0
  262. package/dist/browser-process/port-allocator.d.ts +64 -0
  263. package/dist/browser-process/port-allocator.d.ts.map +1 -0
  264. package/dist/browser-process/port-allocator.js +198 -0
  265. package/dist/browser-process/port-allocator.js.map +1 -0
  266. package/dist/browser-process/process.d.ts +85 -0
  267. package/dist/browser-process/process.d.ts.map +1 -1
  268. package/dist/browser-process/process.js +235 -19
  269. package/dist/browser-process/process.js.map +1 -1
  270. package/dist/browser-process/signal-cleanup.d.ts +40 -0
  271. package/dist/browser-process/signal-cleanup.d.ts.map +1 -0
  272. package/dist/browser-process/signal-cleanup.js +105 -0
  273. package/dist/browser-process/signal-cleanup.js.map +1 -0
  274. package/dist/browser-process/widevine-cdm.d.ts +144 -0
  275. package/dist/browser-process/widevine-cdm.d.ts.map +1 -0
  276. package/dist/browser-process/widevine-cdm.js +298 -0
  277. package/dist/browser-process/widevine-cdm.js.map +1 -0
  278. package/dist/browser-profiles/chrome/populate-profile.d.ts.map +1 -1
  279. package/dist/browser-profiles/chrome/populate-profile.js +21 -22
  280. package/dist/browser-profiles/chrome/populate-profile.js.map +1 -1
  281. package/dist/connection/cdp-reconnection.d.ts +50 -0
  282. package/dist/connection/cdp-reconnection.d.ts.map +1 -0
  283. package/dist/connection/cdp-reconnection.js +75 -0
  284. package/dist/connection/cdp-reconnection.js.map +1 -0
  285. package/dist/connection/page-session.d.ts +114 -0
  286. package/dist/connection/page-session.d.ts.map +1 -0
  287. package/dist/connection/page-session.js +237 -0
  288. package/dist/connection/page-session.js.map +1 -0
  289. package/dist/connection/session-client.d.ts +34 -0
  290. package/dist/connection/session-client.d.ts.map +1 -0
  291. package/dist/connection/session-client.js +129 -0
  292. package/dist/connection/session-client.js.map +1 -0
  293. package/dist/crawler.d.ts +9 -88
  294. package/dist/crawler.d.ts.map +1 -1
  295. package/dist/crawler.js +157 -8
  296. package/dist/crawler.js.map +1 -1
  297. package/dist/fingerprinting/fingerprint-injector.d.ts +24 -114
  298. package/dist/fingerprinting/fingerprint-injector.d.ts.map +1 -1
  299. package/dist/fingerprinting/fingerprint-injector.js +305 -611
  300. package/dist/fingerprinting/fingerprint-injector.js.map +1 -1
  301. package/dist/fingerprinting/fingerprint-overrides/datadome-bypass.d.ts +1 -1
  302. package/dist/fingerprinting/fingerprint-overrides/datadome-bypass.d.ts.map +1 -1
  303. package/dist/fingerprinting/fingerprint-overrides/datadome-bypass.js +16 -14
  304. package/dist/fingerprinting/fingerprint-overrides/datadome-bypass.js.map +1 -1
  305. package/dist/fingerprinting/fingerprint-overrides/index.d.ts +6 -2
  306. package/dist/fingerprinting/fingerprint-overrides/index.d.ts.map +1 -1
  307. package/dist/fingerprinting/fingerprint-overrides/index.js +6 -2
  308. package/dist/fingerprinting/fingerprint-overrides/index.js.map +1 -1
  309. package/dist/fingerprinting/fingerprint-overrides/keyboard-humanization.d.ts +1 -1
  310. package/dist/fingerprinting/fingerprint-overrides/keyboard-humanization.js +4 -4
  311. package/dist/fingerprinting/fingerprint-overrides/keyboard-humanization.js.map +1 -1
  312. package/dist/fingerprinting/fingerprint-overrides/locale-spoofing.js +4 -4
  313. package/dist/fingerprinting/fingerprint-overrides/navigator-override.d.ts +18 -0
  314. package/dist/fingerprinting/fingerprint-overrides/navigator-override.d.ts.map +1 -0
  315. package/dist/fingerprinting/fingerprint-overrides/navigator-override.js +136 -0
  316. package/dist/fingerprinting/fingerprint-overrides/navigator-override.js.map +1 -0
  317. package/dist/fingerprinting/fingerprint-overrides/override.d.ts +151 -0
  318. package/dist/fingerprinting/fingerprint-overrides/override.d.ts.map +1 -0
  319. package/dist/fingerprinting/fingerprint-overrides/override.js +14 -0
  320. package/dist/fingerprinting/fingerprint-overrides/override.js.map +1 -0
  321. package/dist/fingerprinting/fingerprint-overrides/platform-consistency.d.ts.map +1 -1
  322. package/dist/fingerprinting/fingerprint-overrides/platform-consistency.js +46 -15
  323. package/dist/fingerprinting/fingerprint-overrides/platform-consistency.js.map +1 -1
  324. package/dist/fingerprinting/fingerprint-overrides/registry.d.ts +43 -0
  325. package/dist/fingerprinting/fingerprint-overrides/registry.d.ts.map +1 -0
  326. package/dist/fingerprinting/fingerprint-overrides/registry.js +342 -0
  327. package/dist/fingerprinting/fingerprint-overrides/registry.js.map +1 -0
  328. package/dist/fingerprinting/fingerprint-overrides/screen-override.d.ts +16 -0
  329. package/dist/fingerprinting/fingerprint-overrides/screen-override.d.ts.map +1 -0
  330. package/dist/fingerprinting/fingerprint-overrides/screen-override.js +175 -0
  331. package/dist/fingerprinting/fingerprint-overrides/screen-override.js.map +1 -0
  332. package/dist/fingerprinting/fingerprint-overrides/scroll-humanization.d.ts +1 -1
  333. package/dist/fingerprinting/fingerprint-overrides/scroll-humanization.js +4 -4
  334. package/dist/fingerprinting/fingerprint-overrides/scroll-humanization.js.map +1 -1
  335. package/dist/fingerprinting/fingerprint-overrides/stealth-script.d.ts.map +1 -1
  336. package/dist/fingerprinting/fingerprint-overrides/stealth-script.js +75 -9
  337. package/dist/fingerprinting/fingerprint-overrides/stealth-script.js.map +1 -1
  338. package/dist/fingerprinting/fingerprint-overrides/ua-ch.d.ts +1 -1
  339. package/dist/fingerprinting/fingerprint-overrides/ua-ch.d.ts.map +1 -1
  340. package/dist/fingerprinting/fingerprint-overrides/ua-ch.js +34 -20
  341. package/dist/fingerprinting/fingerprint-overrides/ua-ch.js.map +1 -1
  342. package/dist/fingerprinting/fingerprint-overrides/webgl-spoofing.d.ts.map +1 -1
  343. package/dist/fingerprinting/fingerprint-overrides/webgl-spoofing.js +28 -9
  344. package/dist/fingerprinting/fingerprint-overrides/webgl-spoofing.js.map +1 -1
  345. package/dist/fingerprinting/fingerprint-overrides/webrtc-spoofing.js +1 -1
  346. package/dist/fingerprinting/hardware-realism.d.ts +74 -0
  347. package/dist/fingerprinting/hardware-realism.d.ts.map +1 -0
  348. package/dist/fingerprinting/hardware-realism.js +155 -0
  349. package/dist/fingerprinting/hardware-realism.js.map +1 -0
  350. package/dist/fingerprinting/injection-planner.d.ts +133 -0
  351. package/dist/fingerprinting/injection-planner.d.ts.map +1 -0
  352. package/dist/fingerprinting/injection-planner.js +497 -0
  353. package/dist/fingerprinting/injection-planner.js.map +1 -0
  354. package/dist/fingerprinting/locale-resolver.d.ts +8 -1
  355. package/dist/fingerprinting/locale-resolver.d.ts.map +1 -1
  356. package/dist/fingerprinting/locale-resolver.js +26 -3
  357. package/dist/fingerprinting/locale-resolver.js.map +1 -1
  358. package/dist/fingerprinting/non-apify-fingerprint-generator.d.ts +4 -4
  359. package/dist/fingerprinting/non-apify-fingerprint-generator.d.ts.map +1 -1
  360. package/dist/fingerprinting/non-apify-fingerprint-generator.js +35 -69
  361. package/dist/fingerprinting/non-apify-fingerprint-generator.js.map +1 -1
  362. package/dist/fingerprinting/non-apify-profiles.json +10515 -8494
  363. package/dist/fingerprinting/os-consistency.d.ts +28 -0
  364. package/dist/fingerprinting/os-consistency.d.ts.map +1 -0
  365. package/dist/fingerprinting/os-consistency.js +45 -0
  366. package/dist/fingerprinting/os-consistency.js.map +1 -0
  367. package/dist/fingerprinting/profile-quality.d.ts +25 -0
  368. package/dist/fingerprinting/profile-quality.d.ts.map +1 -0
  369. package/dist/fingerprinting/profile-quality.js +310 -0
  370. package/dist/fingerprinting/profile-quality.js.map +1 -0
  371. package/dist/fingerprinting/profile-selector.d.ts +102 -0
  372. package/dist/fingerprinting/profile-selector.d.ts.map +1 -0
  373. package/dist/fingerprinting/profile-selector.js +168 -0
  374. package/dist/fingerprinting/profile-selector.js.map +1 -0
  375. package/dist/fingerprinting/ua-alignment.d.ts +83 -0
  376. package/dist/fingerprinting/ua-alignment.d.ts.map +1 -0
  377. package/dist/fingerprinting/ua-alignment.js +197 -0
  378. package/dist/fingerprinting/ua-alignment.js.map +1 -0
  379. package/dist/fingerprinting/ua-ch-brands.d.ts +88 -0
  380. package/dist/fingerprinting/ua-ch-brands.d.ts.map +1 -0
  381. package/dist/fingerprinting/ua-ch-brands.js +174 -0
  382. package/dist/fingerprinting/ua-ch-brands.js.map +1 -0
  383. package/dist/index.d.ts +5 -0
  384. package/dist/index.d.ts.map +1 -1
  385. package/dist/index.js +6 -0
  386. package/dist/index.js.map +1 -1
  387. package/dist/input/dropdown-selector.d.ts +90 -0
  388. package/dist/input/dropdown-selector.d.ts.map +1 -0
  389. package/dist/input/dropdown-selector.js +394 -0
  390. package/dist/input/dropdown-selector.js.map +1 -0
  391. package/dist/input/element-target.d.ts +152 -0
  392. package/dist/input/element-target.d.ts.map +1 -0
  393. package/dist/input/element-target.js +461 -0
  394. package/dist/input/element-target.js.map +1 -0
  395. package/dist/input/input-emulator.d.ts +85 -0
  396. package/dist/input/input-emulator.d.ts.map +1 -0
  397. package/dist/input/input-emulator.js +325 -0
  398. package/dist/input/input-emulator.js.map +1 -0
  399. package/dist/input/input-transport.d.ts +60 -0
  400. package/dist/input/input-transport.d.ts.map +1 -0
  401. package/dist/input/input-transport.js +28 -0
  402. package/dist/input/input-transport.js.map +1 -0
  403. package/dist/input/recording-transport.d.ts +32 -0
  404. package/dist/input/recording-transport.d.ts.map +1 -0
  405. package/dist/input/recording-transport.js +43 -0
  406. package/dist/input/recording-transport.js.map +1 -0
  407. package/dist/keyboard-layout.d.ts +2 -2
  408. package/dist/keyboard-layout.js +2 -2
  409. package/dist/navigation/page-navigation.d.ts +67 -0
  410. package/dist/navigation/page-navigation.d.ts.map +1 -0
  411. package/dist/navigation/page-navigation.js +122 -0
  412. package/dist/navigation/page-navigation.js.map +1 -0
  413. package/dist/network/network-watch.d.ts +110 -0
  414. package/dist/network/network-watch.d.ts.map +1 -0
  415. package/dist/network/network-watch.js +223 -0
  416. package/dist/network/network-watch.js.map +1 -0
  417. package/dist/page.d.ts +123 -104
  418. package/dist/page.d.ts.map +1 -1
  419. package/dist/page.js +348 -1129
  420. package/dist/page.js.map +1 -1
  421. package/dist/session/inject-hook.d.ts +24 -0
  422. package/dist/session/inject-hook.d.ts.map +1 -0
  423. package/dist/session/inject-hook.js +80 -0
  424. package/dist/session/inject-hook.js.map +1 -0
  425. package/dist/session/save.d.ts +21 -0
  426. package/dist/session/save.d.ts.map +1 -0
  427. package/dist/session/save.js +163 -0
  428. package/dist/session/save.js.map +1 -0
  429. package/dist/session/tar-data-dir.d.ts +18 -0
  430. package/dist/session/tar-data-dir.d.ts.map +1 -0
  431. package/dist/session/tar-data-dir.js +105 -0
  432. package/dist/session/tar-data-dir.js.map +1 -0
  433. package/dist/session/types.d.ts +80 -0
  434. package/dist/session/types.d.ts.map +1 -0
  435. package/dist/session/types.js +29 -0
  436. package/dist/session/types.js.map +1 -0
  437. package/dist/session-profile.d.ts +102 -0
  438. package/dist/session-profile.d.ts.map +1 -0
  439. package/dist/session-profile.js +163 -0
  440. package/dist/session-profile.js.map +1 -0
  441. package/dist/tsconfig.build.tsbuildinfo +1 -1
  442. package/package.json +15 -6
  443. package/dist/fingerprinting/custom-fingerprint-injector.d.ts +0 -87
  444. package/dist/fingerprinting/custom-fingerprint-injector.d.ts.map +0 -1
  445. package/dist/fingerprinting/custom-fingerprint-injector.js +0 -342
  446. package/dist/fingerprinting/custom-fingerprint-injector.js.map +0 -1
  447. package/dist/launcher-wrap.d.ts +0 -10
  448. package/dist/launcher-wrap.d.ts.map +0 -1
  449. package/dist/launcher-wrap.js +0 -11
  450. package/dist/launcher-wrap.js.map +0 -1
@@ -3,12 +3,13 @@ import fs from 'fs/promises';
3
3
  import os from 'os';
4
4
  import path from 'path';
5
5
  import { log } from '@crawlee/core';
6
- import { getChromeBinaryVersion, getChromeExecutablePath, getAntiDetectBrowserPath } from './get-chrome-executable.js';
6
+ import { isAntiDetectBrowserSync } from './anti-detect-browser.js';
7
+ import { writeTempFingerprintConfig } from './anti-detect-config.js';
8
+ import { getChromeExecutablePath, getAntiDetectBrowserPath } from './get-chrome-executable.js';
7
9
  import { BrowserProcess } from './process.js';
10
+ import { seedWidevineCdm } from './widevine-cdm.js';
8
11
  import { populateChromeProfile } from '../browser-profiles/chrome/populate-profile.js';
9
12
  import { createChromeProfile } from '../browser-profiles/index.js';
10
- import { isAntiDetectBrowserSync } from './anti-detect-browser.js';
11
- import { writeTempFingerprintConfig } from './anti-detect-config.js';
12
13
  const EXTENSION_PATH = path.resolve(import.meta.dirname, '../fingerprinting/all-fingerprint-defender');
13
14
  const EXTENSION_PATH2 = path.resolve(import.meta.dirname, '../fingerprinting/anti-webgpu');
14
15
  // --lang is intentionally NOT hardcoded below. It is appended per-session from the
@@ -18,25 +19,35 @@ const EXTENSION_PATH2 = path.resolve(import.meta.dirname, '../fingerprinting/ant
18
19
  // Flag policy (see ChromeDriver default-switch audit in git history for full evidence):
19
20
  //
20
21
  // Dropped as actively harmful:
21
- // --enable-features=NetworkService,NetworkServiceInProcess — desktop default is DISABLED
22
+ // --enable-features=NetworkService,NetworkServiceInProcess - desktop default is DISABLED
22
23
  // since M89; forcing it creates a runtime state no real user has.
23
- // --disable-ipc-flooding-protection — JS-probeable via 201+ history.pushState()/10s.
24
+ // --disable-ipc-flooding-protection - JS-probeable via 201+ history.pushState()/10s.
24
25
  //
25
26
  // Dropped because they have a concrete page-JS detection probe:
26
- // --disable-popup-blocking — window.open() null-vs-non-null from a timer.
27
- // --disable-renderer-backgrounding — document.visibilityState / rAF throttling.
28
- // --disable-backgrounding-occluded-windows — same surface; internally named
29
- // kDisableBackgroundingOccludedWindowsForTesting.
30
- // --disable-component-extensions-with-background-pages — speechSynthesis.getVoices()
27
+ // --disable-popup-blocking - window.open() null-vs-non-null from a timer.
28
+ // --disable-component-extensions-with-background-pages - speechSynthesis.getVoices()
31
29
  // leaks the missing TTS component extension.
32
30
  //
31
+ // Conditionally added based on which binary we launch:
32
+ // --disable-renderer-backgrounding + --disable-backgrounding-occluded-windows
33
+ // auto-enabled when launching the anti-detect binary; the binary's
34
+ // direct source-tree edit to document.visibilityState / document.hidden
35
+ // hides the rAF / setTimeout throttling tell, so these flags are safe.
36
+ // On vanilla Chrome they remain opt-in via CHROMIUM_KEEP_BACKGROUNDING=1.
37
+ // macOS's own compositor-pause-on-occlusion path is in
38
+ // WebContentsViewCocoa and has no command-line switch - the anti-detect
39
+ // binary drops the kOccluded transition directly in
40
+ // content/app_shim_remote_cocoa/web_contents_view_cocoa.mm.
41
+ //
33
42
  // Dropped because they are provably dead or placebo:
34
- // --disable-translate — string absent from Chromium switches files; noop since ~2018.
35
- // --disable-infobars — infobar_manager.cc:22-31 gate requires --headless OR a
43
+ // --disable-translate - string absent from Chromium switches files; noop since ~2018.
44
+ // --disable-infobars - infobar_manager.cc:22-31 gate requires --headless OR a
36
45
  // Chrome-for-Testing build; never active in our setup.
37
- // --disable-blink-features=AutomationControlled — only blocks the disabled-by-default
46
+ // --disable-blink-features=AutomationControlled - only blocks the disabled-by-default
38
47
  // RuntimeEnabledFeatures path; the actual webdriver-setter is
39
- // probe::ApplyAutomationOverride via CDP, which Cromite patch 0001 already no-ops.
48
+ // probe::ApplyAutomationOverride via CDP, which the anti-detect binary's
49
+ // direct edit to inspector_emulation_agent.cc / navigator.cc already no-ops
50
+ // (Navigator::webdriver() hardcoded to false).
40
51
  //
41
52
  // Dropped because they are network-signature flags absent from modern stealth tooling
42
53
  // (nodriver / Patchright) with no page-JS benefit on our setup:
@@ -48,6 +59,21 @@ const EXTENSION_PATH2 = path.resolve(import.meta.dirname, '../fingerprinting/ant
48
59
  * (a) no known page-JS observable probe, AND
49
60
  * (b) an operational or consistency justification (avoid startup dialogs, reduce
50
61
  * automation-irrelevant background work, match realistic fresh-profile defaults).
62
+ *
63
+ * Two operationally-detectable flags moved to opt-in env vars:
64
+ * - --disable-renderer-backgrounding / --disable-backgrounding-occluded-windows
65
+ * (rAF throttle probe): opt in via CHROMIUM_KEEP_BACKGROUNDING=1 for local
66
+ * headfull dev where window focus loss would otherwise stall the crawl. The
67
+ * document.hidden() C++ override (cromite patch D26) handles the JS-property
68
+ * side of the visibility spoof regardless.
69
+ * - --use-angle=swiftshader (WebGL perf probe + tell on macOS): opt in via
70
+ * CHROMIUM_USE_SWIFTSHADER=1 for canvas/WebGL pixel parity between local-mac
71
+ * (Metal-capable) and Apify-linux. Off by default lets ANGLE Metal/D3D/GL
72
+ * run natively. The anti-detect binary's direct source-tree edit to
73
+ * WebGLRenderingContextBase::getParameter (in
74
+ * third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc)
75
+ * still spoofs UNMASKED_VENDOR_WEBGL / UNMASKED_RENDERER_WEBGL regardless
76
+ * of which backend ANGLE picks.
51
77
  */
52
78
  const UNIVERSAL_STEALTH_ARGS = [
53
79
  // CDP transport: Chrome 111+ requires an origin allow-list for WebSocket connections
@@ -61,28 +87,118 @@ const UNIVERSAL_STEALTH_ARGS = [
61
87
  '--disable-session-crashed-bubble',
62
88
  // Suppress the EU search-engine choice modal, which would block a fresh profile.
63
89
  '--disable-search-engine-choice-screen',
64
- // Predictable startup URL — blank page instead of NTP fetch.
90
+ // Predictable startup URL - blank page instead of NTP fetch.
65
91
  '--homepage=about:blank',
66
92
  // Operational: do not show the "Page Unresponsive" dialog mid-crawl. No JS probe.
67
93
  '--disable-hang-monitor',
68
94
  // Operational: suppress the "Confirm form resubmission" dialog on POST reload. No JS probe.
69
95
  '--disable-prompt-on-repost',
70
96
  ];
97
+ /**
98
+ * Optional flags gated on env vars. Adding either of these to the launch argv
99
+ * narrows operational pain (headfull focus-loss stalls; canvas parity drift)
100
+ * but adds a fingerprint surface that real Chrome users do not have.
101
+ *
102
+ * Default-off so production crawls ship the cleanest argv we can manage. Local
103
+ * dev opts in via env. The anti-detect binary path also auto-enables the
104
+ * backgrounding flags - see `getAntiDetectBackgroundingArgs`.
105
+ */
106
+ function getOptionalStealthArgs() {
107
+ const out = [];
108
+ if (process.env.CHROMIUM_KEEP_BACKGROUNDING === '1'
109
+ || process.env.CHROMIUM_KEEP_BACKGROUNDING === 'true') {
110
+ // Keep the renderer running at full speed when the OS window loses focus
111
+ // or is occluded. Without these, headfull dev stalls when the user
112
+ // alt-tabs away - Chrome drops renderer priority and clamps setTimeout
113
+ // to ≥1s. (render_process_host_impl.cc; web_contents_impl.cc.)
114
+ //
115
+ // Trade-off: rAF/setTimeout drift while document.hidden===true is JS-probeable.
116
+ out.push('--disable-renderer-backgrounding');
117
+ out.push('--disable-backgrounding-occluded-windows');
118
+ }
119
+ if (process.env.CHROMIUM_USE_SWIFTSHADER === '1'
120
+ || process.env.CHROMIUM_USE_SWIFTSHADER === 'true') {
121
+ // Force ANGLE → SwiftShader so canvas/WebGL pixel output is identical
122
+ // between local-mac (Metal-capable) and Apify-linux (no GPU). Cromite's
123
+ // seeded pixel noise is a delta on top of a baseline; the baseline must
124
+ // match to make the final hash reproducible across hosts.
125
+ //
126
+ // Trade-off: WebGL perf benchmarks (timing a heavy scene) can detect
127
+ // SwiftShader on macOS hardware. Mainstream anti-bot vendors hash
128
+ // statically, but the flag is also visible in /proc/<pid>/cmdline on
129
+ // Linux when --no-sandbox is active.
130
+ out.push('--use-angle=swiftshader');
131
+ }
132
+ return out;
133
+ }
134
+ /**
135
+ * Backgrounding/occlusion suppression. Auto-enabled when launching the
136
+ * cromite anti-detect binary because cromite patch D26 pins
137
+ * `document.visibilityState` / `document.hidden` to "visible" at the
138
+ * Blink layer, so the rAF/setTimeout drift these flags would expose on
139
+ * vanilla Chrome is invisible to page JS under the patched binary.
140
+ *
141
+ * On macOS the equivalent of `CalculateNativeWinOcclusion` (the AURA
142
+ * Linux/Windows path) is `WebContentsViewCocoa.updateWebContentsVisibility`
143
+ * which has NO command-line kill switch - the kOccluded transition is
144
+ * hardcoded in content/app_shim_remote_cocoa/web_contents_view_cocoa.mm.
145
+ * That path is patched out in the cromite binary directly (see the
146
+ * "Anti-Detect: drop the kOccluded branch" patch), so no darwin-only
147
+ * launch flag is needed (or available) for the same effect.
148
+ *
149
+ * What these flags still do on macOS:
150
+ * --disable-renderer-backgrounding stops the renderer-process priority
151
+ * drop when our window is not the key window (CDP-driven sessions
152
+ * often never become key).
153
+ * --disable-backgrounding-occluded-windows stops the priority drop
154
+ * specifically on occlusion (separate from the visibility transition
155
+ * fixed by the cromite Cocoa patch).
156
+ */
157
+ function getAntiDetectBackgroundingArgs(_platform) {
158
+ return [
159
+ '--disable-renderer-backgrounding',
160
+ '--disable-backgrounding-occluded-windows',
161
+ ];
162
+ }
71
163
  /**
72
164
  * Linux-specific flags. Necessary for server-side operation; no page-JS probe on any:
73
165
  * --no-sandbox: running as root or in a rootless container where seccomp-bpf is
74
166
  * unavailable. Not observable from page JS (/proc/self/status is not reachable
75
- * from the renderer).
76
- * --disable-dev-shm-usage: Docker's default /dev/shm is 64 MB — renderers crash when
167
+ * from the renderer). Can be opted out with $CHROMIUM_KEEP_SANDBOX=1 when the
168
+ * container is rootless AND the host kernel allows user namespaces - e.g.
169
+ * modern Apify runners that drop privileges to UID 1000. The kernel-level
170
+ * sandbox is a small extra defense and removing the --no-sandbox flag
171
+ * reduces /proc/<pid>/cmdline noise.
172
+ * --disable-dev-shm-usage: Docker's default /dev/shm is 64 MB - renderers crash when
77
173
  * it fills. Backing switches to /tmp. Not observable from page JS.
78
174
  * --password-store=basic: avoid blocking on gnome-keyring/kwallet prompts. Not
79
175
  * observable from page JS.
80
176
  */
81
- const LINUX_STEALTH_ARGS = [
82
- '--no-sandbox',
177
+ const LINUX_STEALTH_ARGS_BASE = [
83
178
  '--disable-dev-shm-usage',
84
179
  '--password-store=basic',
85
180
  ];
181
+ /**
182
+ * Build the Linux flag list at runtime so the $CHROMIUM_KEEP_SANDBOX opt-out is
183
+ * picked up per-launch (process env can change between calls in long-running
184
+ * processes, e.g. when an Apify actor sets the var at startup).
185
+ *
186
+ * `--disable-setuid-sandbox` ships alongside `--no-sandbox`: even with
187
+ * `--no-sandbox`, Chrome may try to call the setuid sandbox helper
188
+ * (chrome_sandbox / chrome-sandbox) at startup; on restricted containers
189
+ * (Apify LIMITED_PERMISSIONS, Docker rootless) the helper isn't installed
190
+ * or can't bind, and Chrome hangs waiting on it instead of crashing -
191
+ * presents to the caller as a silent CDP-connect timeout. When
192
+ * CHROMIUM_KEEP_SANDBOX=1, both flags drop so the user-namespace sandbox
193
+ * can engage normally.
194
+ */
195
+ function getLinuxStealthArgs() {
196
+ const keepSandbox = process.env.CHROMIUM_KEEP_SANDBOX === '1'
197
+ || process.env.CHROMIUM_KEEP_SANDBOX === 'true';
198
+ return keepSandbox
199
+ ? LINUX_STEALTH_ARGS_BASE
200
+ : ['--no-sandbox', '--disable-setuid-sandbox', ...LINUX_STEALTH_ARGS_BASE];
201
+ }
86
202
  /**
87
203
  * macOS-specific flags. `--use-mock-keychain` substitutes the real Keychain with an
88
204
  * in-memory mock to avoid blocking-permission dialogs. Not observable from page JS.
@@ -94,28 +210,63 @@ const DARWIN_STEALTH_ARGS = [
94
210
  * Build the stealth argv list. Exported for unit tests so we can assert exact flag
95
211
  * sets without booting a browser.
96
212
  *
97
- * The `usingAntiDetectBrowser` flag is retained as a hook for future native-stealth-only
98
- * divergence, but is not currently needed — every flag in this set is either covered
99
- * identically by Cromite's C++ patches (no conflict) or orthogonal to them.
213
+ * When `usingAntiDetectBrowser` is true, the renderer-backgrounding and
214
+ * (on macOS) native-window-occlusion features are disabled regardless of
215
+ * env vars, because cromite patch D26 hides the only page-JS tell those
216
+ * flags would otherwise expose (document.hidden / rAF throttling). This
217
+ * fixes the "page goes blank until I refocus the window" symptom on
218
+ * headful macOS launches driven by CDP.
100
219
  */
101
220
  export function buildStealthArgs(env = {}) {
102
221
  const platform = env.platform ?? process.platform;
103
- const args = [...UNIVERSAL_STEALTH_ARGS];
222
+ const args = [...UNIVERSAL_STEALTH_ARGS, ...getOptionalStealthArgs()];
223
+ if (env.usingAntiDetectBrowser) {
224
+ for (const flag of getAntiDetectBackgroundingArgs(platform)) {
225
+ if (!args.includes(flag))
226
+ args.push(flag);
227
+ }
228
+ }
104
229
  if (platform === 'linux')
105
- args.push(...LINUX_STEALTH_ARGS);
230
+ args.push(...getLinuxStealthArgs());
106
231
  if (platform === 'darwin')
107
232
  args.push(...DARWIN_STEALTH_ARGS);
108
233
  return args;
109
234
  }
235
+ /**
236
+ * Process-layer launch options: everything in BaseLaunchBrowserOptions
237
+ * (the single shared declaration in launch-options.ts) plus the spawn-time
238
+ * fields only this layer knows about.
239
+ */
240
+ /**
241
+ * Resolve the platform-overrides config for an anti-detect browser launch, or
242
+ * THROW. The anti-detect binary exists to spoof; launching it without a config
243
+ * silently ships the host identity (navigator.platform / userAgent / WebGL all
244
+ * real) - the "30% of sessions are detectable and nobody notices" failure mode.
245
+ *
246
+ * Resolution order: the per-session config (stamped by browser-plugin.ts) first,
247
+ * then the static browser/config/default_fingerprint.json. If both are absent
248
+ * (per-session missing AND the default loader fails or yields nothing), this
249
+ * throws so the launch aborts instead of leaking. `loadStaticDefault` is injected
250
+ * so the dynamic-import path can be scripted in tests.
251
+ */
252
+ export async function resolveAntiDetectFingerprintConfig(perSessionConfig, loadStaticDefault) {
253
+ const resolved = perSessionConfig ?? await loadStaticDefault().catch(() => null);
254
+ if (resolved == null) {
255
+ throw new Error('[AntiDetect] No fingerprint config resolved for the anti-detect browser '
256
+ + '(per-session config absent and browser/config/default_fingerprint.json unavailable). '
257
+ + 'Refusing to launch unspoofed - the session would leak the host platform, user agent, and WebGL identity.');
258
+ }
259
+ return resolved;
260
+ }
110
261
  export const launchBrowserWithNewProfile = async (executablePath, options = {}) => {
111
262
  const { anonymizedProxyUrl, debuggingPort, windowWidth = 1920, windowHeight = 1080, ignoreHttpsErrors, headless, useExperimentalFingerprints = false, maximizeWindow = true, enableStealthMode = true, populateProfile: shouldPopulateProfile = true, userDataDir: providedUserDataDir, keepUserDataDir, dnsOverHttpsServer, secureDnsMode, webrtcIpHandlingPolicy, useNativeStealth = false, } = options;
112
263
  // Detect if we're running with the anti-detect browser binary
113
264
  const usingAntiDetectBrowser = useNativeStealth && isAntiDetectBrowserSync(executablePath);
114
265
  if (useNativeStealth && !usingAntiDetectBrowser) {
115
- log.debug('[AntiDetect] useNativeStealth requested but anti-detect browser not detected — using standard Chrome');
266
+ log.debug('[AntiDetect] useNativeStealth requested but anti-detect browser not detected - using standard Chrome');
116
267
  }
117
268
  else if (usingAntiDetectBrowser) {
118
- log.debug('[AntiDetect] Anti-detect browser detected — native C++ stealth active');
269
+ log.debug('[AntiDetect] Anti-detect browser detected - native C++ stealth active');
119
270
  }
120
271
  // Track temp config file for cleanup
121
272
  let fingerprintConfigPath = null;
@@ -127,8 +278,12 @@ export const launchBrowserWithNewProfile = async (executablePath, options = {})
127
278
  if (providedUserDataDir) {
128
279
  await fs.mkdir(userDataDir, { recursive: true });
129
280
  }
130
- await createChromeProfile(userDataDir);
131
- // Populate profile with realistic user data for 2024-2025 anti-bot evasion
281
+ // Populate profile with realistic user data for 2024-2025 anti-bot evasion.
282
+ // populateChromeProfile is the sole Preferences writer here: it builds the
283
+ // merged Preferences object in memory and writes it once (no separate
284
+ // createChromeProfile write to immediately overwrite). createChromeProfile
285
+ // is only used when population is disabled, or as a fallback if population
286
+ // fails partway so the launch still has a valid base profile.
132
287
  if (shouldPopulateProfile) {
133
288
  try {
134
289
  await populateChromeProfile(userDataDir, {
@@ -140,6 +295,22 @@ export const launchBrowserWithNewProfile = async (executablePath, options = {})
140
295
  }
141
296
  catch (popError) {
142
297
  log.debug(`Could not populate profile: ${popError}`);
298
+ await createChromeProfile(userDataDir).catch(() => { });
299
+ }
300
+ }
301
+ else {
302
+ await createChromeProfile(userDataDir);
303
+ }
304
+ // Seed the Widevine CDM from the per-host cache before Chromium starts, so
305
+ // startup registration picks it up and `requestMediaKeySystemAccess` resolves
306
+ // on the first navigation, exactly as it does in a real Chrome. On Linux this
307
+ // is the ONLY thing that works: the CDM is bound at startup there, so a copy
308
+ // fetched later in the session cannot be used until the next launch (see
309
+ // widevine-cdm.ts). A cold cache is a no-op; ensureWidevineCdm fills it.
310
+ if (usingAntiDetectBrowser) {
311
+ const seeded = await seedWidevineCdm(userDataDir);
312
+ if (seeded) {
313
+ log.debug(`[AntiDetect] Widevine CDM ${seeded.version} seeded into the profile before launch`);
143
314
  }
144
315
  }
145
316
  const browserArgs = [
@@ -154,17 +325,114 @@ export const launchBrowserWithNewProfile = async (executablePath, options = {})
154
325
  '--no-default-browser-check',
155
326
  ];
156
327
  // Add stealth arguments for anti-bot evasion. buildStealthArgs produces a
157
- // platform-scoped set aligned with nodriver — see the rationale block above
328
+ // platform-scoped set aligned with nodriver - see the rationale block above
158
329
  // the UNIVERSAL_STEALTH_ARGS constant for per-flag evidence.
330
+ // Chromium keeps only the LAST --enable-features switch on the command
331
+ // line, so two independent `push('--enable-features=...')` calls
332
+ // silently drop one another's features. Everything that wants a feature
333
+ // enabled adds it here and a single switch is emitted below.
334
+ const enabledFeatures = [];
159
335
  if (enableStealthMode) {
160
336
  browserArgs.push(...buildStealthArgs({ usingAntiDetectBrowser }));
161
337
  }
162
338
  if (dnsOverHttpsServer) {
163
- browserArgs.push('--enable-features=UseDnsHttpsSvcb,SecureDns', `--dns-over-https-mode=${secureDnsMode ?? 'secure'}`, `--dns-over-https-templates=${dnsOverHttpsServer}`);
339
+ enabledFeatures.push('UseDnsHttpsSvcb', 'SecureDns');
340
+ browserArgs.push(`--dns-over-https-mode=${secureDnsMode ?? 'secure'}`, `--dns-over-https-templates=${dnsOverHttpsServer}`);
164
341
  }
165
342
  if (webrtcIpHandlingPolicy) {
166
343
  browserArgs.push(`--force-webrtc-ip-handling-policy=${webrtcIpHandlingPolicy}`);
167
344
  }
345
+ if (options.fingerprintOptions?.forceEmulateWebgl === true) {
346
+ // Two switch names, because two binaries are in play and each reads
347
+ // only its own. Verified with `strings` on the shipped artifacts:
348
+ //
349
+ // dist/linux-x64/chrome (151) force-gpu-emulation x2
350
+ // dist/linux-x64.cromite-147.bak/… (147) force-webgl-emulation x2
351
+ //
352
+ // and neither name appears in the other binary. The 147 artifact is
353
+ // the one currently published to the KV store, so pushing only the
354
+ // 151 name means `forceEmulateWebgl: true` is silently dropped for
355
+ // every consumer on the released browser - exactly the way stock
356
+ // Chrome drops an unknown switch. Pushing only the 147 name has the
357
+ // mirror-image problem once #148 flips.
358
+ //
359
+ // Same dual-pass shape as --blink-settings-file /
360
+ // --blink-platform-overrides-file above. Delete the 147 line at the
361
+ // #148 flip, not before.
362
+ //
363
+ // The canonical declaration is switches::kForceGpuEmulation in
364
+ // chrome/common/chrome_switches.{h,cc}, propagated to
365
+ // renderer/GPU children via the kCommonSwitchNames list in
366
+ // chrome/browser/chrome_content_browser_client.cc. The renderer
367
+ // WebGL factory hook in third_party/blink/renderer/modules/webgl/
368
+ // webgl_rendering_context_base.cc reads the switch and, when
369
+ // FingerprintConfig::IsLoaded() also holds, returns the in-process
370
+ // EmulatedWebGraphicsContext3DProvider directly (skipping the GPU
371
+ // command-buffer round-trip entirely). On stock Chrome the switch
372
+ // is unknown and silently dropped.
373
+ browserArgs.push('--force-gpu-emulation');
374
+ browserArgs.push('--force-webgl-emulation');
375
+ // WebGPU has to answer for the same GPU the WebGL strings claim.
376
+ // Emulating WebGL leaves Dawn untouched, and on a GPU-less container
377
+ // `navigator.gpu.requestAdapter()` then resolves to NULL - so a
378
+ // session whose WEBGL_debug_renderer_info names an Apple M4 Pro also
379
+ // says, one line later, that the machine has no GPU at all. Measured
380
+ // in a Linux container against the real-macOS-Chrome baseline
381
+ // (test/docker-linux/baselines/coherence-macos-chrome151.json):
382
+ //
383
+ // without these switches hasAdapter=false vendor=null
384
+ // with them hasAdapter=true vendor=apple architecture=metal-3
385
+ //
386
+ // The two switches are upstream Chromium, not ours:
387
+ // --enable-unsafe-webgpu lifts the "no supported adapter" refusal
388
+ // that Linux applies to software adapters, and
389
+ // --use-webgpu-adapter=swiftshader points Dawn at the
390
+ // libvk_swiftshader.so that already ships in our dist. The adapter's
391
+ // IDENTITY still comes from us: the GPUAdapter info override in
392
+ // third_party/blink/renderer/modules/webgpu/gpu_adapter.cc reads
393
+ // vendor/architecture/device/description out of FingerprintConfig, so
394
+ // the software adapter reports the claimed Apple GPU rather than
395
+ // SwiftShader.
396
+ browserArgs.push('--enable-unsafe-webgpu');
397
+ browserArgs.push('--use-webgpu-adapter=swiftshader');
398
+ // …but --enable-unsafe-webgpu also turns on the experimental WebGPU
399
+ // surface, which installs `subgroupMatrixConfigs` on GPUAdapterInfo.
400
+ // Real macOS Chrome has no such property, so enumerating the info
401
+ // object's keys would show one the claimed browser cannot have -
402
+ // a switch tell earned while fixing a GPU tell. Turning the
403
+ // experimental features back off keeps the adapter and drops the key
404
+ // (verified in-container: infoKeys then match macOS exactly).
405
+ browserArgs.push('--disable-blink-features=WebGPUExperimentalFeatures');
406
+ // A laptop has a camera and a microphone. A container has neither, so
407
+ // `navigator.mediaDevices.enumerateDevices()` returns an EMPTY list
408
+ // while every other surface says MacBook - and the empty list needs
409
+ // no permission to read. The fake-device switch registers one
410
+ // synthetic camera, microphone and speaker, which is exactly the
411
+ // shape real macOS Chrome reports: 3 devices, kinds
412
+ // audioinput/audiooutput/videoinput, sharing one groupId (measured
413
+ // against Chrome 151 on macOS 26.5.2, and byte-identical in the
414
+ // container afterwards).
415
+ //
416
+ // Deliberately NOT --use-fake-ui-for-media-stream: that auto-accepts
417
+ // the permission prompt, and a camera permission that resolves
418
+ // without a user gesture is its own tell. Labels stay empty and
419
+ // permissions stay "prompt", same as an untouched profile.
420
+ browserArgs.push('--use-fake-device-for-media-stream');
421
+ // Scrollbars are drawn by the platform, and macOS draws them as
422
+ // OVERLAYS - zero layout width. GTK reserves 15 CSS px, which any
423
+ // page measures with two divs and no permission: a "Mac" whose
424
+ // scrollbars take 15px of layout is contradicting itself. Measured
425
+ // in-container: 15/15 without this switch, 0/0 with it, against 0/0
426
+ // on real Chrome 151/macOS.
427
+ enabledFeatures.push('OverlayScrollbar');
428
+ log.debug('[AntiDetect] forceEmulateWebgl=true → pushed --force-gpu-emulation + --force-webgl-emulation '
429
+ + '+ --enable-unsafe-webgpu + --use-webgpu-adapter=swiftshader + --use-fake-device-for-media-stream');
430
+ }
431
+ // The one --enable-features switch, built from every contributor above.
432
+ if (enabledFeatures.length > 0) {
433
+ browserArgs.push(`--enable-features=${enabledFeatures.join(',')}`);
434
+ log.debug(`[AntiDetect] enabled features: ${enabledFeatures.join(',')}`);
435
+ }
168
436
  // Set window size - either specific dimensions or maximize
169
437
  // Don't maximize if specific dimensions are provided to avoid conflicts
170
438
  if (windowWidth && windowHeight) {
@@ -184,41 +452,87 @@ export const launchBrowserWithNewProfile = async (executablePath, options = {})
184
452
  browserArgs.push(`--disable-extensions-except=${extList}`, // optional, keeps Chrome from loading the rest
185
453
  `--load-extension=${extList}`);
186
454
  }
187
- // Anti-detect browser: write --fingerprint-config JSON and pass it to the browser.
188
- // The C++ FingerprintConfig singleton reads this file at startup (lazy, first access).
455
+ // Anti-detect browser: write platform-overrides JSON and pass its path to the
456
+ // browser via the config switch (see the two-name note below - the shipped
457
+ // 147 binary calls it --blink-platform-overrides-file, the vanilla-151 one
458
+ // --blink-settings-file). The C++ singleton reads this file at startup
459
+ // (lazy, first access); the browser process re-emits the content inline as
460
+ // the matching -data switch so sandboxed children (renderer/GPU/utility)
461
+ // can parse it without file IO.
189
462
  if (usingAntiDetectBrowser) {
190
463
  try {
191
464
  // Prefer a pre-generated per-session config (passed by browser-plugin.ts) so that
192
465
  // each browser instance gets unique C++-level hardware fingerprints (GPU, screen,
193
466
  // CPU). Fall back to the static default_fingerprint.json if none was provided.
194
- const configToWrite = options.fingerprintConfig ?? await (async () => {
467
+ // Throws if neither source yields a config - see resolveAntiDetectFingerprintConfig.
468
+ const configToWrite = await resolveAntiDetectFingerprintConfig(options.fingerprintConfig, async () => {
195
469
  const { default: def } = await import(
196
- /* @vite-ignore */ new URL('../../browser/config/default_fingerprint.json', import.meta.url).href).catch(() => ({ default: null }));
470
+ /* @vite-ignore */ new URL('../../browser/config/default_fingerprint.json', import.meta.url).href);
197
471
  return def;
198
- })();
199
- if (configToWrite) {
472
+ });
473
+ {
200
474
  fingerprintConfigPath = await writeTempFingerprintConfig(configToWrite);
201
- browserArgs.push(`--fingerprint-config=${fingerprintConfigPath}`);
202
- // --fingerprint-accept-language is our custom C++ hook in
203
- // net/url_request/url_request_http_job.cc. Only the anti-detect binary
204
- // understands this flag. The hook splits on commas and feeds each entry
205
- // verbatim into `navigator.languages`, so we MUST strip q-values here;
206
- // otherwise the JS array leaks `"ko;q=0.9"`-style entries and becomes a
207
- // detectable anomaly (real Chrome's navigator.languages never carries
208
- // q-values — those are HTTP Accept-Language semantics only).
209
- const fpLangs = configToWrite.navigator?.languages;
210
- if (fpLangs) {
211
- const cleanLangs = fpLangs.split(',')
212
- .map((l) => l.split(';')[0].trim())
213
- .filter(Boolean)
214
- .join(',');
215
- browserArgs.push(`--fingerprint-accept-language=${cleanLangs}`);
475
+ // Opaque switch name (renamed from --fingerprint-config). Mimics
476
+ // existing Blink runtime-flag shape so /proc/<pid>/cmdline does
477
+ // not advertise an anti-detect-specific flag.
478
+ // #145 lockstep: the vanilla-151 binary renamed this switch to
479
+ // --blink-settings-file (switches::kBlinkSettingsFile). It knows
480
+ // no other name, and because it hard-fails when neither
481
+ // --blink-settings-file nor --blink-settings-data is present
482
+ // (#78), sending only the old name does not degrade gracefully -
483
+ // the browser SIGTRAPs before the CDP endpoint is ready and the
484
+ // crawler cannot launch it at all. The SHIPPED Cromite/147
485
+ // artifact has the mirror-image problem: it only knows the old
486
+ // name and hard-fails the same way if it is not passed.
487
+ //
488
+ // TEMPORARY COMPATIBILITY SHIM, not design. Both names are
489
+ // pushed, pointing at the SAME file, so one crawler build drives
490
+ // both binaries. Each binary reads the name it knows and ignores
491
+ // the other: base::CommandLine::AppendSwitchesAndArguments
492
+ // (base/command_line.cc) stores every `--k=v` token in a plain
493
+ // map with no allowlist, so an unrecognised switch is inert -
494
+ // nothing in the tree validates the switch set, and the only
495
+ // switch-name-driven warning path (kBadFlags in
496
+ // chrome/browser/ui/startup/bad_flags_prompt.cc) is a fixed list
497
+ // that contains neither name. Verified against the artifacts
498
+ // themselves: `strings` on the 147 build exports only
499
+ // "blink-platform-overrides-file", on the 151 build only
500
+ // "blink-settings-file".
501
+ //
502
+ // DELETE the legacy line below - and this comment - once the
503
+ // shipped artifact is the vanilla line, i.e. after the browser
504
+ // submodule pointer is flipped to the 151 build (#148). At that
505
+ // point no binary we ship or download reads the old name, and
506
+ // keeping it only adds a needless token to /proc/<pid>/cmdline.
507
+ browserArgs.push(`--blink-settings-file=${fingerprintConfigPath}`);
508
+ // LEGACY (Cromite/147 only) - remove with #148.
509
+ browserArgs.push(`--blink-platform-overrides-file=${fingerprintConfigPath}`);
510
+ // Anti-Detect 2026-05-18: also pass the spoofed User-Agent via
511
+ // Chromium's standard `--user-agent` switch so the HTTP wire
512
+ // layer sees the same UA as JS-visible `navigator.userAgent`.
513
+ // The previous mechanism - reading FingerprintConfig directly
514
+ // from `net/url_request/url_request_http_job.cc` - was a
515
+ // net/→blink/ layering violation that broke component builds
516
+ // (FAST_BUILD=1). `--user-agent` is honored by
517
+ // embedder_support::GetUserAgent() which HttpUserAgentSettings
518
+ // caches per-NetworkContext, so every outgoing HTTP request
519
+ // carries the spoofed UA without any cross-layer dep.
520
+ // Accept-Language is already covered by the `--lang=` switch
521
+ // pushed below (line ~520) plus the `setenv("LANG"/"LC_ALL")`
522
+ // pin in chrome_main_delegate.cc::BasicStartupComplete.
523
+ const spoofedUA = configToWrite?.navigator?.userAgent;
524
+ if (spoofedUA) {
525
+ browserArgs.push(`--user-agent=${spoofedUA}`);
216
526
  }
217
- log.debug(`[AntiDetect] Wrote fingerprint config: ${fingerprintConfigPath}${options.fingerprintConfig ? ' (per-session)' : ' (static default)'}`);
527
+ log.debug(`[Blink] Wrote platform-overrides config: ${fingerprintConfigPath}${options.fingerprintConfig ? ' (per-session)' : ' (static default)'}`);
218
528
  }
219
529
  }
220
530
  catch (configErr) {
221
- log.debug(`[AntiDetect] Could not write fingerprint config: ${configErr}`);
531
+ // Never swallow a config failure for the anti-detect binary: a
532
+ // browser launched without the platform-overrides config runs
533
+ // unspoofed and silently leaks the host identity. Surface it so
534
+ // the launch aborts rather than shipping a detectable session.
535
+ throw new Error(`[AntiDetect] Failed to prepare the fingerprint config for the anti-detect browser - refusing to launch unspoofed: ${configErr instanceof Error ? configErr.message : String(configErr)}`);
222
536
  }
223
537
  }
224
538
  // --lang is understood by every Chrome binary (stock + anti-detect) and configures
@@ -235,14 +549,24 @@ export const launchBrowserWithNewProfile = async (executablePath, options = {})
235
549
  browserArgs.push(`--lang=${langArg || options.resolvedLocale.locale}`);
236
550
  }
237
551
  else {
238
- // No locale resolved — preserve historical default so we don't change behavior
552
+ // No locale resolved - preserve historical default so we don't change behavior
239
553
  // for callers that never touched fingerprintOptions.
240
554
  browserArgs.push('--lang=en-US,en');
241
555
  }
242
556
  if (anonymizedProxyUrl) {
243
557
  browserArgs.push(`--proxy-server=${anonymizedProxyUrl}`);
244
558
  }
245
- if (ignoreHttpsErrors) {
559
+ // Anti-Detect 2026-05-18: `--ignore-certificate-errors` is JS-detectable.
560
+ // A page can issue a `fetch()` to a sub-resource hosted under a
561
+ // self-signed certificate; real Chrome blocks the load (cert error),
562
+ // we silently succeed. That single asymmetry is enough to flag us.
563
+ //
564
+ // When running the native anti-detect binary we therefore force real
565
+ // certificate validation, regardless of the caller's `ignoreHttpsErrors`
566
+ // setting - the stealth invariant (mimic real Chrome wire behaviour)
567
+ // overrides the convenience flag. Stock-Chromium / non-stealth callers
568
+ // keep the historical default.
569
+ if (ignoreHttpsErrors && !usingAntiDetectBrowser) {
246
570
  browserArgs.push('--ignore-certificate-errors');
247
571
  // disabled because of cloudflare blocking
248
572
  // browserArgs.push('--disable-web-security');
@@ -251,20 +575,35 @@ export const launchBrowserWithNewProfile = async (executablePath, options = {})
251
575
  browserArgs.push('--headless=new');
252
576
  }
253
577
  log.debug(`Launching browser with user data dir: ${userDataDir}`);
254
- const chromeVersion = getChromeBinaryVersion(executablePath);
255
- if (chromeVersion) {
256
- log.debug(`Using Chrome binary version: ${chromeVersion}`);
257
- }
258
578
  log.debug('browserArgs', browserArgs);
259
579
  log.debug(`Stealth mode: ${enableStealthMode}, Profile populated: ${shouldPopulateProfile}, cleanup profile: ${shouldCleanupUserDataDir}`);
260
- // Chrome bundles its own ICU so LANG/LC_ALL don't drive Intl — but a handful of
580
+ // Chrome bundles its own ICU so LANG/LC_ALL don't drive Intl - but a handful of
261
581
  // system APIs (file dialogs, native notifications, extension readers) consult them.
262
582
  // Setting these matches what a real user's shell would do and costs nothing.
583
+ //
584
+ // We also override TZ and LANGUAGE: a hardcoded host TZ (e.g. Europe/Berlin from
585
+ // an Apify Dockerfile) would otherwise leak through to anything that reads
586
+ // /etc/localtime or honors $TZ for fallback paths. Chrome's main Intl/Date path
587
+ // is covered by Emulation.setTimezoneOverride (called from fingerprint-injector)
588
+ // but workers and out-of-process subsystems can still consult $TZ.
263
589
  const spawnEnv = { ...process.env };
264
590
  if (options.resolvedLocale?.posix) {
265
591
  spawnEnv.LANG = options.resolvedLocale.posix;
266
592
  spawnEnv.LC_ALL = options.resolvedLocale.posix;
267
593
  }
594
+ if (options.resolvedLocale?.languages) {
595
+ // GTK/glib and some apps read $LANGUAGE for fallback chains; align with the
596
+ // primary language (strip q-values).
597
+ const primaryLang = options.resolvedLocale.languages
598
+ .split(',')[0]
599
+ ?.split(';')[0]
600
+ ?.trim();
601
+ if (primaryLang)
602
+ spawnEnv.LANGUAGE = primaryLang;
603
+ }
604
+ if (options.resolvedLocale?.timezone) {
605
+ spawnEnv.TZ = options.resolvedLocale.timezone;
606
+ }
268
607
  // Pipe stderr instead of swallowing it so a Chromium crash on Linux
269
608
  // (missing shared lib, sandbox failure, GLIBC mismatch, …) surfaces
270
609
  // in the logs instead of presenting as a generic "Failed to connect to
@@ -274,10 +613,27 @@ export const launchBrowserWithNewProfile = async (executablePath, options = {})
274
613
  const browserProcess = spawn(executablePath, browserArgs, {
275
614
  stdio: ['ignore', 'ignore', 'pipe'],
276
615
  env: spawnEnv,
616
+ // Put the browser in its own process group (pgid === pid) so the
617
+ // whole Chromium process tree - parent + renderer/GPU/utility
618
+ // helpers - can be reaped at once via `process.kill(-pid, sig)`.
619
+ // Without this, signals hit only the parent and helpers orphan to
620
+ // launchd/init, leaking RAM and exhausting macOS IOSurface clients.
621
+ // Ctrl+C is still handled explicitly by signal-cleanup.ts, so losing
622
+ // the TTY's automatic SIGINT forwarding to the group is fine.
623
+ detached: true,
277
624
  });
625
+ const proc = new BrowserProcess(browserProcess, userDataDir, shouldCleanupUserDataDir);
278
626
  const stderrBuffer = [];
279
627
  const STDERR_BUFFER_LIMIT = 50;
280
628
  let stderrLeftover = '';
629
+ // Chromium writes a large volume of benign stderr on headless container
630
+ // launches (GPU-less init, ALSA "no sound card", dbus, WebRTC P2P
631
+ // resolve failures, the DevTools banner, zygote teardown races, …).
632
+ // None of it is actionable at runtime, so EVERY stderr line is logged at
633
+ // DEBUG only - it surfaces solely when the Apify log level is DEBUG.
634
+ // Real failures are not lost: the `exit` handler below dumps the buffered
635
+ // stderr tail at WARNING whenever Chromium exits with a non-zero code,
636
+ // which is the signature of an actual crash.
281
637
  browserProcess.stderr?.on('data', (chunk) => {
282
638
  stderrLeftover += chunk.toString('utf8');
283
639
  const lines = stderrLeftover.split('\n');
@@ -289,26 +645,47 @@ export const launchBrowserWithNewProfile = async (executablePath, options = {})
289
645
  if (stderrBuffer.length > STDERR_BUFFER_LIMIT)
290
646
  stderrBuffer.shift();
291
647
  log.debug(`[Chromium stderr] ${line}`);
648
+ // Puppeteer-style readiness: Chromium prints the browser-level CDP
649
+ // WebSocket endpoint here once its remote-debugging server is
650
+ // accepting connections. Parsing it is the exact moment the browser
651
+ // is connectable - no TCP-port poll or fixed pre-connect delay
652
+ // needed.
653
+ const wsMatch = line.match(/DevTools listening on (ws:\S+)/);
654
+ if (wsMatch) {
655
+ proc.resolveWsEndpoint(wsMatch[1]);
656
+ }
292
657
  }
293
658
  });
294
659
  // Surface the buffered stderr if Chrome exits before something else
295
- // gets a chance to inspect it (typical crash-on-startup signature).
660
+ // gets a chance to inspect it (typical crash-on-startup signature), and
661
+ // fail the readiness promise fast with that same tail so a dead binary
662
+ // never presents as a generic connect timeout.
296
663
  browserProcess.once('exit', (code, signal) => {
664
+ if (stderrLeftover) {
665
+ stderrBuffer.push(stderrLeftover);
666
+ stderrLeftover = '';
667
+ }
668
+ const tail = stderrBuffer.slice(-20).join('\n');
297
669
  if (code !== 0 && code !== null) {
298
- if (stderrLeftover)
299
- stderrBuffer.push(stderrLeftover);
300
- const tail = stderrBuffer.slice(-20).join('\n');
301
670
  log.warning(`Chromium exited with code=${code} signal=${signal}. `
302
- + `Last stderr lines:\n${tail || '(empty — Chrome wrote nothing to stderr)'}`);
671
+ + `Last stderr lines:\n${tail || '(empty - Chrome wrote nothing to stderr)'}`);
303
672
  }
673
+ // No-op once the ws line was already seen (settled-once guard).
674
+ proc.rejectWsEndpoint(new Error(`Chromium exited (code=${code} signal=${signal}) before the CDP WebSocket endpoint was ready. `
675
+ + `Last stderr lines:\n${tail || '(empty - Chrome wrote nothing to stderr)'}`));
304
676
  });
305
- const proc = new BrowserProcess(browserProcess, userDataDir, shouldCleanupUserDataDir);
306
- // Clean up temp fingerprint config file when the browser process exits
677
+ // A spawn-level failure (missing binary, EACCES, …) fires `error` and may
678
+ // never fire `exit`, so reject readiness here too. Also settled-once.
679
+ browserProcess.once('error', (err) => {
680
+ const tail = stderrBuffer.slice(-20).join('\n');
681
+ proc.rejectWsEndpoint(new Error(`Browser process failed to start before the CDP WebSocket endpoint was ready: ${err.message}${tail ? `\nLast stderr lines:\n${tail}` : ''}`));
682
+ });
683
+ // Track the temp fingerprint config file on the BrowserProcess so it is
684
+ // removed both on normal exit (via cleanup()) and on a SIGINT/SIGTERM
685
+ // signal (via the signal-cleanup registry), not just the exit event -
686
+ // which never fires when the process is interrupted before teardown.
307
687
  if (fingerprintConfigPath) {
308
- const configPath = fingerprintConfigPath;
309
- browserProcess.once('exit', () => {
310
- fs.unlink(configPath).catch(() => { });
311
- });
688
+ proc.trackFingerprintConfigPath(fingerprintConfigPath);
312
689
  }
313
690
  return proc;
314
691
  }
@@ -317,7 +694,18 @@ export const launchBrowserWithNewProfile = async (executablePath, options = {})
317
694
  throw new Error(`Error launching the browser: ${error}`);
318
695
  }
319
696
  };
320
- export const createTempUserDataDir = async () => {
697
+ // Cache the resolved writable base directory for the process lifetime. The
698
+ // mkdir + write-test probe below only depends on the filesystem layout and the
699
+ // process's cwd/home, none of which change underneath us mid-process, so the
700
+ // probe is run once and its result reused. Only a SUCCESSFUL probe is cached -
701
+ // a transient failure (e.g. a full disk during the first launch) must be
702
+ // retried on the next launch rather than pinned for the whole process. Each
703
+ // launch still gets its own fresh user-data-dir via fs.mkdtemp under the base.
704
+ let cachedBaseTempDir = null;
705
+ const resolveWritableBaseTempDir = async () => {
706
+ if (cachedBaseTempDir) {
707
+ return cachedBaseTempDir;
708
+ }
321
709
  // Try to create temp directory in order of preference:
322
710
  // 1. Project's temp directory (if we're in a project)
323
711
  // 2. User's home directory temp folder
@@ -330,7 +718,6 @@ export const createTempUserDataDir = async () => {
330
718
  // System temp directory (fallback)
331
719
  path.join(os.tmpdir(), 'cdp-crawler-browsers'),
332
720
  ];
333
- let baseTempDir = null;
334
721
  for (const candidate of tempDirCandidates) {
335
722
  try {
336
723
  await fs.mkdir(candidate, { recursive: true });
@@ -338,17 +725,18 @@ export const createTempUserDataDir = async () => {
338
725
  const testFile = path.join(candidate, '.write-test');
339
726
  await fs.writeFile(testFile, 'test');
340
727
  await fs.unlink(testFile);
341
- baseTempDir = candidate;
342
- break;
728
+ cachedBaseTempDir = candidate;
729
+ return candidate;
343
730
  }
344
731
  catch (error) {
345
732
  log.debug(`Cannot use temp directory ${candidate}: ${error}`);
346
733
  continue;
347
734
  }
348
735
  }
349
- if (!baseTempDir) {
350
- throw new Error('Could not find a writable directory for browser profiles');
351
- }
736
+ throw new Error('Could not find a writable directory for browser profiles');
737
+ };
738
+ export const createTempUserDataDir = async () => {
739
+ const baseTempDir = await resolveWritableBaseTempDir();
352
740
  const tempDirPrefix = path.join(baseTempDir, 'browser_');
353
741
  const userDataDir = await fs.mkdtemp(tempDirPrefix);
354
742
  log.debug(`Created browser profile directory: ${userDataDir}`);
@@ -359,7 +747,7 @@ export const launchChromeBrowserWithNewProfile = async (options = {}) => {
359
747
  // CDPBrowserPlugin._launch() (resolveAntiDetectBrowserPath throws there if the
360
748
  // binary is missing). Direct callers of this function without native stealth
361
749
  // still fall through to the system Chrome lookup.
362
- let executablePath = options.executablePath;
750
+ let { executablePath } = options;
363
751
  if (!executablePath) {
364
752
  const wantsNativeStealth = options.useNativeStealth || options.fingerprintOptions?.useNativeStealth;
365
753
  if (wantsNativeStealth) {