@@ -2,12 +2,15 @@ import { dialog, shell } from 'electron'
2
2
import { autoUpdater } from 'electron-updater'
3
3
import { t } from 'i18next'
4
4
5
- import { configStore , icon , isLinux } from './constants'
5
+ import { configStore , icon , isLinux , isMac } from './constants'
6
6
import { logError , logInfo , LogPrefix } from './logger/logger'
7
7
import { captureException } from '@sentry/electron'
8
8
import { getFileSize } from './utils'
9
9
import { ClientUpdateStatuses } from '@hyperplay/utils'
10
10
import { trackEvent } from './metrics/metrics'
11
+ import { homedir } from 'os'
12
+ import { join } from 'path'
13
+ import { rm } from 'fs/promises'
11
14
// to test auto update on windows locally make sure you added the option "verifyUpdateCodeSignature": false
12
15
// under build.win in package.json and also change the app version to an old one there
13
16
@@ -21,18 +24,32 @@ autoUpdater.autoInstallOnAppQuit = true
21
24
let isAppUpdating = false
22
25
let hasUpdated = false
23
26
let updateAttempts = 0
24
- const MAX_UPDATE_ATTEMPTS = 3
27
+ const MAX_UPDATE_ATTEMPTS = 5
25
28
26
29
// check for updates every hour
27
- const checkUpdateInterval = 1 * 60 * 60 * 1000
28
- setInterval ( ( ) => {
29
- if ( shouldCheckForUpdates ) {
30
- autoUpdater . checkForUpdates ( )
30
+ const checkUpdateInterval = 1 * 1000 * 60 * 60
31
+ setInterval ( async ( ) => {
32
+ if ( shouldCheckForUpdates && ! hasUpdated && ! isAppUpdating ) {
33
+ logInfo ( 'Checking for client updates...' , LogPrefix . AutoUpdater )
34
+ await autoUpdater . checkForUpdates ( )
31
35
}
32
36
} , checkUpdateInterval )
33
37
34
38
autoUpdater . on ( 'update-available' , async ( info ) => {
39
+ if ( isAppUpdating && hasUpdated ) {
40
+ logInfo (
41
+ 'New update available, but user has already updated the app' ,
42
+ LogPrefix . AutoUpdater
43
+ )
44
+ return
45
+ }
46
+
35
47
if ( ! shouldCheckForUpdates ) {
48
+ logInfo (
49
+ 'New update available, but user has disabled auto updates' ,
50
+ LogPrefix . AutoUpdater
51
+ )
52
+
36
53
return
37
54
}
38
55
newVersion = info . version
@@ -70,6 +87,7 @@ autoUpdater.on('download-progress', (progress) => {
70
87
71
88
autoUpdater . on ( 'update-downloaded' , async ( ) => {
72
89
logInfo ( 'App update is downloaded' )
90
+ hasUpdated = true
73
91
74
92
trackEvent ( {
75
93
event : 'Client Update Downloaded' ,
@@ -92,12 +110,22 @@ autoUpdater.on('update-downloaded', async () => {
92
110
if ( response === 1 ) {
93
111
return autoUpdater . quitAndInstall ( )
94
112
}
95
- hasUpdated = true
113
+ logInfo ( 'User chose not to update the app for now.' , LogPrefix . AutoUpdater )
96
114
} )
97
115
98
116
autoUpdater . on ( 'error' , async ( error ) => {
99
117
isAppUpdating = false
100
- logError ( `Error updating HyperPlay: ${ error . message } ` , LogPrefix . AutoUpdater )
118
+
119
+ // To avoid false positives, we should not show the error dialog if the app has already updated successfully
120
+ if ( hasUpdated ) {
121
+ return
122
+ }
123
+
124
+ const errorMessage = getErrorMessage ( error . message )
125
+ logError ( `Error updating HyperPlay: ${ errorMessage } ` , LogPrefix . AutoUpdater )
126
+
127
+ // will remove cached updates when it fails to avoid corrupted updates
128
+ await removeCachedUpdatesFolder ( )
101
129
102
130
updateAttempts ++
103
131
@@ -133,12 +161,11 @@ autoUpdater.on('error', async (error) => {
133
161
updateAttempts = 0
134
162
135
163
const { response } = await dialog . showMessageBox ( {
136
- title : t ( 'box.error.update.title ' , 'Error Updating' ) ,
164
+ title : t ( 'box.error.update.message ' , 'Error Updating' ) ,
137
165
message : t (
138
- 'box.error.update.message' ,
139
- `Something went wrong with the update after multiple attempts! Please manually uninstall and reinstall HyperPlay. error: ${ JSON . stringify (
140
- error
141
- ) } `
166
+ 'box.error.update.body' ,
167
+ `Something went wrong with the update after multiple attempts! Please check the error message below or reinstall HyperPlay. error: {{error}}` ,
168
+ { error : errorMessage }
142
169
) ,
143
170
type : 'error' ,
144
171
buttons : [ t ( 'button.cancel' , 'Cancel' ) , t ( 'button.download' , 'Download' ) ]
@@ -155,3 +182,119 @@ export function isClientUpdating(): ClientUpdateStatuses {
155
182
}
156
183
return isAppUpdating ? 'updating' : 'idle'
157
184
}
185
+
186
+ async function removeCachedUpdatesFolder ( ) {
187
+ // remove hyperplay-updates folder from cache directory
188
+ // on macOS: /Users/<username>/Library/Caches/hyperplay-updates
189
+ // on Windows: C:\Users\<username>\AppData\Local\hyperplay-updates
190
+ const macOSPath = join ( homedir ( ) , 'Library' , 'Caches' , 'hyperplay-updates' )
191
+ const windowsPath = join ( homedir ( ) , 'AppData' , 'Local' , 'hyperplay-updates' )
192
+
193
+ try {
194
+ await rm ( isMac ? macOSPath : windowsPath , { recursive : true } )
195
+ } catch ( error ) {
196
+ logError (
197
+ `Error removing cached updates folder: ${ error } ` ,
198
+ LogPrefix . AutoUpdater
199
+ )
200
+ }
201
+ }
202
+
203
+ const commonDownloadErrors : Record < string , ( ) => string > = {
204
+ ERR_NETWORK_CHANGED : ( ) =>
205
+ t (
206
+ 'box.error.update.networkChanged' ,
207
+ 'Network changed. Please check your internet connection.'
208
+ ) ,
209
+ ERR_INTERNET_DISCONNECTED : ( ) =>
210
+ t (
211
+ 'box.error.update.internetDisconnected' ,
212
+ 'Internet disconnected. Please check your internet connection.'
213
+ ) ,
214
+ ERR_CONNECTION_RESET : ( ) =>
215
+ t (
216
+ 'box.error.update.connectionReset' ,
217
+ 'Connection reset. Please check your internet connection.'
218
+ ) ,
219
+ ERR_CONNECTION_CLOSED : ( ) =>
220
+ t (
221
+ 'box.error.update.connectionClosed' ,
222
+ 'Connection closed. Please check your internet connection.'
223
+ ) ,
224
+ ERR_CONNECTION_TIMED_OUT : ( ) =>
225
+ t (
226
+ 'box.error.update.connectionTimedOut' ,
227
+ 'Connection timed out. Please check your internet connection.'
228
+ ) ,
229
+ ERR_NAME_NOT_RESOLVED : ( ) =>
230
+ t (
231
+ 'box.error.update.nameNotResolved' ,
232
+ 'Name not resolved. Please check your internet connection.'
233
+ ) ,
234
+ ERR_CONNECTION_REFUSED : ( ) =>
235
+ t (
236
+ 'box.error.update.connectionRefused' ,
237
+ 'Connection refused. Please check your internet connection.'
238
+ ) ,
239
+ ERR_SSL_PROTOCOL_ERROR : ( ) =>
240
+ t (
241
+ 'box.error.update.sslProtocolError' ,
242
+ 'SSL protocol error. Please check your system time and date or open a ticket with HyperPlay support.'
243
+ ) ,
244
+ ERR_CERT_AUTHORITY_INVALID : ( ) =>
245
+ t (
246
+ 'box.error.update.certAuthorityInvalid' ,
247
+ 'Certificate authority invalid. Please check your system time and date or open a ticket with HyperPlay support.'
248
+ ) ,
249
+ ERR_NETWORK_ACCESS_DENIED : ( ) =>
250
+ t (
251
+ 'box.error.update.networkAccessDenied' ,
252
+ 'Network access denied. Please check your internet connection.'
253
+ ) ,
254
+ ERR_PROXY_CONNECTION_FAILED : ( ) =>
255
+ t (
256
+ 'box.error.update.proxyConnectionFailed' ,
257
+ 'Proxy connection failed. Please check your proxy settings.'
258
+ ) ,
259
+ ERR_CONNECTION_ABORTED : ( ) =>
260
+ t (
261
+ 'box.error.update.connectionAborted' ,
262
+ 'Connection aborted. Please check your internet connection.'
263
+ ) ,
264
+ ERR_ADDRESS_UNREACHABLE : ( ) =>
265
+ t (
266
+ 'box.error.update.addressUnreachable' ,
267
+ 'Address unreachable. Please check your internet connection.'
268
+ ) ,
269
+ ERR_CERT_DATE_INVALID : ( ) =>
270
+ t (
271
+ 'box.error.update.certDateInvalid' ,
272
+ 'Certificate date invalid. Please check your system time and date or open a ticket with HyperPlay support.'
273
+ ) ,
274
+ ERR_HTTP2_SERVER_REFUSED_STREAM : ( ) =>
275
+ t (
276
+ 'box.error.update.http2ServerRefusedStream' ,
277
+ 'HTTP2 server refused stream. Please check your internet connection.'
278
+ ) ,
279
+ ERR_EMPTY_RESPONSE : ( ) =>
280
+ t (
281
+ 'box.error.update.emptyResponse' ,
282
+ 'Empty response. Please check your internet connection.'
283
+ ) ,
284
+ ERR_FAILED : ( ) =>
285
+ t (
286
+ 'box.error.update.failed' ,
287
+ 'Download Failed. Please check your internet connection.'
288
+ )
289
+ }
290
+
291
+ function getErrorMessage ( error : string ) : string {
292
+ const trimmedError = error . replace ( 'net::' , '' ) . trim ( )
293
+ if (
294
+ Object . prototype . hasOwnProperty . call ( commonDownloadErrors , trimmedError )
295
+ ) {
296
+ return commonDownloadErrors [ trimmedError ] ( )
297
+ } else {
298
+ return error
299
+ }
300
+ }
0 commit comments