browserless 13.12.5 → 13.13.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.
- package/index.d.ts +2 -2
- package/package.json +2 -2
- package/src/index.js +44 -11
package/index.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface Context {
|
|
|
31
31
|
respawn: () => void
|
|
32
32
|
context: () => Promise<BrowserContext>
|
|
33
33
|
browser: () => Promise<Browser>
|
|
34
|
-
evaluate: <T>(fn: (page: Page, response?: HTTPResponse | undefined, error?: Error) => T | Promise<T>, gotoOpts?: GotoOptions) => Promise<T>
|
|
34
|
+
evaluate: <T>(fn: (page: Page, response?: HTTPResponse | undefined, error?: Error) => T | Promise<T>, gotoOpts?: GotoOptions & { keepPage?: boolean, preserveContext?: boolean }) => Promise<T>
|
|
35
35
|
goto: (page: Page, opts: GotoOptions & { url: string }) => Promise<GotoResult>
|
|
36
36
|
html: (url: string, opts?: GotoOptions) => Promise<string>
|
|
37
37
|
page: (name?: string) => Promise<Page>
|
|
@@ -41,7 +41,7 @@ export interface Context {
|
|
|
41
41
|
getDevice: (deviceName: string) => Viewport | undefined
|
|
42
42
|
report: (opts?: { benchmark?: boolean }) => Promise<HardwareInfo>
|
|
43
43
|
destroyContext: (opts?: { force?: boolean }) => Promise<void>
|
|
44
|
-
withPage: <T>(fn: (page: Page, goto: unknown) => Promise<T>, opts?: { timeout?: number }) => Promise<T>
|
|
44
|
+
withPage: <T>(fn: (page: Page, goto: unknown) => Promise<T>, opts?: { timeout?: number, keepPage?: boolean, preserveContext?: boolean }) => Promise<T>
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export interface GotoOptions {
|
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": "13.
|
|
5
|
+
"version": "13.13.0",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"author": {
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"timeout": "2m",
|
|
70
70
|
"workerThreads": false
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "e118824d6fc40a4d930b58bbe7d449731b5df2aa"
|
|
73
73
|
}
|
package/src/index.js
CHANGED
|
@@ -110,7 +110,10 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
const withPage = (
|
|
113
|
+
const withPage = (
|
|
114
|
+
fn,
|
|
115
|
+
{ timeout: evaluateTimeout, keepPage = false, preserveContext = false } = {}
|
|
116
|
+
) => {
|
|
114
117
|
const name = fn.name || 'anonymous'
|
|
115
118
|
|
|
116
119
|
return async (...args) => {
|
|
@@ -119,24 +122,39 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
|
|
|
119
122
|
async function run () {
|
|
120
123
|
let page
|
|
121
124
|
let closePageTimeout
|
|
125
|
+
let isRetained = false
|
|
122
126
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
closePageTimeout = setTimeout(() => {
|
|
127
|
+
const startCloseTimeout = () => {
|
|
128
|
+
const timer = setTimeout(() => {
|
|
126
129
|
closePage(page, name).catch(error => {
|
|
127
130
|
const { message, code, name } = ensureError(error)
|
|
128
131
|
debug('closePage:timeout:error', { message, code, name })
|
|
129
132
|
})
|
|
130
133
|
}, timeout)
|
|
131
|
-
if (typeof
|
|
134
|
+
if (typeof timer.unref === 'function') timer.unref()
|
|
135
|
+
return timer
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
page = await createPage(name)
|
|
140
|
+
closePageTimeout = startCloseTimeout()
|
|
132
141
|
const value = await fn(page, goto)(...args)
|
|
133
|
-
|
|
142
|
+
// A timeout already rejected the caller, who never received this
|
|
143
|
+
// page. Retaining it would clear the in-call watchdog and arm a
|
|
144
|
+
// new one with nobody holding the page.
|
|
145
|
+
if (keepPage && !isRejected) isRetained = true
|
|
146
|
+
else await closePage(page, `${name}:success`)
|
|
134
147
|
return value
|
|
135
148
|
} catch (error) {
|
|
136
149
|
await closePage(page, `${name}:error`)
|
|
137
150
|
if (!isRejected) throw ensureError(error)
|
|
138
151
|
} finally {
|
|
152
|
+
// The in-call watchdog ends with the call. A retained page gets a
|
|
153
|
+
// fresh window of this same timeout, measured from handover.
|
|
154
|
+
// The owner has to finish or close the page inside that window.
|
|
155
|
+
// Failed attempts already closed their page, so only the last one retains.
|
|
139
156
|
if (closePageTimeout) clearTimeout(closePageTimeout)
|
|
157
|
+
if (isRetained) startCloseTimeout()
|
|
140
158
|
}
|
|
141
159
|
}
|
|
142
160
|
|
|
@@ -150,10 +168,20 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
|
|
|
150
168
|
const isRetryable =
|
|
151
169
|
error.code === 'EBRWSRCONTEXTCONNRESET' || error.code === 'EPROTOCOL'
|
|
152
170
|
if (!isRetryable) throw error
|
|
171
|
+
const { message, attemptNumber, retriesLeft } = error
|
|
172
|
+
// Every attempt builds its own page, so both codes are retryable
|
|
173
|
+
// inside the existing context: `EBRWSRCONTEXTCONNRESET` covers a
|
|
174
|
+
// closed page, a detached frame and a destroyed execution context,
|
|
175
|
+
// which a fresh page recovers from. Replacing the context is the
|
|
176
|
+
// only part a caller that owns it cannot afford, since that closes
|
|
177
|
+
// the pages it still holds. Skip that, not the retry.
|
|
178
|
+
if (preserveContext) {
|
|
179
|
+
debug('retry', { attemptNumber, retriesLeft, message, preserveContext })
|
|
180
|
+
return
|
|
181
|
+
}
|
|
153
182
|
const previousContextPromise = _contextPromise
|
|
154
183
|
_contextPromise = createBrowserContext(contextOpts)
|
|
155
184
|
await pReflect(previousContextPromise.then(ctx => ctx.close()))
|
|
156
|
-
const { message, attemptNumber, retriesLeft } = error
|
|
157
185
|
debug('retry', { attemptNumber, retriesLeft, message })
|
|
158
186
|
}
|
|
159
187
|
})
|
|
@@ -167,11 +195,15 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
|
|
|
167
195
|
}
|
|
168
196
|
}
|
|
169
197
|
|
|
170
|
-
const evaluate = (fn, gotoOpts) =>
|
|
171
|
-
withPage
|
|
198
|
+
const evaluate = (fn, gotoOpts) => {
|
|
199
|
+
// Both belong to withPage, so they are pulled out rather than spread into
|
|
200
|
+
// the navigation options.
|
|
201
|
+
const { keepPage, preserveContext, ...navigateOpts } = gotoOpts ?? {}
|
|
202
|
+
|
|
203
|
+
return withPage(
|
|
172
204
|
Object.defineProperty(
|
|
173
205
|
(page, goto) => async (url, opts) => {
|
|
174
|
-
const { response, error } = await goto(page, { url, ...
|
|
206
|
+
const { response, error } = await goto(page, { url, ...navigateOpts, ...opts })
|
|
175
207
|
return fn(page, response, error)
|
|
176
208
|
},
|
|
177
209
|
'name',
|
|
@@ -180,8 +212,9 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
|
|
|
180
212
|
writable: false
|
|
181
213
|
}
|
|
182
214
|
),
|
|
183
|
-
|
|
215
|
+
{ ...navigateOpts, keepPage, preserveContext }
|
|
184
216
|
)
|
|
217
|
+
}
|
|
185
218
|
|
|
186
219
|
const destroyContext = async ({ force = false } = {}) => {
|
|
187
220
|
if (force) isDestroyedForced = true
|