Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions cli/release-staging/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
const http = require('http')
const https = require('https')
const tls = require('tls')

function createReleaseHttpClient({
env = process.env,
userAgent,
requestTimeout,
httpModule = http,
httpsModule = https,
tlsModule = tls,
}) {
function getProxyUrl() {
return (
env.HTTPS_PROXY ||
env.https_proxy ||
env.HTTP_PROXY ||
env.http_proxy ||
null
)
}

function shouldBypassProxy(hostname) {
const noProxy = env.NO_PROXY || env.no_proxy || ''
if (!noProxy) return false

const domains = noProxy
.split(',')
.map((domain) => domain.trim().toLowerCase().replace(/:\d+$/, ''))
const host = hostname.toLowerCase()

return domains.some((domain) => {
if (domain === '*') return true
if (domain.startsWith('.')) {
return host.endsWith(domain) || host === domain.slice(1)
}
return host === domain || host.endsWith(`.${domain}`)
})
}

function connectThroughProxy(proxyUrl, targetHost, targetPort) {
return new Promise((resolve, reject) => {
const proxy = new URL(proxyUrl)
const isHttpsProxy = proxy.protocol === 'https:'
const connectOptions = {
hostname: proxy.hostname,
port: proxy.port || (isHttpsProxy ? 443 : 80),
method: 'CONNECT',
path: `${targetHost}:${targetPort}`,
headers: {
Host: `${targetHost}:${targetPort}`,
},
}

if (proxy.username || proxy.password) {
const auth = Buffer.from(
`${decodeURIComponent(proxy.username || '')}:${decodeURIComponent(
proxy.password || '',
)}`,
).toString('base64')
connectOptions.headers['Proxy-Authorization'] = `Basic ${auth}`
}

const transport = isHttpsProxy ? httpsModule : httpModule
const req = transport.request(connectOptions)

req.on('connect', (res, socket) => {
if (res.statusCode === 200) {
resolve(socket)
return
}

socket.destroy()
reject(new Error(`Proxy CONNECT failed with status ${res.statusCode}`))
})

req.on('error', (error) => {
reject(new Error(`Proxy connection failed: ${error.message}`))
})

req.setTimeout(requestTimeout, () => {
req.destroy()
reject(new Error('Proxy connection timeout.'))
})

req.end()
})
}

async function buildRequestOptions(url, options = {}) {
const parsedUrl = new URL(url)
const reqOptions = {
hostname: parsedUrl.hostname,
port: parsedUrl.port || 443,
path: parsedUrl.pathname + parsedUrl.search,
headers: {
'User-Agent': userAgent,
...options.headers,
},
}

const proxyUrl = getProxyUrl()
if (!proxyUrl || shouldBypassProxy(parsedUrl.hostname)) {
return reqOptions
}

const tunnelSocket = await connectThroughProxy(
proxyUrl,
parsedUrl.hostname,
parsedUrl.port || 443,
)

class TunnelAgent extends httpsModule.Agent {
createConnection(_options, callback) {
const secureSocket = tlsModule.connect({
socket: tunnelSocket,
servername: parsedUrl.hostname,
})

if (typeof callback === 'function') {
if (typeof secureSocket.once === 'function') {
let settled = false
const finish = (error) => {
if (settled) return
settled = true
callback(error || null, error ? undefined : secureSocket)
}

secureSocket.once('secureConnect', () => finish(null))
secureSocket.once('error', (error) => finish(error))
} else {
callback(null, secureSocket)
}
}

return secureSocket
}
}

reqOptions.agent = new TunnelAgent({ keepAlive: false })
return reqOptions
}

async function httpGet(url, options = {}) {
const reqOptions = await buildRequestOptions(url, options)

return new Promise((resolve, reject) => {
const req = httpsModule.get(reqOptions, (res) => {
if (res.statusCode === 301 || res.statusCode === 302) {
res.resume()
httpGet(new URL(res.headers.location, url).href, options)
.then(resolve)
.catch(reject)
return
}

resolve(res)
})

req.on('error', reject)
req.setTimeout(options.timeout || requestTimeout, () => {
req.destroy()
reject(new Error('Request timeout.'))
})
})
}

return {
getProxyUrl,
httpGet,
}
}

module.exports = {
createReleaseHttpClient,
}
125 changes: 6 additions & 119 deletions cli/release-staging/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ const http = require('http')
const https = require('https')
const os = require('os')
const path = require('path')
const tls = require('tls')
const zlib = require('zlib')

