@zorilla/puppeteer-extra-plugin-stealth 1.0.0

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 (270) hide show
  1. package/.claude/settings.local.json +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +324 -0
  4. package/dist/evasions/README.md +13 -0
  5. package/dist/evasions/_template/README.md +18 -0
  6. package/dist/evasions/_template/index.d.ts +13 -0
  7. package/dist/evasions/_template/index.d.ts.map +1 -0
  8. package/dist/evasions/_template/index.js +26 -0
  9. package/dist/evasions/_template/index.js.map +1 -0
  10. package/dist/evasions/_template/package.json +5 -0
  11. package/dist/evasions/_utils/README.md +287 -0
  12. package/dist/evasions/_utils/index.d.ts +238 -0
  13. package/dist/evasions/_utils/index.d.ts.map +1 -0
  14. package/dist/evasions/_utils/index.js +588 -0
  15. package/dist/evasions/_utils/index.js.map +1 -0
  16. package/dist/evasions/_utils/withUtils.d.ts +12 -0
  17. package/dist/evasions/_utils/withUtils.d.ts.map +1 -0
  18. package/dist/evasions/_utils/withUtils.js +47 -0
  19. package/dist/evasions/_utils/withUtils.js.map +1 -0
  20. package/dist/evasions/chrome.app/README.md +16 -0
  21. package/dist/evasions/chrome.app/index.d.ts +11 -0
  22. package/dist/evasions/chrome.app/index.d.ts.map +1 -0
  23. package/dist/evasions/chrome.app/index.js +97 -0
  24. package/dist/evasions/chrome.app/index.js.map +1 -0
  25. package/dist/evasions/chrome.app/package.json +5 -0
  26. package/dist/evasions/chrome.csi/README.md +29 -0
  27. package/dist/evasions/chrome.csi/index.d.ts +25 -0
  28. package/dist/evasions/chrome.csi/index.d.ts.map +1 -0
  29. package/dist/evasions/chrome.csi/index.js +69 -0
  30. package/dist/evasions/chrome.csi/index.js.map +1 -0
  31. package/dist/evasions/chrome.csi/package.json +5 -0
  32. package/dist/evasions/chrome.loadTimes/README.md +27 -0
  33. package/dist/evasions/chrome.loadTimes/index.d.ts +23 -0
  34. package/dist/evasions/chrome.loadTimes/index.d.ts.map +1 -0
  35. package/dist/evasions/chrome.loadTimes/index.js +163 -0
  36. package/dist/evasions/chrome.loadTimes/index.js.map +1 -0
  37. package/dist/evasions/chrome.loadTimes/package.json +5 -0
  38. package/dist/evasions/chrome.runtime/README.md +32 -0
  39. package/dist/evasions/chrome.runtime/index.d.ts +14 -0
  40. package/dist/evasions/chrome.runtime/index.d.ts.map +1 -0
  41. package/dist/evasions/chrome.runtime/index.js +252 -0
  42. package/dist/evasions/chrome.runtime/index.js.map +1 -0
  43. package/dist/evasions/chrome.runtime/package.json +5 -0
  44. package/dist/evasions/chrome.runtime/staticData.json +41 -0
  45. package/dist/evasions/defaultArgs/README.md +17 -0
  46. package/dist/evasions/defaultArgs/index.d.ts +2 -0
  47. package/dist/evasions/defaultArgs/index.d.ts.map +1 -0
  48. package/dist/evasions/defaultArgs/index.js +43 -0
  49. package/dist/evasions/defaultArgs/index.js.map +1 -0
  50. package/dist/evasions/defaultArgs/package.json +5 -0
  51. package/dist/evasions/iframe.contentWindow/README.md +19 -0
  52. package/dist/evasions/iframe.contentWindow/index.d.ts +15 -0
  53. package/dist/evasions/iframe.contentWindow/index.d.ts.map +1 -0
  54. package/dist/evasions/iframe.contentWindow/index.js +132 -0
  55. package/dist/evasions/iframe.contentWindow/index.js.map +1 -0
  56. package/dist/evasions/iframe.contentWindow/package.json +5 -0
  57. package/dist/evasions/media.codecs/README.md +38 -0
  58. package/dist/evasions/media.codecs/index.d.ts +12 -0
  59. package/dist/evasions/media.codecs/index.d.ts.map +1 -0
  60. package/dist/evasions/media.codecs/index.js +89 -0
  61. package/dist/evasions/media.codecs/index.js.map +1 -0
  62. package/dist/evasions/media.codecs/package.json +5 -0
  63. package/dist/evasions/navigator.hardwareConcurrency/README.md +19 -0
  64. package/dist/evasions/navigator.hardwareConcurrency/index.d.ts +2 -0
  65. package/dist/evasions/navigator.hardwareConcurrency/index.d.ts.map +1 -0
  66. package/dist/evasions/navigator.hardwareConcurrency/index.js +45 -0
  67. package/dist/evasions/navigator.hardwareConcurrency/index.js.map +1 -0
  68. package/dist/evasions/navigator.hardwareConcurrency/package.json +5 -0
  69. package/dist/evasions/navigator.languages/README.md +17 -0
  70. package/dist/evasions/navigator.languages/index.d.ts +2 -0
  71. package/dist/evasions/navigator.languages/index.d.ts.map +1 -0
  72. package/dist/evasions/navigator.languages/index.js +44 -0
  73. package/dist/evasions/navigator.languages/index.js.map +1 -0
  74. package/dist/evasions/navigator.languages/package.json +5 -0
  75. package/dist/evasions/navigator.permissions/README.md +16 -0
  76. package/dist/evasions/navigator.permissions/index.d.ts +2 -0
  77. package/dist/evasions/navigator.permissions/index.d.ts.map +1 -0
  78. package/dist/evasions/navigator.permissions/index.js +66 -0
  79. package/dist/evasions/navigator.permissions/index.js.map +1 -0
  80. package/dist/evasions/navigator.permissions/package.json +5 -0
  81. package/dist/evasions/navigator.plugins/README.md +24 -0
  82. package/dist/evasions/navigator.plugins/data.json +48 -0
  83. package/dist/evasions/navigator.plugins/functionMocks.d.ts +9 -0
  84. package/dist/evasions/navigator.plugins/functionMocks.d.ts.map +1 -0
  85. package/dist/evasions/navigator.plugins/functionMocks.js +47 -0
  86. package/dist/evasions/navigator.plugins/functionMocks.js.map +1 -0
  87. package/dist/evasions/navigator.plugins/index.d.ts +19 -0
  88. package/dist/evasions/navigator.plugins/index.d.ts.map +1 -0
  89. package/dist/evasions/navigator.plugins/index.js +98 -0
  90. package/dist/evasions/navigator.plugins/index.js.map +1 -0
  91. package/dist/evasions/navigator.plugins/magicArray.d.ts +2 -0
  92. package/dist/evasions/navigator.plugins/magicArray.d.ts.map +1 -0
  93. package/dist/evasions/navigator.plugins/magicArray.js +145 -0
  94. package/dist/evasions/navigator.plugins/magicArray.js.map +1 -0
  95. package/dist/evasions/navigator.plugins/mimeTypes.d.ts +2 -0
  96. package/dist/evasions/navigator.plugins/mimeTypes.d.ts.map +1 -0
  97. package/dist/evasions/navigator.plugins/mimeTypes.js +18 -0
  98. package/dist/evasions/navigator.plugins/mimeTypes.js.map +1 -0
  99. package/dist/evasions/navigator.plugins/package.json +5 -0
  100. package/dist/evasions/navigator.plugins/plugins.d.ts +2 -0
  101. package/dist/evasions/navigator.plugins/plugins.d.ts.map +1 -0
  102. package/dist/evasions/navigator.plugins/plugins.js +18 -0
  103. package/dist/evasions/navigator.plugins/plugins.js.map +1 -0
  104. package/dist/evasions/navigator.vendor/README.md +36 -0
  105. package/dist/evasions/navigator.vendor/index.d.ts +2 -0
  106. package/dist/evasions/navigator.vendor/index.d.ts.map +1 -0
  107. package/dist/evasions/navigator.vendor/index.js +64 -0
  108. package/dist/evasions/navigator.vendor/index.js.map +1 -0
  109. package/dist/evasions/navigator.vendor/package.json +5 -0
  110. package/dist/evasions/navigator.webdriver/README.md +17 -0
  111. package/dist/evasions/navigator.webdriver/index.d.ts +13 -0
  112. package/dist/evasions/navigator.webdriver/index.d.ts.map +1 -0
  113. package/dist/evasions/navigator.webdriver/index.js +48 -0
  114. package/dist/evasions/navigator.webdriver/index.js.map +1 -0
  115. package/dist/evasions/navigator.webdriver/package.json +5 -0
  116. package/dist/evasions/sourceurl/README.md +17 -0
  117. package/dist/evasions/sourceurl/_fixtures/test.html +35 -0
  118. package/dist/evasions/sourceurl/index.d.ts +12 -0
  119. package/dist/evasions/sourceurl/index.d.ts.map +1 -0
  120. package/dist/evasions/sourceurl/index.js +82 -0
  121. package/dist/evasions/sourceurl/index.js.map +1 -0
  122. package/dist/evasions/sourceurl/package.json +5 -0
  123. package/dist/evasions/user-agent-override/README.md +53 -0
  124. package/dist/evasions/user-agent-override/index.d.ts +2 -0
  125. package/dist/evasions/user-agent-override/index.d.ts.map +1 -0
  126. package/dist/evasions/user-agent-override/index.js +206 -0
  127. package/dist/evasions/user-agent-override/index.js.map +1 -0
  128. package/dist/evasions/user-agent-override/package.json +5 -0
  129. package/dist/evasions/webgl.vendor/README.md +20 -0
  130. package/dist/evasions/webgl.vendor/index.d.ts +17 -0
  131. package/dist/evasions/webgl.vendor/index.d.ts.map +1 -0
  132. package/dist/evasions/webgl.vendor/index.js +57 -0
  133. package/dist/evasions/webgl.vendor/index.js.map +1 -0
  134. package/dist/evasions/webgl.vendor/package.json +5 -0
  135. package/dist/evasions/window.outerdimensions/README.md +17 -0
  136. package/dist/evasions/window.outerdimensions/index.d.ts +13 -0
  137. package/dist/evasions/window.outerdimensions/index.d.ts.map +1 -0
  138. package/dist/evasions/window.outerdimensions/index.js +42 -0
  139. package/dist/evasions/window.outerdimensions/index.js.map +1 -0
  140. package/dist/evasions/window.outerdimensions/package.json +5 -0
  141. package/dist/index.d.ts +130 -0
  142. package/dist/index.d.ts.map +1 -0
  143. package/dist/index.js +164 -0
  144. package/dist/index.js.map +1 -0
  145. package/examples/detect-headless.js +95 -0
  146. package/examples/test1.js +20 -0
  147. package/examples/test2.js +25 -0
  148. package/package.json +70 -0
  149. package/src/evasions/README.md +13 -0
  150. package/src/evasions/_template/README.md +18 -0
  151. package/src/evasions/_template/index.js +26 -0
  152. package/src/evasions/_template/package.json +5 -0
  153. package/src/evasions/_utils/README.md +287 -0
  154. package/src/evasions/_utils/index.js +588 -0
  155. package/src/evasions/_utils/withUtils.js +47 -0
  156. package/src/evasions/chrome.app/README.md +16 -0
  157. package/src/evasions/chrome.app/index.js +97 -0
  158. package/src/evasions/chrome.app/package.json +5 -0
  159. package/src/evasions/chrome.csi/README.md +29 -0
  160. package/src/evasions/chrome.csi/index.js +69 -0
  161. package/src/evasions/chrome.csi/package.json +5 -0
  162. package/src/evasions/chrome.loadTimes/README.md +27 -0
  163. package/src/evasions/chrome.loadTimes/index.js +163 -0
  164. package/src/evasions/chrome.loadTimes/package.json +5 -0
  165. package/src/evasions/chrome.runtime/README.md +32 -0
  166. package/src/evasions/chrome.runtime/index.js +252 -0
  167. package/src/evasions/chrome.runtime/package.json +5 -0
  168. package/src/evasions/chrome.runtime/staticData.json +41 -0
  169. package/src/evasions/defaultArgs/README.md +17 -0
  170. package/src/evasions/defaultArgs/index.js +43 -0
  171. package/src/evasions/defaultArgs/package.json +5 -0
  172. package/src/evasions/iframe.contentWindow/README.md +19 -0
  173. package/src/evasions/iframe.contentWindow/index.js +132 -0
  174. package/src/evasions/iframe.contentWindow/package.json +5 -0
  175. package/src/evasions/media.codecs/README.md +38 -0
  176. package/src/evasions/media.codecs/index.js +89 -0
  177. package/src/evasions/media.codecs/package.json +5 -0
  178. package/src/evasions/navigator.hardwareConcurrency/README.md +19 -0
  179. package/src/evasions/navigator.hardwareConcurrency/index.js +45 -0
  180. package/src/evasions/navigator.hardwareConcurrency/package.json +5 -0
  181. package/src/evasions/navigator.languages/README.md +17 -0
  182. package/src/evasions/navigator.languages/index.js +44 -0
  183. package/src/evasions/navigator.languages/package.json +5 -0
  184. package/src/evasions/navigator.permissions/README.md +16 -0
  185. package/src/evasions/navigator.permissions/index.js +66 -0
  186. package/src/evasions/navigator.permissions/package.json +5 -0
  187. package/src/evasions/navigator.plugins/README.md +24 -0
  188. package/src/evasions/navigator.plugins/data.json +48 -0
  189. package/src/evasions/navigator.plugins/functionMocks.js +47 -0
  190. package/src/evasions/navigator.plugins/index.js +98 -0
  191. package/src/evasions/navigator.plugins/magicArray.js +145 -0
  192. package/src/evasions/navigator.plugins/mimeTypes.js +18 -0
  193. package/src/evasions/navigator.plugins/package.json +5 -0
  194. package/src/evasions/navigator.plugins/plugins.js +18 -0
  195. package/src/evasions/navigator.vendor/README.md +36 -0
  196. package/src/evasions/navigator.vendor/index.js +64 -0
  197. package/src/evasions/navigator.vendor/package.json +5 -0
  198. package/src/evasions/navigator.webdriver/README.md +17 -0
  199. package/src/evasions/navigator.webdriver/index.js +48 -0
  200. package/src/evasions/navigator.webdriver/package.json +5 -0
  201. package/src/evasions/sourceurl/README.md +17 -0
  202. package/src/evasions/sourceurl/_fixtures/test.html +35 -0
  203. package/src/evasions/sourceurl/index.js +82 -0
  204. package/src/evasions/sourceurl/package.json +5 -0
  205. package/src/evasions/user-agent-override/README.md +53 -0
  206. package/src/evasions/user-agent-override/index.js +206 -0
  207. package/src/evasions/user-agent-override/package.json +5 -0
  208. package/src/evasions/webgl.vendor/README.md +20 -0
  209. package/src/evasions/webgl.vendor/index.js +57 -0
  210. package/src/evasions/webgl.vendor/package.json +5 -0
  211. package/src/evasions/window.outerdimensions/README.md +17 -0
  212. package/src/evasions/window.outerdimensions/index.js +42 -0
  213. package/src/evasions/window.outerdimensions/package.json +5 -0
  214. package/src/index.d.ts +111 -0
  215. package/src/index.ts +188 -0
  216. package/test/cat-and-mouse.test.ts +170 -0
  217. package/test/evasions/_utils/index.test.ts +710 -0
  218. package/test/evasions/chrome.app/index.test.ts +69 -0
  219. package/test/evasions/chrome.csi/index.test.ts +46 -0
  220. package/test/evasions/chrome.loadTimes/index.test.ts +61 -0
  221. package/test/evasions/chrome.runtime/index.test.ts +282 -0
  222. package/test/evasions/defaultArgs/index.test.ts +36 -0
  223. package/test/evasions/iframe.contentWindow/index.test.js +450 -0
  224. package/test/evasions/media.codecs/index.test.js +103 -0
  225. package/test/evasions/navigator.hardwareConcurrency/index.test.js +58 -0
  226. package/test/evasions/navigator.languages/index.test.js +101 -0
  227. package/test/evasions/navigator.permissions/index.test.js +104 -0
  228. package/test/evasions/navigator.plugins/index.test.js +55 -0
  229. package/test/evasions/navigator.plugins/mimeTypes.test.js +220 -0
  230. package/test/evasions/navigator.plugins/plugins.test.js +181 -0
  231. package/test/evasions/navigator.vendor/index.test.js +68 -0
  232. package/test/evasions/navigator.webdriver/index.test.js +47 -0
  233. package/test/evasions/sourceurl/_fixtures/test.html +35 -0
  234. package/test/evasions/sourceurl/index.test.js +62 -0
  235. package/test/evasions/user-agent-override/index.test.js +338 -0
  236. package/test/evasions/webgl.vendor/index.test.js +220 -0
  237. package/test/fixtures/dummy-with-service-worker.html +22 -0
  238. package/test/fixtures/dummy.html +11 -0
  239. package/test/fixtures/sw.js +1 -0
  240. package/test/fpscanner.test.ts +54 -0
  241. package/test/index.test.ts +51 -0
  242. package/test/service-worker.test.ts +112 -0
  243. package/test/stealth/_results/_thumbs/headful-chrome-stealth.js.png +0 -0
  244. package/test/stealth/_results/_thumbs/headful-chrome-vanilla.js.png +0 -0
  245. package/test/stealth/_results/_thumbs/headful-chromium-stealth.js.png +0 -0
  246. package/test/stealth/_results/_thumbs/headful-chromium-vanilla.js.png +0 -0
  247. package/test/stealth/_results/_thumbs/headless-chrome-stealth.js.png +0 -0
  248. package/test/stealth/_results/_thumbs/headless-chrome-vanilla.js.png +0 -0
  249. package/test/stealth/_results/_thumbs/headless-chromium-stealth.js.png +0 -0
  250. package/test/stealth/_results/_thumbs/headless-chromium-vanilla.js.png +0 -0
  251. package/test/stealth/_results/headful-chrome-stealth.js.png +0 -0
  252. package/test/stealth/_results/headful-chrome-vanilla.js.png +0 -0
  253. package/test/stealth/_results/headful-chromium-stealth.js.png +0 -0
  254. package/test/stealth/_results/headful-chromium-vanilla.js.png +0 -0
  255. package/test/stealth/_results/headless-chrome-stealth.js.png +0 -0
  256. package/test/stealth/_results/headless-chrome-vanilla.js.png +0 -0
  257. package/test/stealth/_results/headless-chromium-stealth.js.png +0 -0
  258. package/test/stealth/_results/headless-chromium-vanilla.js.png +0 -0
  259. package/test/stealth/headful-chrome-stealth.js +25 -0
  260. package/test/stealth/headful-chrome-vanilla.js +23 -0
  261. package/test/stealth/headful-chromium-stealth.js +22 -0
  262. package/test/stealth/headful-chromium-vanilla.js +20 -0
  263. package/test/stealth/headless-chrome-stealth.js +25 -0
  264. package/test/stealth/headless-chrome-vanilla.js +23 -0
  265. package/test/stealth/headless-chromium-stealth.js +22 -0
  266. package/test/stealth/headless-chromium-vanilla.js +20 -0
  267. package/test/util.js +82 -0
  268. package/tsconfig.json +10 -0
  269. package/tsconfig.tsbuildinfo +1 -0
  270. package/vitest.config.ts +28 -0
