1'use strict'
2
3const { AsyncResource } = require('async_hooks')
4const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
5const util = require('../core/util')
6const { addSignal, removeSignal } = require('./abort-signal')
7
8class ConnectHandler extends AsyncResource {
9  constructor (opts, callback) {
10    if (!opts || typeof opts !== 'object') {
11      throw new InvalidArgumentError('invalid opts')
12    }
13
14    if (typeof callback !== 'function') {
15      throw new InvalidArgumentError('invalid callback')
16    }
17
18    const { signal, opaque, responseHeaders } = opts
19
20    if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
21      throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
22    }
23
24    super('UNDICI_CONNECT')
25
26    this.opaque = opaque || null
27    this.responseHeaders = responseHeaders || null
28    this.callback = callback
29    this.abort = null
30
31    addSignal(this, signal)
32  }
33
34  onConnect (abort, context) {
35    if (!this.callback) {
36      throw new RequestAbortedError()
37    }
38
39    this.abort = abort
40    this.context = context
41  }
42
43  onHeaders () {
44    throw new SocketError('bad connect', null)
45  }
46
47  onUpgrade (statusCode, rawHeaders, socket) {
48    const { callback, opaque, context } = this
49
50    removeSignal(this)
51
52    this.callback = null
53
54    let headers = rawHeaders
55    // Indicates is an HTTP2Session
56    if (headers != null) {
57      headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
58    }
59
60    this.runInAsyncScope(callback, null, null, {
61      statusCode,
62      headers,
63      socket,
64      opaque,
65      context
66    })
67  }
68
69  onError (err) {
70    const { callback, opaque } = this
71
72    removeSignal(this)
73
74    if (callback) {
75      this.callback = null
76      queueMicrotask(() => {
77        this.runInAsyncScope(callback, null, err, { opaque })
78      })
79    }
80  }
81}
82
83function connect (opts, callback) {
84  if (callback === undefined) {
85    return new Promise((resolve, reject) => {
86      connect.call(this, opts, (err, data) => {
87        return err ? reject(err) : resolve(data)
88      })
89    })
90  }
91
92  try {
93    const connectHandler = new ConnectHandler(opts, callback)
94    this.dispatch({ ...opts, method: 'CONNECT' }, connectHandler)
95  } catch (err) {
96    if (typeof callback !== 'function') {
97      throw err
98    }
99    const opaque = opts && opts.opaque
100    queueMicrotask(() => callback(err, { opaque }))
101  }
102}
103
104module.exports = connect
105