@@ -6,6 +6,7 @@ import { homedir, platform, tmpdir } from 'node:os'
66import { join } from 'node:path'
77import { pipeline } from 'node:stream/promises'
88import { Readable } from 'node:stream'
9+ import { ProxyAgent , fetch as undiciFetch } from 'undici'
910
1011import {
1112 buildPersistentCodeburnLookupPath ,
@@ -31,6 +32,34 @@ export type InstallResult = { installedPath: string; launched: boolean }
3132export type ReleaseAsset = { name : string ; browser_download_url : string }
3233export type ReleaseResponse = { tag_name : string ; assets : ReleaseAsset [ ] }
3334export type ResolvedAssets = { release : ReleaseResponse ; zip : ReleaseAsset ; checksum : ReleaseAsset }
35+ type ProxyEnv = Partial < Record < 'HTTPS_PROXY' | 'https_proxy' | 'HTTP_PROXY' | 'http_proxy' | 'NO_PROXY' | 'no_proxy' , string > >
36+ type FetchOptions = Parameters < typeof undiciFetch > [ 1 ]
37+
38+ export function resolveProxyUrlForUrl ( url : string , env : ProxyEnv = process . env ) : string | undefined {
39+ const target = new URL ( url )
40+ if ( matchesNoProxy ( target . hostname , env . NO_PROXY ?? env . no_proxy ) ) return undefined
41+ if ( target . protocol === 'https:' ) return env . HTTPS_PROXY ?? env . https_proxy ?? env . HTTP_PROXY ?? env . http_proxy
42+ if ( target . protocol === 'http:' ) return env . HTTP_PROXY ?? env . http_proxy
43+ return undefined
44+ }
45+
46+ function matchesNoProxy ( hostname : string , noProxy ?: string ) : boolean {
47+ if ( ! noProxy ) return false
48+ const host = hostname . toLowerCase ( )
49+ return noProxy . split ( ',' ) . some ( entry => {
50+ const rule = entry . trim ( ) . toLowerCase ( ) . split ( ':' ) [ 0 ]
51+ if ( ! rule ) return false
52+ if ( rule === '*' ) return true
53+ if ( rule . startsWith ( '.' ) ) return host === rule . slice ( 1 ) || host . endsWith ( rule )
54+ return host === rule || host . endsWith ( `.${ rule } ` )
55+ } )
56+ }
57+
58+ function fetchWithProxy ( url : string , options : FetchOptions = { } ) {
59+ const proxyUrl = resolveProxyUrlForUrl ( url )
60+ const dispatcher = proxyUrl ? new ProxyAgent ( proxyUrl ) : undefined
61+ return undiciFetch ( url , dispatcher ? { ...options , dispatcher } : options )
62+ }
3463
3564export function resolveMenubarReleaseAssets ( release : ReleaseResponse ) : ResolvedAssets {
3665 const zip = release . assets . find ( a => VERSIONED_ASSET_PATTERN . test ( a . name ) )
@@ -102,7 +131,7 @@ async function sysProductVersion(): Promise<string> {
102131}
103132
104133async function fetchLatestReleaseAssets ( ) : Promise < ResolvedAssets > {
105- const response = await fetch ( RELEASE_API , {
134+ const response = await fetchWithProxy ( RELEASE_API , {
106135 headers : {
107136 'User-Agent' : 'codeburn-menubar-installer' ,
108137 Accept : 'application/vnd.github+json' ,
@@ -116,7 +145,7 @@ async function fetchLatestReleaseAssets(): Promise<ResolvedAssets> {
116145}
117146
118147async function verifyChecksum ( archivePath : string , checksumUrl : string ) : Promise < void > {
119- const response = await fetch ( checksumUrl , {
148+ const response = await fetchWithProxy ( checksumUrl , {
120149 headers : { 'User-Agent' : 'codeburn-menubar-installer' } ,
121150 redirect : 'follow' ,
122151 } )
@@ -138,7 +167,7 @@ async function verifyChecksum(archivePath: string, checksumUrl: string): Promise
138167}
139168
140169async function downloadToFile ( url : string , destPath : string ) : Promise < void > {
141- const response = await fetch ( url , {
170+ const response = await fetchWithProxy ( url , {
142171 headers : { 'User-Agent' : 'codeburn-menubar-installer' } ,
143172 redirect : 'follow' ,
144173 } )
0 commit comments