browserless 14.1.2 → 14.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.d.ts +4 -1
  2. package/package.json +2 -2
  3. package/src/index.js +30 -19
package/index.d.ts CHANGED
@@ -40,7 +40,10 @@ export interface Context {
40
40
  text: (url: string, opts?: GotoOptions) => Promise<string>
41
41
  getDevice: (deviceName: string) => Viewport | undefined
42
42
  report: (opts?: { benchmark?: boolean }) => Promise<HardwareInfo>
43
- destroyContext: (opts?: { force?: boolean }) => Promise<void>
43
+ destroyContext: (opts?: {
44
+ /** @deprecated A destroyed context never retries, so `force` has no effect. */
45
+ force?: boolean
46
+ }) => Promise<void>
44
47
  withPage: <T>(fn: (page: Page, goto: unknown) => Promise<T>, opts?: { timeout?: number, keepPage?: boolean }) => Promise<T>
45
48
  }
46
49
 
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.1.2",
5
+ "version": "14.1.3",
6
6
  "types": "index.d.ts",
7
7
  "main": "src/index.js",
8
8
  "author": {
@@ -70,5 +70,5 @@
70
70
  "timeout": "2m",
71
71
  "workerThreads": false
72
72
  },
73
- "gitHead": "700bbabadea0a16facf30d76ecf60ff9602e06f8"
73
+ "gitHead": "c45e62a88191f36685a98af337762bc0c7a72953"
74
74
  }
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { ensureError, browserTimeout } = require('@browserless/errors')
3
+ const { ensureError, browserTimeout, contextDisconnected } = require('@browserless/errors')
4
4
  const createScreenshot = require('@browserless/screenshot')
5
5
  const debug = require('debug-logfmt')('browserless')
6
6
  const createGoto = require('@browserless/goto')
@@ -111,18 +111,21 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
111
111
  }
112
112
 
113
113
  const createContext = async ({ retry = 2, timeout: contextTimeout, ...contextOpts } = {}) => {
114
- const forwardedContextOpts = isClosed
115
- ? Promise.resolve(contextOpts)
116
- : forwardProxy(contextOpts, { mode: launchOpts.mode })
117
- forwarders.add(forwardedContextOpts)
118
- const newBrowserContext = () => forwardedContextOpts.then(createBrowserContext)
119
- const usesForwarder = () => forwardedContextOpts.then(opts => opts !== contextOpts)
114
+ let forwardedContextOpts
115
+ const forwardContextOpts = () => {
116
+ forwardedContextOpts = isClosed
117
+ ? Promise.resolve(contextOpts)
118
+ : forwardProxy(contextOpts, { mode: launchOpts.mode })
119
+ forwarders.add(forwardedContextOpts)
120
+ return forwardedContextOpts
121
+ }
122
+ const newBrowserContext = () =>
123
+ (forwardedContextOpts ?? forwardContextOpts()).then(createBrowserContext)
120
124
  let _contextPromise = newBrowserContext()
121
- let isDestroyedForced = false
122
- let isDestroyed = false
125
+ let destroyCount = 0
123
126
  const pageMetadata = new WeakMap()
124
127
 
125
- const getBrowserContext = () => _contextPromise
128
+ const getBrowserContext = () => (_contextPromise ??= newBrowserContext())
126
129
 
127
130
  const createPage = async name => {
128
131
  const duration = debug.duration('createPage')
@@ -154,6 +157,8 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
154
157
 
155
158
  return async (...args) => {
156
159
  let isRejected = false
160
+ const destroysAtStart = destroyCount
161
+ const isAbandoned = () => isClosed || destroyCount !== destroysAtStart
157
162
 
158
163
  async function run () {
159
164
  let page
@@ -172,9 +177,11 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
172
177
  }
173
178
 
174
179
  try {
180
+ if (isAbandoned()) throw contextDisconnected()
175
181
  page = await createPage(name)
176
182
  closePageTimeout = startCloseTimeout()
177
183
  const value = await fn(page, goto)(...args)
184
+ if (isAbandoned()) throw contextDisconnected()
178
185
  // A timeout already rejected the caller, who never received this
179
186
  // page. Retaining it would clear the in-call watchdog and arm a
180
187
  // new one with nobody holding the page.
@@ -200,11 +207,11 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
200
207
  onFailedAttempt: async error => {
201
208
  debug('onFailedAttempt', { name: error.name, code: error.code, isRejected })
202
209
  if (error.name === 'AbortError') throw error
203
- if (isRejected || isDestroyedForced) throw new AbortError()
210
+ if (isRejected) throw new AbortError()
204
211
  const isRetryable =
205
212
  error.code === 'EBRWSRCONTEXTCONNRESET' || error.code === 'EPROTOCOL'
206
213
  if (!isRetryable) throw error
207
- if (isDestroyed && (await usesForwarder())) throw new AbortError()
214
+ if (isAbandoned()) throw new AbortError()
208
215
  const { message, attemptNumber, retriesLeft } = error
209
216
  // Every attempt builds its own page, so both codes are retryable
210
217
  // inside the existing context: `EBRWSRCONTEXTCONNRESET` covers a
@@ -247,20 +254,24 @@ module.exports = ({ timeout: globalTimeout = 30000, ...launchOpts } = {}) => {
247
254
  )
248
255
  }
249
256
 
250
- const destroyContext = async ({ force = false } = {}) => {
251
- isDestroyed = true
252
- if (force) isDestroyedForced = true
257
+ const destroyContext = async () => {
258
+ destroyCount++
259
+ const closingContextPromise = _contextPromise
260
+ const closingContextOpts = forwardedContextOpts
261
+ _contextPromise = undefined
262
+ forwardedContextOpts = undefined
263
+ if (!closingContextPromise) return
253
264
 
254
265
  try {
255
266
  const [browserProcess, browserContext] = await Promise.all([
256
267
  getBrowser(),
257
- getBrowserContext()
268
+ closingContextPromise
258
269
  ])
259
270
  await pReflect(browserContext.close())
260
- debug('destroyContext', { pid: driver.pid(browserProcess), force, id: browserContext.id })
271
+ debug('destroyContext', { pid: driver.pid(browserProcess), id: browserContext.id })
261
272
  } finally {
262
- forwarders.delete(forwardedContextOpts)
263
- await closeForwarder(forwardedContextOpts)
273
+ forwarders.delete(closingContextOpts)
274
+ await closeForwarder(closingContextOpts)
264
275
  }
265
276
  }
266
277