1
1
import BluebirdPromise from "bluebird-lst"
2
+ import { configureRequestOptions , DigestTransform , safeGetHeader } from "electron-builder-http"
2
3
import { CancellationToken } from "electron-builder-http/out/CancellationToken"
4
+ import { ProgressCallbackTransform } from "electron-builder-http/out/ProgressCallbackTransform"
3
5
import { PublishConfiguration , VersionInfo } from "electron-builder-http/out/publishOptions"
4
- import { createServer , IncomingMessage , Server , ServerResponse } from "http"
6
+ import { createServer , IncomingMessage , ServerResponse } from "http"
7
+ import { parse as parseUrl } from "url"
5
8
import { AppUpdater } from "./AppUpdater"
6
- import { FileInfo } from "./main"
9
+ import { DOWNLOAD_PROGRESS , FileInfo } from "./main"
7
10
import AutoUpdater = Electron . AutoUpdater
8
11
9
12
export class MacUpdater extends AppUpdater {
10
13
private readonly nativeUpdater : AutoUpdater = require ( "electron" ) . autoUpdater
11
14
12
- private server : Server | null
13
-
14
15
constructor ( options ?: PublishConfiguration ) {
15
16
super ( options )
16
17
@@ -28,43 +29,107 @@ export class MacUpdater extends AppUpdater {
28
29
} )
29
30
}
30
31
31
- protected onUpdateAvailable ( versionInfo : VersionInfo , fileInfo : FileInfo ) {
32
- super . onUpdateAvailable ( versionInfo , fileInfo )
32
+ protected doDownloadUpdate ( versionInfo : VersionInfo , fileInfo : FileInfo , cancellationToken : CancellationToken ) {
33
+ const server = createServer ( )
34
+ server . on ( "close" , ( ) => {
35
+ if ( this . logger != null ) {
36
+ this . logger . info ( `Proxy server for native Squirrel.Mac is closed (was started to download ${ fileInfo . url } )` )
37
+ }
38
+ } )
33
39
34
- let server = this . server
35
- if ( server != null ) {
36
- return
40
+ function getServerUrl ( ) {
41
+ const address = server . address ( )
42
+ return `http:// ${ address . address } : ${ address . port } `
37
43
}
38
44
39
- server = createServer ( )
40
- this . server = server
41
- server . on ( "request" , ( request : IncomingMessage , response : ServerResponse ) => {
42
- if ( request . url ! . endsWith ( "/update.json" ) ) {
43
- response . writeHead ( 200 , { "Content-Type" : "application/json" } )
44
- response . end ( `{ "url": "${ this . getServerUrl ( ) } /app.zip" }` )
45
+ return new BluebirdPromise ( ( resolve , reject ) => {
46
+ server . on ( "request" , ( request : IncomingMessage , response : ServerResponse ) => {
47
+ const requestUrl = request . url !
48
+ if ( requestUrl === "/" ) {
49
+ response . writeHead ( 200 , { "Content-Type" : "application/json" } )
50
+ response . end ( `{ "url": "${ getServerUrl ( ) } /app.zip" }` )
51
+ }
52
+ else if ( requestUrl === "/app.zip" ) {
53
+ let errorOccurred = false
54
+ response . on ( "finish" , ( ) => {
55
+ if ( ! errorOccurred ) {
56
+ resolve ( )
57
+ }
58
+ } )
59
+ this . proxyUpdateFile ( response , fileInfo , error => {
60
+ errorOccurred = true
61
+ try {
62
+ response . writeHead ( 500 )
63
+ response . end ( )
64
+ }
65
+ finally {
66
+ reject ( new Error ( `Cannot download "${ fileInfo . url } ": ${ error } ` ) )
67
+ }
68
+ } )
69
+ }
70
+ else {
71
+ response . writeHead ( 404 )
72
+ response . end ( )
73
+ }
74
+ } )
75
+ server . listen ( 0 , "127.0.0.1" , 16 , ( ) => {
76
+ this . nativeUpdater . setFeedURL ( `${ getServerUrl ( ) } ` , Object . assign ( { "Cache-Control" : "no-cache" } , this . computeRequestHeaders ( fileInfo ) ) )
77
+ this . nativeUpdater . checkForUpdates ( )
78
+ } )
79
+ } )
80
+ }
81
+
82
+ private proxyUpdateFile ( nativeResponse : ServerResponse , fileInfo : FileInfo , errorHandler : ( error : Error ) => void ) {
83
+ nativeResponse . writeHead ( 200 , { "Content-Type" : "application/zip" } )
84
+
85
+ const parsedUrl = parseUrl ( fileInfo . url )
86
+ const downloadRequest = this . httpExecutor . doRequest ( configureRequestOptions ( {
87
+ protocol : parsedUrl . protocol ,
88
+ hostname : parsedUrl . hostname ,
89
+ path : parsedUrl . path ,
90
+ port : parsedUrl . port ? parseInt ( parsedUrl . port , 10 ) : undefined ,
91
+ headers : this . computeRequestHeaders ( fileInfo ) || undefined ,
92
+ } ) , downloadResponse => {
93
+ if ( downloadResponse . statusCode ! >= 400 ) {
94
+ try {
95
+ nativeResponse . writeHead ( 404 )
96
+ nativeResponse . end ( )
97
+ }
98
+ finally {
99
+ this . emit ( "error" , new Error ( `Cannot download "${ fileInfo . url } ", status ${ downloadResponse . statusCode } : ${ downloadResponse . statusMessage } ` ) )
100
+ }
101
+ return
45
102
}
46
- else {
47
- response . writeHead ( 404 )
48
- response . end ( )
49
103
104
+ const streams : Array < any > = [ ]
105
+ if ( this . listenerCount ( DOWNLOAD_PROGRESS ) > 0 ) {
106
+ const contentLength = safeGetHeader ( downloadResponse , "content-length" )
107
+ if ( contentLength != null ) {
108
+ streams . push ( new ProgressCallbackTransform ( parseInt ( contentLength , 10 ) , new CancellationToken ( ) , it => this . emit ( DOWNLOAD_PROGRESS , it ) ) )
109
+ }
110
+ }
111
+
112
+ // for mac only sha512 is produced (sha256 is published for windows only to preserve backward compatibility)
113
+ const sha512 = fileInfo . sha512
114
+ if ( sha512 != null ) {
115
+ // "hex" to easy migrate to new base64 encoded hash (we already produces latest-mac.yml with hex encoded hash)
116
+ streams . push ( new DigestTransform ( sha512 , "sha512" , sha512 . length === 128 && ! sha512 . includes ( "+" ) && ! sha512 . includes ( "Z" ) && ! sha512 . includes ( "=" ) ? "hex" : "base64" ) )
117
+ }
118
+
119
+ streams . push ( nativeResponse )
120
+
121
+ let lastStream = downloadResponse
122
+ for ( const stream of streams ) {
123
+ stream . on ( "error" , errorHandler )
124
+ lastStream = lastStream . pipe ( stream )
50
125
}
51
126
} )
52
- server . listen ( 0 , "127.0.0.1" , 16 , ( ) => {
53
- this . nativeUpdater . setFeedURL ( `${ this . getServerUrl ( ) } ` , Object . assign ( { "Cache-Control" : "no-cache" } , this . computeRequestHeaders ( fileInfo ) ) )
54
- } )
55
- }
56
127
57
- protected doDownloadUpdate ( versionInfo : VersionInfo , fileInfo : FileInfo , cancellationToken : CancellationToken ) {
58
- this . nativeUpdater . checkForUpdates ( )
59
- return BluebirdPromise . resolve ( )
128
+ downloadRequest . on ( "error" , errorHandler )
129
+ downloadRequest . end ( )
60
130
}
61
131
62
132
quitAndInstall ( ) : void {
63
133
this . nativeUpdater . quitAndInstall ( )
64
134
}
65
-
66
- private getServerUrl ( ) {
67
- const address = this . server ! . address ( )
68
- return `http://${ address . address } :${ address . port } `
69
- }
70
135
}
0 commit comments