• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microlinkhq / browserless / 38436658604

13 Oct 2026 05:11PM UTC coverage: 77.414%. First build
38436658604

Pull #994

github

web-flow
Merge 793984c4a into b8fbd261a
Pull Request #994: fix(function): pass the browser socket as an isolate egress exception

2136 of 2422 branches covered (88.19%)

Branch coverage included in aggregate %.

44 of 46 new or added lines in 1 file covered. (95.65%)

7735 of 10329 relevant lines covered (74.89%)

58189.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.15
/packages/function/src/function.js
1
'use strict'
14 ✔
2

14 ✔
3
const { SLOT } = require('isolated-function')
14 ✔
4
const net = require('node:net')
14 ✔
5

14 ✔
6
const template = require('./template')
14 ✔
7

14 ✔
8
// `new URL()` drops a scheme's default port, so `ws://127.0.0.1:80` has an
14 ✔
9
// empty `url.port`. The port is still in the raw authority. An endpoint that
14 ✔
10
// names no port stays ungranted.
14 ✔
11
const portOf = (endpoint, url) => {
14 ✔
12
  if (url.port !== '') return url.port
41 ✔
13
  const scheme = endpoint.indexOf('://')
9 ✔
14
  if (scheme === -1) return ''
14 !
15
  const authority = endpoint.slice(scheme + 3).split(/[/?#]/, 1)[0]
9 ✔
16
  const colon = authority.lastIndexOf(':')
9 ✔
17
  if (colon === -1 || authority.lastIndexOf(']') > colon) return ''
41 ✔
18
  const port = authority.slice(colon + 1)
3 ✔
19
  return /^\d+$/.test(port) ? port : ''
41 !
20
}
41 ✔
21

14 ✔
22
const pair = (address, port) => {
14 ✔
23
  const value = String(port)
17 ✔
24
  return net.isIP(address) === 6 ? `[${address}]:${value}` : `${address}:${value}`
17 ✔
25
}
17 ✔
26

14 ✔
27
const egressOf = endpoint => {
14 ✔
28
  if (typeof endpoint !== 'string') return []
73 ✔
29
  let url
41 ✔
30
  try {
41 ✔
31
    url = new URL(endpoint)
41 ✔
32
  } catch {
54 !
NEW
33
    return []
×
NEW
34
  }
×
35
  const port = portOf(endpoint, url)
41 ✔
36
  if (port === '') return []
54 ✔
37
  const host =
35 ✔
38
    url.hostname.startsWith('[') && url.hostname.endsWith(']')
49 ✔
39
      ? url.hostname.slice(1, -1)
73 ✔
40
      : url.hostname
73 ✔
41
  if (net.isIP(host) === 0) return []
73 ✔
42
  return [net.isIP(host) === 6 ? `[${host}]:${port}` : `${host}:${port}`]
73 ✔
43
}
73 ✔
44

14 ✔
45
const [nodeMajor] = process.version.slice(1).split('.').map(Number)
14 ✔
46

14 ✔
47
/**
14 ✔
48
 * The template is built around `SLOT` rather than the user code, so every
14 ✔
49
 * function with the same page shape shares one bundled program and only the
14 ✔
50
 * slot is filled per call, instead of re-bundling the whole program.
14 ✔
51
 */
14 ✔
52
module.exports =
14 ✔
53
  isolatedFunction =>
14 ✔
54
    async ({
24 ✔
55
      url,
73 ✔
56
      code,
73 ✔
57
      vmOpts,
73 ✔
58
      browserWSEndpoint,
73 ✔
59
      extendPage,
73 ✔
60
      hostPage,
73 ✔
61
      needsNetwork = template.needsBrowser(code, { ...extendPage, ...hostPage }),
73 ✔
62
      source = template(SLOT, {
73 ✔
63
        usesPage: template.isUsingPage(code),
73 ✔
64
        needsBrowser: needsNetwork,
73 ✔
65
        extendPage,
73 ✔
66
        hostPage
73 ✔
67
      }),
73 ✔
68
      ...opts
73 ✔
69
    }) => {
73 ✔
70
      const permissions = needsNetwork && nodeMajor >= 25 ? ['net'] : []
73 !
71
      const { ssrf: policy, allow: vmOptsAllow = {}, ...vmOptsRest } = vmOpts ?? {}
73 ✔
72
      const granted = new Set(egressOf(browserWSEndpoint))
73 ✔
73
      const ssrf =
73 ✔
74
      typeof policy === 'function'
73 ✔
75
        ? (address, port) => granted.has(pair(address, port)) || policy(address, port) === true
73 ✔
76
        : undefined
73 ✔
77
      // `isolated-function` rejects `slot` unless the sentinel appears once.
73 ✔
78
      // A second copy means an extendPage method mentioned it; inline the user
73 ✔
79
      // code and take a full build so that call still runs.
73 ✔
80
      const copies = source.split(SLOT).length - 1
73 ✔
81
      const program =
73 ✔
82
      copies > 1
73 ✔
83
        ? template(code, {
73 ✔
84
          usesPage: template.isUsingPage(code),
1 ✔
85
          needsBrowser: needsNetwork,
1 ✔
86
          extendPage,
1 ✔
87
          hostPage
1 ✔
88
        })
1 ✔
89
        : source
73 ✔
90
      const host = template.exposedHost(hostPage, extendPage)
73 ✔
91
      const fn = isolatedFunction(program, {
73 ✔
92
        ...vmOptsRest,
73 ✔
93
        ...(ssrf && { ssrf }),
73 ✔
94
        ...(copies === 1 && { slot: code }),
73 ✔
95
        ...(host && { host }),
73 ✔
96
        allow: {
73 ✔
97
          ...vmOptsAllow,
73 ✔
98
          permissions: [...(vmOptsAllow.permissions || []), ...permissions]
73 ✔
99
        },
73 ✔
100
        throwError: false
73 ✔
101
      })
73 ✔
102
      return fn(url, browserWSEndpoint, opts)
73 ✔
103
    }
73 ✔
104

14 ✔
105
module.exports.isUsingPage = template.isUsingPage
14 ✔
106
module.exports.needsBrowser = template.needsBrowser
14 ✔
107
module.exports.buildTemplate = template
14 ✔
108
module.exports.inspect = template.inspect
14 ✔
109
module.exports.PAGE_NOT_FOUND = template.PAGE_NOT_FOUND
14 ✔
110
module.exports.RESOLVE_PAGE = template.RESOLVE_PAGE
14 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc