browserless 14.0.19 → 14.0.22
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.
- package/package.json +7 -7
- package/src/driver.js +20 -14
- package/src/index.js +33 -4
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "browserless",
|
|
3
3
|
"description": "The headless Chrome/Chromium driver on top of Puppeteer. Take screenshots, generate PDFs, extract text and HTML with a production-ready API.",
|
|
4
4
|
"homepage": "https://browserless.js.org",
|
|
5
|
-
"version": "14.0.
|
|
5
|
+
"version": "14.0.22",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"author": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"web-scraping"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@browserless/errors": "14.0.
|
|
39
|
-
"@browserless/goto": "14.0.
|
|
40
|
-
"@browserless/pdf": "14.0.
|
|
41
|
-
"@browserless/screenshot": "14.0.
|
|
38
|
+
"@browserless/errors": "14.0.22",
|
|
39
|
+
"@browserless/goto": "14.0.22",
|
|
40
|
+
"@browserless/pdf": "14.0.22",
|
|
41
|
+
"@browserless/screenshot": "14.0.22",
|
|
42
42
|
"debug-logfmt": "~1.4.15",
|
|
43
43
|
"kill-process-group": "~1.0.16",
|
|
44
44
|
"p-reflect": "~2.1.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"superlock": "~1.3.5"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"ava": "
|
|
51
|
+
"ava": "8",
|
|
52
52
|
"ps-list": "7",
|
|
53
53
|
"puppeteer": "25",
|
|
54
54
|
"tinyspawn": "latest"
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"timeout": "2m",
|
|
70
70
|
"workerThreads": false
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "22c8d447e55cfe1eb7b1e36af9a5cc4d9139bd23"
|
|
73
73
|
}
|
package/src/driver.js
CHANGED
|
@@ -69,26 +69,32 @@ const getPid = subprocess => {
|
|
|
69
69
|
return 'pid' in browserProcess ? browserProcess.pid : undefined
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
const close = async (subprocess, { signal = 'SIGKILL', ...debugOpts } = {}) => {
|
|
72
|
+
const close = async (subprocess, { signal = 'SIGKILL', force = false, ...debugOpts } = {}) => {
|
|
73
73
|
const pid = getPid(subprocess)
|
|
74
74
|
const hasDisconnect = subprocess && typeof subprocess.disconnect === 'function'
|
|
75
75
|
const hasClose = subprocess && typeof subprocess.close === 'function'
|
|
76
76
|
|
|
77
77
|
if (pid === undefined && !hasDisconnect && !hasClose) return
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
const child = typeof subprocess.process === 'function' ? subprocess.process() : undefined
|
|
80
|
+
const kill = () => {
|
|
81
|
+
const proc = child && child.pid ? child : subprocess
|
|
82
|
+
return proc && proc.pid ? killProcessGroup(proc, { signal }) : undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// `browser.close` removes temp files and listeners on the main process:
|
|
86
|
+
// https://github.com/puppeteer/puppeteer/blob/778ac92469d66c542c3c12fe0aa23703dd6315c2/src/node/BrowserRunner.ts#L146
|
|
87
|
+
// https://github.com/puppeteer/puppeteer/blob/69d85e874416d62de6e821bef30e5cebcfd42f15/src/node/BrowserRunner.ts#L189
|
|
88
|
+
const graceful = () => {
|
|
89
|
+
if (pid === undefined) {
|
|
90
|
+
return hasDisconnect ? subprocess.disconnect() : subprocess.close()
|
|
91
|
+
}
|
|
92
|
+
if (hasClose) return subprocess.close()
|
|
93
|
+
return kill()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A browser that has stopped answering CDP will not finish `browser.close()`.
|
|
97
|
+
await pReflect(force ? kill() : graceful())
|
|
92
98
|
|
|
93
99
|
debug('close', { pid, signal, ...debugOpts })
|
|
94
100
|
return pid === undefined ? {} : { pid }
|
package/src/index.js
CHANGED
|
@@ -14,6 +14,19 @@ const { AbortError } = pRetry
|
|
|
14
14
|
|
|
15
15
|
const driver = require('./driver')
|
|
16
16
|
|
|
17
|
+
// Chrome can stop answering CDP while `connected` stays true, so the
|
|
18
|
+
// disconnect respawn never runs. Puppeteer then waits out its 180s protocol
|
|
19
|
+
// timeout on every context, and each wait holds a pool slot.
|
|
20
|
+
const CREATE_CONTEXT_TIMEOUT = 10_000
|
|
21
|
+
|
|
22
|
+
const closeLateContext = pending => pending.then(context => context.close()).catch(() => {})
|
|
23
|
+
|
|
24
|
+
const isUnresponsiveBrowser = error =>
|
|
25
|
+
error?.name === 'TimeoutError' ||
|
|
26
|
+
(typeof error?.message === 'string' &&
|
|
27
|
+
error.message.includes('timed out') &&
|
|
28
|
+
error.message.includes('protocolTimeout'))
|
|
29
|
+
|
|
17
30
|
module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
|
|
18
31
|
const lock = withLock()
|
|
19
32
|
const goto = createGoto({ timeout: globalTimeout, ...launchOpts })
|
|
@@ -26,10 +39,10 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
|
|
|
26
39
|
return browserProcessPromise.then(browserProcess => driver.close(browserProcess, opts))
|
|
27
40
|
}
|
|
28
41
|
|
|
29
|
-
const respawn = () =>
|
|
42
|
+
const respawn = ({ force = false } = {}) =>
|
|
30
43
|
!isClosed &&
|
|
31
44
|
Promise.all([
|
|
32
|
-
browserProcessPromise.then(driver.close),
|
|
45
|
+
browserProcessPromise.then(browser => driver.close(browser, { force })),
|
|
33
46
|
(browserProcessPromise = spawn({ respawn: true }))
|
|
34
47
|
])
|
|
35
48
|
|
|
@@ -64,8 +77,24 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
|
|
|
64
77
|
|
|
65
78
|
let browserProcessPromise = spawn()
|
|
66
79
|
|
|
67
|
-
const createBrowserContext = contextOpts =>
|
|
68
|
-
|
|
80
|
+
const createBrowserContext = async contextOpts => {
|
|
81
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
82
|
+
const browser = await getBrowser()
|
|
83
|
+
const pending = browser.createBrowserContext(contextOpts)
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
return await pTimeout(pending, Math.min(globalTimeout, CREATE_CONTEXT_TIMEOUT))
|
|
87
|
+
} catch (error) {
|
|
88
|
+
closeLateContext(pending)
|
|
89
|
+
if (attempt > 0 || !isUnresponsiveBrowser(error)) throw error
|
|
90
|
+
debug('respawn', { reason: 'createBrowserContext', message: error.message })
|
|
91
|
+
await lock(async () => {
|
|
92
|
+
const current = await browserProcessPromise.catch(() => {})
|
|
93
|
+
if (current === browser) await respawn({ force: true })
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
69
98
|
|
|
70
99
|
const getBrowser = async () => {
|
|
71
100
|
if (isClosed) return browserProcessPromise
|