@@ -0,0 +1,45 @@
1
+ import { PuppeteerExtraPlugin } from '@zorilla/puppeteer-extra-plugin';
2
+
3
+ import withUtils from '../_utils/withUtils.js';
4
+
5
+ /**
6
+ * Set the hardwareConcurrency to 4 (optionally configurable with `hardwareConcurrency`)
7
+ *
8
+ * @see https://arh.antoinevastel.com/reports/stats/osName_hardwareConcurrency_report.html
9
+ *
10
+ * @param {Object} [opts] - Options
11
+ * @param {number} [opts.hardwareConcurrency] - The value to use in `navigator.hardwareConcurrency` (default: `4`)
12
+ */
13
+
14
+ class Plugin extends PuppeteerExtraPlugin {
15
+ constructor(opts = {}) {
16
+ super(opts);
17
+ }
18
+
19
+ get name() {
20
+ return 'stealth/evasions/navigator.hardwareConcurrency';
21
+ }
22
+
23
+ get defaults() {
24
+ return {
25
+ hardwareConcurrency: 4,
26
+ };
27
+ }
28
+
29
+ async onPageCreated(page) {
30
+ await withUtils(page).evaluateOnNewDocument(
31
+ (utils, { opts }) => {
32
+ utils.replaceGetterWithProxy(
33
+ Object.getPrototypeOf(navigator),
34
+ 'hardwareConcurrency',
35
+ utils.makeHandler().getterValue(opts.hardwareConcurrency)
36
+ );
37
+ },
38
+ {
39
+ opts: this.opts,
40
+ }
41
+ );
42
+ }
43
+ }
44
+
45
+ module.exports = pluginConfig => new Plugin(pluginConfig);
@@ -0,0 +1,5 @@
1
+ {
2
+ "private": true,
3
+ "type": "module",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,17 @@
1
+ ## API
2
+
3
+
4
+ #### Table of Contents
5
+
6
+ - [class: Plugin](#class-plugin)
7
+
8
+ ### class: [Plugin](https://github.com/zorillajs/zorilla/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.languages/index.js#L11-L28)
9
+
10
+ - `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`)
11
+ - `opts.languages` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>?** The languages to use (default: `['en-US', 'en']`)
12
+
13
+ **Extends: PuppeteerExtraPlugin**
14
+
15
+ Pass the Languages Test. Allows setting custom languages.
16
+
17
+ ---
@@ -0,0 +1,44 @@
1
+ import { PuppeteerExtraPlugin } from '@zorilla/puppeteer-extra-plugin';
2
+ import withUtils from '../_utils/withUtils.js';
3
+
4
+ /**
5
+ * Pass the Languages Test. Allows setting custom languages.
6
+ *
7
+ * @param {Object} [opts] - Options
8
+ * @param {Array<string>} [opts.languages] - The languages to use (default: `['en-US', 'en']`)
9
+ */
10
+ class Plugin extends PuppeteerExtraPlugin {
11
+ constructor(opts = {}) {
12
+ super(opts);
13
+ }
14
+
15
+ get name() {
16
+ return 'stealth/evasions/navigator.languages';
17
+ }
18
+
19
+ get defaults() {
20
+ return {
21
+ languages: [], // Empty default, otherwise this would be merged with user defined array override
22
+ };
23
+ }
24
+
25
+ async onPageCreated(page) {
26
+ await withUtils(page).evaluateOnNewDocument(
27
+ (utils, { opts }) => {
28
+ const languages = opts.languages.length
29
+ ? opts.languages
30
+ : ['en-US', 'en'];
31
+ utils.replaceGetterWithProxy(
32
+ Object.getPrototypeOf(navigator),
33
+ 'languages',
34
+ utils.makeHandler().getterValue(Object.freeze([...languages]))
35
+ );
36
+ },
37
+ {
38
+ opts: this.opts,
39
+ }
40
+ );
41
+ }
42
+ }
43
+
44
+ module.exports = pluginConfig => new Plugin(pluginConfig);
@@ -0,0 +1,5 @@
1
+ {
2
+ "private": true,
3
+ "type": "module",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,16 @@
1
+ ## API
2
+
3
+
4
+ #### Table of Contents
5
+
6
+ - [class: Plugin](#class-plugin)
7
+
8
+ ### class: [Plugin](https://github.com/zorillajs/zorilla/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.permissions/index.js#L12-L45)
9
+
10
+ - `opts` (optional, default `{}`)
11
+
12
+ **Extends: PuppeteerExtraPlugin**
13
+
14
+ Pass the Permissions Test.
15
+
16
+ ---
@@ -0,0 +1,66 @@
1
+ import { PuppeteerExtraPlugin } from '@zorilla/puppeteer-extra-plugin';
2
+
3
+ import withUtils from '../_utils/withUtils.js';
4
+
5
+ /**
6
+ * Fix `Notification.permission` behaving weirdly in headless mode
7
+ *
8
+ * @see https://bugs.chromium.org/p/chromium/issues/detail?id=1052332
9
+ */
10
+
11
+ class Plugin extends PuppeteerExtraPlugin {
12
+ constructor(opts = {}) {
13
+ super(opts);
14
+ }
15
+
16
+ get name() {
17
+ return 'stealth/evasions/navigator.permissions';
18
+ }
19
+
20
+ /* global Notification Permissions PermissionStatus */
21
+ async onPageCreated(page) {
22
+ await withUtils(page).evaluateOnNewDocument((utils, _opts) => {
23
+ const isSecure = document.location.protocol.startsWith('https');
24
+
25
+ // In headful on secure origins the permission should be "default", not "denied"
26
+ if (isSecure) {
27
+ utils.replaceGetterWithProxy(Notification, 'permission', {
28
+ apply() {
29
+ return 'default';
30
+ },
31
+ });
32
+ }
33
+
34
+ // Another weird behavior:
35
+ // On insecure origins in headful the state is "denied",
36
+ // whereas in headless it's "prompt"
37
+ if (!isSecure) {
38
+ const handler = {
39
+ apply(_target, _ctx, args) {
40
+ const param = (args || [])[0];
41
+
42
+ const isNotifications =
43
+ param?.name && param.name === 'notifications';
44
+ if (!isNotifications) {
45
+ return utils.cache.Reflect.apply(...arguments);
46
+ }
47
+
48
+ return Promise.resolve(
49
+ Object.setPrototypeOf(
50
+ {
51
+ state: 'denied',
52
+ onchange: null,
53
+ },
54
+ PermissionStatus.prototype
55
+ )
56
+ );
57
+ },
58
+ };
59
+ // Note: Don't use `Object.getPrototypeOf` here
60
+ utils.replaceWithProxy(Permissions.prototype, 'query', handler);
61
+ }
62
+ }, this.opts);
63
+ }
64
+ }
65
+
66
+ module.exports = pluginConfig => new Plugin(pluginConfig);
@@ -0,0 +1,5 @@
1
+ {
2
+ "private": true,
3
+ "type": "module",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,24 @@
1
+ ## API
2
+
3
+
4
+ #### Table of Contents
5
+
6
+ - [class: Plugin](#class-plugin)
7
+
8
+ ### class: [Plugin](https://github.com/zorillajs/zorilla/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.js#L26-L88)
9
+
10
+ - `opts` (optional, default `{}`)
11
+
12
+ **Extends: PuppeteerExtraPlugin**
13
+
14
+ In headless mode `navigator.mimeTypes` and `navigator.plugins` are empty.
15
+ This plugin emulates both of these with functional mocks to match regular headful Chrome.
16
+
17
+ Note: mimeTypes and plugins cross-reference each other, so it makes sense to do them at the same time.
18
+
19
+ - **See: <https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes>**
20
+ - **See: <https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray>**
21
+ - **See: <https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins>**
22
+ - **See: <https://developer.mozilla.org/en-US/docs/Web/API/PluginArray>**
23
+
24
+ ---
@@ -0,0 +1,48 @@
1
+ {
2
+ "mimeTypes": [
3
+ {
4
+ "type": "application/pdf",
5
+ "suffixes": "pdf",
6
+ "description": "Portable Document Format",
7
+ "__pluginName": "PDF Viewer"
8
+ },
9
+ {
10
+ "type": "text/pdf",
11
+ "suffixes": "pdf",
12
+ "description": "Portable Document Format",
13
+ "__pluginName": "PDF Viewer"
14
+ }
15
+ ],
16
+ "plugins": [
17
+ {
18
+ "name": "PDF Viewer",
19
+ "filename": "internal-pdf-viewer",
20
+ "description": "Portable Document Format",
21
+ "__mimeTypes": ["application/pdf", "text/pdf"]
22
+ },
23
+ {
24
+ "name": "Chrome PDF Viewer",
25
+ "filename": "mhjfbmdgcfjbbpaeojofohoefgiehjai",
26
+ "description": "Portable Document Format",
27
+ "__mimeTypes": ["application/pdf", "text/pdf"]
28
+ },
29
+ {
30
+ "name": "Chromium PDF Viewer",
31
+ "filename": "chromium-pdf-viewer",
32
+ "description": "Portable Document Format",
33
+ "__mimeTypes": ["application/pdf", "text/pdf"]
34
+ },
35
+ {
36
+ "name": "Microsoft Edge PDF Viewer",
37
+ "filename": "edge-pdf-viewer",
38
+ "description": "Portable Document Format",
39
+ "__mimeTypes": ["application/pdf", "text/pdf"]
40
+ },
41
+ {
42
+ "name": "WebKit built-in PDF",
43
+ "filename": "webkit-pdf-plugin",
44
+ "description": "Portable Document Format",
45
+ "__mimeTypes": ["application/pdf", "text/pdf"]
46
+ }
47
+ ]
48
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `navigator.{plugins,mimeTypes}` share similar custom functions to look up properties
3
+ *
4
+ * Note: This is meant to be run in the context of the page.
5
+ */
6
+ export const generateFunctionMocks =
7
+ utils => (proto, itemMainProp, dataArray) => ({
8
+ /** Returns the MimeType object with the specified index. */
9
+ item: utils.createProxy(proto.item, {
10
+ apply(_target, _ctx, args) {
11
+ if (!args.length) {
12
+ throw new TypeError(
13
+ `Failed to execute 'item' on '${
14
+ proto[Symbol.toStringTag]
15
+ }': 1 argument required, but only 0 present.`
16
+ );
17
+ }
18
+ // Special behavior alert:
19
+ // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookup
20
+ // - If anything else than an integer (including as string) is provided it will return the first entry
21
+ const isInteger = args[0] && Number.isInteger(Number(args[0])); // Cast potential string to number first, then check for integer
22
+ // Note: Vanilla never returns `undefined`
23
+ return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || null;
24
+ },
25
+ }),
26
+ /** Returns the MimeType object with the specified name. */
27
+ namedItem: utils.createProxy(proto.namedItem, {
28
+ apply(_target, _ctx, args) {
29
+ if (!args.length) {
30
+ throw new TypeError(
31
+ `Failed to execute 'namedItem' on '${
32
+ proto[Symbol.toStringTag]
33
+ }': 1 argument required, but only 0 present.`
34
+ );
35
+ }
36
+ return dataArray.find(mt => mt[itemMainProp] === args[0]) || null; // Not `undefined`!
37
+ },
38
+ }),
39
+ /** Does nothing and shall return nothing */
40
+ refresh: proto.refresh
41
+ ? utils.createProxy(proto.refresh, {
42
+ apply(_target, _ctx, _args) {
43
+ return undefined;
44
+ },
45
+ })
46
+ : undefined,
47
+ });
@@ -0,0 +1,98 @@
1
+ import { PuppeteerExtraPlugin } from '@zorilla/puppeteer-extra-plugin';
2
+
3
+ import utils from '../_utils/index.js';
4
+
5
+ import withUtils from '../_utils/withUtils.js';
6
+ import data from './data.json' with { type: 'json' };
7
+ import { generateFunctionMocks } from './functionMocks.js';
8
+ import { generateMagicArray } from './magicArray.js';
9
+ import { generateMimeTypeArray } from './mimeTypes.js';
10
+ import { generatePluginArray } from './plugins.js';
11
+
12
+ /**
13
+ * In headless mode `navigator.mimeTypes` and `navigator.plugins` are empty.
14
+ * This plugin emulates both of these with functional mocks to match regular headful Chrome.
15
+ *
16
+ * Note: mimeTypes and plugins cross-reference each other, so it makes sense to do them at the same time.
17
+ *
18
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes
19
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray
20
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins
21
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/PluginArray
22
+ */
23
+ class Plugin extends PuppeteerExtraPlugin {
24
+ constructor(opts = {}) {
25
+ super(opts);
26
+ }
27
+
28
+ get name() {
29
+ return 'stealth/evasions/navigator.plugins';
30
+ }
31
+
32
+ async onPageCreated(page) {
33
+ await withUtils(page).evaluateOnNewDocument(
34
+ (utils, { fns, data }) => {
35
+ fns = utils.materializeFns(fns);
36
+
37
+ // That means we're running headful
38
+ const hasPlugins = 'plugins' in navigator && navigator.plugins.length;
39
+ if (hasPlugins) {
40
+ return; // nothing to do here
41
+ }
42
+
43
+ const mimeTypes = fns.generateMimeTypeArray(utils, fns)(data.mimeTypes);
44
+ const plugins = fns.generatePluginArray(utils, fns)(data.plugins);
45
+
46
+ // Plugin and MimeType cross-reference each other, let's do that now
47
+ // Note: We're looping through `data.plugins` here, not the generated `plugins`
48
+ for (const pluginData of data.plugins) {
49
+ pluginData.__mimeTypes.forEach((type, index) => {
50
+ plugins[pluginData.name][index] = mimeTypes[type];
51
+
52
+ Object.defineProperty(plugins[pluginData.name], type, {
53
+ value: mimeTypes[type],
54
+ writable: false,
55
+ enumerable: false, // Not enumerable
56
+ configurable: true,
57
+ });
58
+ Object.defineProperty(mimeTypes[type], 'enabledPlugin', {
59
+ value:
60
+ type === 'application/x-pnacl'
61
+ ? mimeTypes['application/x-nacl'].enabledPlugin // these reference the same plugin, so we need to re-use the Proxy in order to avoid leaks
62
+ : new Proxy(plugins[pluginData.name], {}), // Prevent circular references
63
+ writable: false,
64
+ enumerable: false, // Important: `JSON.stringify(navigator.plugins)`
65
+ configurable: true,
66
+ });
67
+ });
68
+ }
69
+
70
+ const patchNavigator = (name, value) =>
71
+ utils.replaceProperty(Object.getPrototypeOf(navigator), name, {
72
+ get() {
73
+ return value;
74
+ },
75
+ });
76
+
77
+ patchNavigator('mimeTypes', mimeTypes);
78
+ patchNavigator('plugins', plugins);
79
+
80
+ // All done
81
+ },
82
+ {
83
+ // We pass some functions to evaluate to structure the code more nicely
84
+ fns: utils.stringifyFns({
85
+ generateMimeTypeArray,
86
+ generatePluginArray,
87
+ generateMagicArray,
88
+ generateFunctionMocks,
89
+ }),
90
+ data,
91
+ }
92
+ );
93
+ }
94
+ }
95
+
96
+ export default function (pluginConfig) {
97
+ return new Plugin(pluginConfig);
98
+ }
@@ -0,0 +1,145 @@
1
+ /* global MimeType MimeTypeArray Plugin PluginArray */
2
+
3
+ /**
4
+ * Generate a convincing and functional MimeType or Plugin array from scratch.
5
+ * They're so similar that it makes sense to use a single generator here.
6
+ *
7
+ * Note: This is meant to be run in the context of the page.
8
+ */
9
+ export const generateMagicArray =
10
+ (utils, fns) =>
11
+ (
12
+ dataArray = [],
13
+ proto = MimeTypeArray.prototype,
14
+ itemProto = MimeType.prototype,
15
+ itemMainProp = 'type'
16
+ ) => {
17
+ // Quick helper to set props with the same descriptors vanilla is using
18
+ const defineProp = (obj, prop, value) =>
19
+ Object.defineProperty(obj, prop, {
20
+ value,
21
+ writable: false,
22
+ enumerable: false, // Important for mimeTypes & plugins: `JSON.stringify(navigator.mimeTypes)`
23
+ configurable: true,
24
+ });
25
+
26
+ // Loop over our fake data and construct items
27
+ const makeItem = data => {
28
+ const item = {};
29
+ for (const prop of Object.keys(data)) {
30
+ if (prop.startsWith('__')) {
31
+ continue;
32
+ }
33
+ defineProp(item, prop, data[prop]);
34
+ }
35
+ return patchItem(item, data);
36
+ };
37
+
38
+ const patchItem = (item, data) => {
39
+ let descriptor = Object.getOwnPropertyDescriptors(item);
40
+
41
+ // Special case: Plugins have a magic length property which is not enumerable
42
+ // e.g. `navigator.plugins[i].length` should always be the length of the assigned mimeTypes
43
+ if (itemProto === Plugin.prototype) {
44
+ descriptor = {
45
+ ...descriptor,
46
+ length: {
47
+ value: data.__mimeTypes.length,
48
+ writable: false,
49
+ enumerable: false,
50
+ configurable: true, // Important to be able to use the ownKeys trap in a Proxy to strip `length`
51
+ },
52
+ };
53
+ }
54
+
55
+ // We need to spoof a specific `MimeType` or `Plugin` object
56
+ const obj = Object.create(itemProto, descriptor);
57
+
58
+ // Virtually all property keys are not enumerable in vanilla
59
+ const blacklist = [...Object.keys(data), 'length', 'enabledPlugin'];
60
+ return new Proxy(obj, {
61
+ ownKeys(target) {
62
+ return Reflect.ownKeys(target).filter(k => !blacklist.includes(k));
63
+ },
64
+ getOwnPropertyDescriptor(target, prop) {
65
+ if (blacklist.includes(prop)) {
66
+ return undefined;
67
+ }
68
+ return Reflect.getOwnPropertyDescriptor(target, prop);
69
+ },
70
+ });
71
+ };
72
+
73
+ const magicArray = [];
74
+
75
+ // Loop through our fake data and use that to create convincing entities
76
+ dataArray.forEach(data => {
77
+ magicArray.push(makeItem(data));
78
+ });
79
+
80
+ // Add direct property access based on types (e.g. `obj['application/pdf']`) afterwards
81
+ magicArray.forEach(entry => {
82
+ defineProp(magicArray, entry[itemMainProp], entry);
83
+ });
84
+
85
+ // This is the best way to fake the type to make sure this is false: `Array.isArray(navigator.mimeTypes)`
86
+ const magicArrayObj = Object.create(proto, {
87
+ ...Object.getOwnPropertyDescriptors(magicArray),
88
+
89
+ // There's one ugly quirk we unfortunately need to take care of:
90
+ // The `MimeTypeArray` prototype has an enumerable `length` property,
91
+ // but headful Chrome will still skip it when running `Object.getOwnPropertyNames(navigator.mimeTypes)`.
92
+ // To strip it we need to make it first `configurable` and can then overlay a Proxy with an `ownKeys` trap.
93
+ length: {
94
+ value: magicArray.length,
95
+ writable: false,
96
+ enumerable: false,
97
+ configurable: true, // Important to be able to use the ownKeys trap in a Proxy to strip `length`
98
+ },
99
+ });
100
+
101
+ // Generate our functional function mocks :-)
102
+ const functionMocks = fns.generateFunctionMocks(utils)(
103
+ proto,
104
+ itemMainProp,
105
+ magicArray
106
+ );
107
+
108
+ // We need to overlay our custom object with a JS Proxy
109
+ const magicArrayObjProxy = new Proxy(magicArrayObj, {
110
+ get(_target, key = '') {
111
+ // Redirect function calls to our custom proxied versions mocking the vanilla behavior
112
+ if (key === 'item') {
113
+ return functionMocks.item;
114
+ }
115
+ if (key === 'namedItem') {
116
+ return functionMocks.namedItem;
117
+ }
118
+ if (proto === PluginArray.prototype && key === 'refresh') {
119
+ return functionMocks.refresh;
120
+ }
121
+ // Everything else can pass through as normal
122
+ return utils.cache.Reflect.get(...arguments);
123
+ },
124
+ ownKeys(_target) {
125
+ // There are a couple of quirks where the original property demonstrates "magical" behavior that makes no sense
126
+ // This can be witnessed when calling `Object.getOwnPropertyNames(navigator.mimeTypes)` and the absense of `length`
127
+ // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdly
128
+ // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doing
129
+ // Specific issues when not patching this: `length` property is available, direct `types` props (e.g. `obj['application/pdf']`) are missing
130
+ const keys = [];
131
+ const typeProps = magicArray.map(mt => mt[itemMainProp]);
132
+ typeProps.forEach((_, i) => keys.push(`${i}`));
133
+ typeProps.forEach(propName => keys.push(propName));
134
+ return keys;
135
+ },
136
+ getOwnPropertyDescriptor(target, prop) {
137
+ if (prop === 'length') {
138
+ return undefined;
139
+ }
140
+ return Reflect.getOwnPropertyDescriptor(target, prop);
141
+ },
142
+ });
143
+
144
+ return magicArrayObjProxy;
145
+ };
@@ -0,0 +1,18 @@
1
+ /* global MimeType MimeTypeArray */
2
+
3
+ /**
4
+ * Generate a convincing and functional MimeTypeArray (with mime types) from scratch.
5
+ *
6
+ * Note: This is meant to be run in the context of the page.
7
+ *
8
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes
9
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray
10
+ */
11
+ export const generateMimeTypeArray = (utils, fns) => mimeTypesData => {
12
+ return fns.generateMagicArray(utils, fns)(
13
+ mimeTypesData,
14
+ MimeTypeArray.prototype,
15
+ MimeType.prototype,
16
+ 'type'
17
+ );
18
+ };
@@ -0,0 +1,5 @@
1
+ {
2
+ "private": true,
3
+ "type": "module",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,18 @@
1
+ /* global Plugin PluginArray */
2
+
3
+ /**
4
+ * Generate a convincing and functional PluginArray (with plugins) from scratch.
5
+ *
6
+ * Note: This is meant to be run in the context of the page.
7
+ *
8
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins
9
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/PluginArray
10
+ */
11
+ export const generatePluginArray = (utils, fns) => pluginsData => {
12
+ return fns.generateMagicArray(utils, fns)(
13
+ pluginsData,
14
+ PluginArray.prototype,
15
+ Plugin.prototype,
16
+ 'name'
17
+ );
18
+ };
@@ -0,0 +1,36 @@
1
+ ## API
2
+
3
+
4
+ #### Table of Contents
5
+
6
+ - [class: Plugin](#class-plugin)
7
+
8
+ ### class: [Plugin](https://github.com/zorillajs/zorilla/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.vendor/index.js#L28-L55)
9
+
10
+ - `opts` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`)
11
+ - `opts.vendor` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The vendor to use in `navigator.vendor` (default: `Google Inc.`)
12
+
13
+ **Extends: PuppeteerExtraPlugin**
14
+
15
+ By default puppeteer will have a fixed `navigator.vendor` property.
16
+
17
+ This plugin makes it possible to change this property.
18
+
19
+ Example:
20
+
21
+ ```javascript
22
+ const puppeteer = require('@zorilla/puppeteer-extra')
23
+
24
+ const StealthPlugin = require('@zorilla/puppeteer-extra-plugin-stealth')
25
+ const stealth = StealthPlugin()
26
+ // Remove this specific stealth plugin from the default set
27
+ stealth.enabledEvasions.delete('navigator.vendor')
28
+ puppeteer.use(stealth)
29
+
30
+ // Stealth plugins are just regular `puppeteer-extra` plugins and can be added as such
31
+ const NavigatorVendorPlugin = require('@zorilla/puppeteer-extra-plugin-stealth/evasions/navigator.vendor')
32
+ const nvp = NavigatorVendorPlugin({ vendor: 'Apple Computer, Inc.' }) // Custom vendor
33
+ puppeteer.use(nvp)
34
+ ```
35
+
36
+ ---