const tar = require('tar')
const { createReleaseHttpClient } = require('./http')

const packageName = 'codecane'

Expand Down Expand Up @@ -66,6 +66,11 @@ function createConfig(packageName) {
}

const CONFIG = createConfig(packageName)
const { getProxyUrl, httpGet } = createReleaseHttpClient({
env: process.env,
userAgent: CONFIG.userAgent,
requestTimeout: CONFIG.requestTimeout,
})

function getPostHogConfig() {
const apiKey =
Expand Down Expand Up @@ -131,76 +136,6 @@ function trackUpdateFailed(errorMessage, version, context = {}) {
}
}

function getProxyUrl() {
return (
process.env.HTTPS_PROXY ||
process.env.https_proxy ||
process.env.HTTP_PROXY ||
process.env.http_proxy ||
null
)
}

function shouldBypassProxy(hostname) {
const noProxy = process.env.NO_PROXY || process.env.no_proxy || ''
if (!noProxy) return false
const domains = noProxy.split(',').map((d) => d.trim().toLowerCase().replace(/:\d+$/, ''))
const host = hostname.toLowerCase()
return domains.some((d) => {
if (d === '*') return true
if (d.startsWith('.')) return host.endsWith(d) || host === d.slice(1)
return host === d || host.endsWith('.' + d)
})
}

function connectThroughProxy(proxyUrl, targetHost, targetPort) {
return new Promise((resolve, reject) => {
const proxy = new URL(proxyUrl)
const isHttpsProxy = proxy.protocol === 'https:'
const connectOptions = {
hostname: proxy.hostname,
port: proxy.port || (isHttpsProxy ? 443 : 80),
method: 'CONNECT',
path: `${targetHost}:${targetPort}`,
headers: {
Host: `${targetHost}:${targetPort}`,
},
}

if (proxy.username || proxy.password) {
const auth = Buffer.from(
`${decodeURIComponent(proxy.username || '')}:${decodeURIComponent(proxy.password || '')}`,
).toString('base64')
connectOptions.headers['Proxy-Authorization'] = `Basic ${auth}`
}

const transport = isHttpsProxy ? https : http
const req = transport.request(connectOptions)

req.on('connect', (res, socket) => {
if (res.statusCode === 200) {
resolve(socket)
} else {
socket.destroy()
reject(
new Error(`Proxy CONNECT failed with status ${res.statusCode}`),
)
}
})

req.on('error', (err) => {
reject(new Error(`Proxy connection failed: ${err.message}`))
})

req.setTimeout(CONFIG.requestTimeout, () => {
req.destroy()
reject(new Error('Proxy connection timeout.'))
})

req.end()
})
}

const PLATFORM_TARGETS = {
'linux-x64': `${packageName}-linux-x64.tar.gz`,
'linux-arm64': `${packageName}-linux-arm64.tar.gz`,
Expand All @@ -225,54 +160,6 @@ const term = {
},
}

async function httpGet(url, options = {}) {
const parsedUrl = new URL(url)
const proxyUrl = getProxyUrl()

const reqOptions = {
hostname: parsedUrl.hostname,
path: parsedUrl.pathname + parsedUrl.search,
headers: {
'User-Agent': CONFIG.userAgent,
...options.headers,
},
}

if (proxyUrl && !shouldBypassProxy(parsedUrl.hostname)) {
const tunnelSocket = await connectThroughProxy(
proxyUrl,
parsedUrl.hostname,
parsedUrl.port || 443,
)
reqOptions.agent = false
reqOptions.createConnection = () =>
tls.connect({
socket: tunnelSocket,
servername: parsedUrl.hostname,
})
}

return new Promise((resolve, reject) => {
const req = https.get(reqOptions, (res) => {
if (res.statusCode === 302 || res.statusCode === 301) {
res.resume()
return httpGet(new URL(res.headers.location, url).href, options)
.then(resolve)
.catch(reject)
}
resolve(res)
})

req.on('error', reject)

const timeout = options.timeout || CONFIG.requestTimeout
req.setTimeout(timeout, () => {
req.destroy()
reject(new Error('Request timeout.'))
})
})
}

async function getLatestVersion() {
try {
const res = await httpGet(
Expand Down
1 change: 1 addition & 0 deletions cli/release-staging/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"files": [
"index.js",
"http.js",
"postinstall.js",
"README.md"
],
Expand Down
Loading