2022-03-15 19:11:54 +00:00
const { join } = require ( 'path' ) ;
const fs = require ( 'fs' ) ;
const mkdirp = require ( 'mkdirp' ) ;
const Module = require ( 'module' ) ;
2022-04-03 21:27:05 +00:00
const { execFile } = require ( 'child_process' ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
const paths = require ( '../paths' ) ;
2021-12-09 16:25:14 +00:00
const request = require ( './request' ) ;
2022-03-15 19:11:54 +00:00
const events = exports . events = new ( require ( 'events' ) . EventEmitter ) ( ) ;
2022-03-20 12:33:24 +00:00
exports . INSTALLED _MODULE = 'installed-module' ; // Fixes DiscordNative ensureModule as it uses export
2022-03-15 19:11:54 +00:00
2022-04-04 17:41:32 +00:00
let bootstrapping ,
2022-03-15 19:11:54 +00:00
skipHost , skipModule ,
remote = { } ,
installed = { } ,
downloading , installing ,
2022-04-04 17:41:32 +00:00
basePath , manifestPath , downloadPath ,
2022-03-15 19:11:54 +00:00
hostUpdater ,
baseUrl , baseQuery ,
2022-03-23 09:54:57 +00:00
checking , hostAvail , lastUpdate ;
2022-03-15 19:11:54 +00:00
const resetTracking = ( ) => {
const base = {
done : 0 ,
total : 0 ,
fail : 0
} ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
downloading = Object . assign ( { } , base ) ;
installing = Object . assign ( { } , base ) ;
} ;
2021-12-09 16:25:14 +00:00
2022-04-05 12:13:55 +00:00
exports . init = ( endpoint , { releaseChannel , version } ) => {
2022-03-15 19:11:54 +00:00
skipHost = settings . get ( 'SKIP_HOST_UPDATE' ) ;
skipModule = settings . get ( 'SKIP_MODULE_UPDATE' ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
basePath = join ( paths . getUserDataVersioned ( ) , 'modules' ) ;
manifestPath = join ( basePath , 'installed.json' ) ;
downloadPath = join ( basePath , 'pending' ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
resetTracking ( ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
Module . globalPaths . push ( basePath ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
// Purge pending
fs . rmSync ( downloadPath , { recursive : true , force : true } ) ;
mkdirp . sync ( downloadPath ) ;
2021-12-09 16:25:14 +00:00
try {
2022-03-15 19:11:54 +00:00
installed = JSON . parse ( fs . readFileSync ( manifestPath ) ) ;
} catch ( e ) {
bootstrapping = true ;
2021-12-09 16:25:14 +00:00
}
2022-03-19 11:23:59 +00:00
2022-03-15 19:11:54 +00:00
hostUpdater = require ( './hostUpdater' ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
hostUpdater . on ( 'update-available' , ( ) => {
log ( 'Modules' , 'Host available' ) ;
hostAvail = true ;
events . emit ( 'update-check-finished' , {
2021-12-09 16:25:14 +00:00
succeeded : true ,
2022-03-23 09:54:57 +00:00
updateCount : 1
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
events . emit ( 'downloading-module' , {
name : 'host' ,
current : 1 ,
2022-03-23 09:54:57 +00:00
total : 1
2022-03-15 19:11:54 +00:00
} ) ;
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
hostUpdater . on ( 'update-progress' , progress => events . emit ( 'downloading-module-progress' , { name : 'host' , progress } ) ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
hostUpdater . on ( 'update-not-available' , hostPassed ) ;
2021-12-09 16:25:14 +00:00
2022-04-04 14:41:49 +00:00
hostUpdater . on ( 'update-manually' , v => {
2022-03-15 19:11:54 +00:00
log ( 'Modules' , 'Host manual' ) ;
checking = false ;
events . emit ( 'update-manually' , {
newVersion : v
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
events . emit ( 'update-check-finished' , {
succeeded : true ,
2022-03-23 09:54:57 +00:00
updateCount : 1
2022-03-15 19:11:54 +00:00
} ) ;
} ) ;
hostUpdater . on ( 'update-downloaded' , ( ) => {
checking = false ;
events . emit ( 'downloaded-module' , {
2021-12-09 16:25:14 +00:00
name : 'host' ,
current : 1 ,
total : 1 ,
2022-03-15 19:11:54 +00:00
succeeded : true
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
events . emit ( 'downloading-modules-finished' , {
succeeded : 1 ,
failed : 0
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
} ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
hostUpdater . on ( 'error' , ( ) => {
log ( 'Modules' , 'Host error' ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
checking = false ;
2022-04-04 07:28:57 +00:00
setTimeout ( ( ) => hostUpdater . checkForUpdates ( ) , 1000 ) ; // Retry after 1s
2022-03-15 19:11:54 +00:00
} ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 22:00:20 +00:00
const platform = process . platform === 'darwin' ? 'osx' : 'linux' ;
2022-04-04 07:50:32 +00:00
hostUpdater . setFeedURL ( ` ${ endpoint } /updates/ ${ releaseChannel } ?platform= ${ platform } &version= ${ version } ` ) ;
2021-12-09 16:25:14 +00:00
2022-04-04 07:50:32 +00:00
baseUrl = ` ${ endpoint } /modules/ ${ releaseChannel } ` ;
2022-03-15 19:11:54 +00:00
baseQuery = {
2022-04-04 07:50:32 +00:00
host _version : version ,
2022-03-15 22:00:20 +00:00
platform
2022-03-15 19:11:54 +00:00
} ;
} ;
2021-12-09 16:25:14 +00:00
2022-03-23 09:54:57 +00:00
const hostPassed = ( skip = skipModule ) => {
if ( skip ) return events . emit ( 'update-check-finished' , {
2022-03-15 19:11:54 +00:00
succeeded : true ,
2022-03-23 09:54:57 +00:00
updateCount : 0
2022-03-15 19:11:54 +00:00
} ) ;
2022-03-23 09:54:57 +00:00
log ( 'Modules' , 'Host good' ) ;
2022-03-15 19:11:54 +00:00
checkModules ( ) ;
} ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
const checkModules = async ( ) => {
hostAvail = false ;
2021-12-09 16:25:14 +00:00
try {
2022-03-23 10:56:26 +00:00
const { body } = await request . get ( {
url : baseUrl + '/versions.json' ,
qs : {
... baseQuery ,
_ : Math . floor ( Date . now ( ) / 300000 ) // 5 min intervals
2022-03-23 10:58:33 +00:00
}
2022-03-23 10:56:26 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
checking = false ;
2022-03-23 09:54:57 +00:00
remote = JSON . parse ( body ) ;
2022-03-15 19:11:54 +00:00
} catch ( e ) {
log ( 'Modules' , 'Check failed' , e ) ;
return events . emit ( 'update-check-finished' , {
2021-12-09 16:25:14 +00:00
succeeded : false ,
2022-03-23 09:54:57 +00:00
updateCount : 0
2021-12-09 16:25:14 +00:00
} ) ;
}
2022-03-23 09:54:57 +00:00
let doing = 0 ;
2022-03-15 19:11:54 +00:00
for ( const name in installed ) {
const inst = installed [ name ] . installedVersion ;
const rem = remote [ name ] ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
if ( inst !== rem ) {
2022-03-19 11:23:59 +00:00
log ( 'Modules' , 'Update:' , name , '|' , inst , '->' , rem ) ;
2022-03-23 09:54:57 +00:00
doing ++ ;
downloadModule ( name , rem ) ;
2021-12-09 16:25:14 +00:00
}
}
2022-03-15 19:11:54 +00:00
events . emit ( 'update-check-finished' , {
2021-12-09 16:25:14 +00:00
succeeded : true ,
2022-03-23 09:54:57 +00:00
updateCount : doing
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
} ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
const downloadModule = async ( name , ver ) => {
downloading . total ++ ;
events . emit ( 'downloading-module' , {
2021-12-09 16:25:14 +00:00
name ,
2022-03-15 19:11:54 +00:00
current : downloading . total ,
2022-03-23 09:54:57 +00:00
total : downloading . total
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
const url = baseUrl + '/' + name + '/' + ver ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
const path = join ( downloadPath , name + '-' + ver + '.zip' ) ;
const stream = fs . createWriteStream ( path ) ;
2021-12-09 16:25:14 +00:00
2022-04-04 14:45:37 +00:00
stream . on ( 'progress' , ( [ cur , total ] ) => events . emit ( 'downloading-module-progress' , {
name ,
progress : Math . min ( 100 , Math . floor ( 100 * ( cur / total ) ) ) ,
cur ,
total
} ) ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
log ( 'Modules' , 'Downloading' , ` ${ name } @ ${ ver } ` , 'from' , url , 'to' , path ) ;
let success = false ;
2021-12-09 16:25:14 +00:00
try {
2022-03-15 19:11:54 +00:00
const resp = await request . get ( {
2021-12-09 16:25:14 +00:00
url ,
2022-03-15 19:11:54 +00:00
qs : baseQuery ,
2021-12-09 16:25:14 +00:00
stream
} ) ;
2022-03-15 19:11:54 +00:00
success = resp . statusCode === 200 ;
} catch ( e ) {
log ( 'Modules' , 'Fetch errored' , e ) ;
2021-12-09 16:25:14 +00:00
}
2022-03-15 19:11:54 +00:00
if ( ! installed [ name ] ) installed [ name ] = { } ;
if ( success ) commitManifest ( ) ;
else downloading . fail ++ ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
events . emit ( 'downloaded-module' , {
2022-04-03 21:27:05 +00:00
name ,
2022-03-15 19:11:54 +00:00
current : downloading . total ,
2022-03-23 09:54:57 +00:00
total : downloading . total
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
downloading . done ++ ;
if ( downloading . done === downloading . total ) {
2022-03-15 22:27:06 +00:00
const succeeded = downloading . total - downloading . fail ;
2022-03-16 10:40:58 +00:00
log ( 'Modules' , 'Done downloads' , ` | ${ succeeded } / ${ downloading . total } success ` ) ;
2022-03-15 19:11:54 +00:00
events . emit ( 'downloading-modules-finished' , {
2022-03-15 22:27:06 +00:00
succeeded ,
2022-03-15 19:11:54 +00:00
failed : downloading . fail
2021-12-09 16:25:14 +00:00
} ) ;
}
2022-03-15 19:11:54 +00:00
installModule ( name , ver , path ) ;
} ;
2021-12-09 16:25:14 +00:00
2022-04-03 21:27:05 +00:00
const installModule = async ( name , ver , path ) => {
2022-03-15 19:11:54 +00:00
installing . total ++ ;
events . emit ( 'installing-module' , {
2021-12-09 16:25:14 +00:00
name ,
2022-03-15 19:11:54 +00:00
current : installing . total ,
2022-03-23 09:54:57 +00:00
total : installing . total
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
log ( 'Modules' , 'Installing' , ` ${ name } @ ${ ver } ` , 'from' , path ) ;
2021-12-09 16:25:14 +00:00
2022-03-23 11:01:44 +00:00
let hasError ;
2021-12-09 16:25:14 +00:00
2022-04-04 14:41:49 +00:00
const handleErr = e => {
2022-03-15 19:11:54 +00:00
if ( hasError ) return ;
hasError = true ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
log ( 'Modules' , 'Failed install' , ` ${ name } @ ${ ver } ` , e ) ;
2021-12-09 16:25:14 +00:00
2022-03-23 11:01:44 +00:00
finishInstall ( name , ver , false ) ;
2022-03-15 19:11:54 +00:00
} ;
2021-12-09 16:25:14 +00:00
2022-04-03 21:27:05 +00:00
// Extract zip via unzip cmd line - replaces yauzl dep (speed++, size--, jank++)
const ePath = join ( basePath , name ) ;
const total = await new Promise ( ( res ) => {
const p = execFile ( 'unzip' , [ '-l' , path ] ) ;
2022-04-04 14:41:49 +00:00
p . stdout . on ( 'data' , x => {
2022-04-03 21:27:05 +00:00
const m = x . toString ( ) . match ( /([0-9]+) files/ ) ;
if ( m ) res ( parseInt ( m [ 1 ] ) ) ;
} ) ;
p . stderr . on ( 'data' , res ) ; // On error resolve undefined (??'d to 0)
} ) ? ? 0 ;
mkdirp . sync ( ePath ) ;
const proc = execFile ( 'unzip' , [ '-o' , path , '-d' , ePath ] ) ;
proc . on ( 'error' , ( err ) => {
if ( err . code === 'ENOENT' ) {
require ( 'electron' ) . dialog . showErrorBox ( 'Failed Dependency' , 'Please install "unzip"' ) ;
process . exit ( 1 ) ; // Close now
}
handleErr ( err ) ;
} ) ;
proc . stderr . on ( 'data' , handleErr ) ;
2022-04-04 14:19:27 +00:00
let cur = 0 ;
2022-04-04 14:41:49 +00:00
proc . stdout . on ( 'data' , x => x . toString ( ) . split ( '\n' ) . forEach ( y => {
if ( ! y . includes ( 'inflating' ) ) return ;
2022-04-03 21:27:05 +00:00
2022-04-04 14:19:27 +00:00
cur ++ ;
2022-04-03 21:27:05 +00:00
events . emit ( 'installing-module-progress' , {
name ,
2022-04-04 14:45:37 +00:00
progress : Math . min ( 100 , Math . floor ( cur / total * 100 ) ) ,
2022-04-04 14:19:27 +00:00
cur ,
2022-04-03 21:27:05 +00:00
total
} ) ;
} ) ) ;
proc . on ( 'close' , ( ) => {
if ( hasError ) return ;
2022-03-19 12:31:39 +00:00
2022-04-03 21:27:05 +00:00
installed [ name ] . installedVersion = ver ;
commitManifest ( ) ;
2022-03-19 12:31:39 +00:00
2022-04-03 21:27:05 +00:00
finishInstall ( name , ver , true ) ;
} ) ;
2022-03-15 19:11:54 +00:00
} ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
const finishInstall = ( name , ver , success ) => {
if ( ! success ) installing . fail ++ ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
events . emit ( 'installed-module' , {
name ,
current : installing . total ,
total : installing . total ,
succeeded : success
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
installing . done ++ ;
2022-03-16 10:40:58 +00:00
log ( 'Modules' , 'Finished' , ` ${ name } @ ${ ver } ` ) ;
2022-03-15 19:11:54 +00:00
2022-03-19 11:23:59 +00:00
if ( installing . done === ( downloading . total || installing . done ) ) {
2022-03-15 22:27:06 +00:00
const succeeded = installing . total - installing . fail ;
2022-03-16 10:40:58 +00:00
log ( 'Modules' , 'Done installs' , ` | ${ succeeded } / ${ installing . total } success ` ) ;
2022-03-15 19:11:54 +00:00
2022-03-23 09:54:57 +00:00
if ( ! installing . fail ) lastUpdate = Date . now ( ) ;
2022-03-15 19:11:54 +00:00
events . emit ( 'installing-modules-finished' , {
2022-03-15 22:27:06 +00:00
succeeded ,
2022-03-15 19:11:54 +00:00
failed : installing . fail
2021-12-09 16:25:14 +00:00
} ) ;
2022-03-15 19:11:54 +00:00
resetTracking ( ) ;
bootstrapping = false ;
2021-12-09 16:25:14 +00:00
}
2022-03-15 19:11:54 +00:00
} ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
exports . checkForUpdates = ( ) => {
log ( 'Modules' , 'Checking' ) ;
if ( checking ) return ;
checking = true ;
2021-12-09 16:25:14 +00:00
2022-04-04 07:55:48 +00:00
events . emit ( 'checking-for-updates' ) ;
2022-03-23 09:54:57 +00:00
2022-04-04 07:55:48 +00:00
if ( skipHost || lastUpdate > Date . now ( ) - 10000 ) hostPassed ( true ) ;
else hostUpdater . checkForUpdates ( ) ;
2022-03-15 19:11:54 +00:00
} ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
exports . quitAndInstallUpdates = ( ) => {
log ( 'Modules' , 'Relaunching' ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
if ( hostAvail ) hostUpdater . quitAndInstall ( ) ;
else {
const { app } = require ( 'electron' ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
app . relaunch ( ) ;
app . quit ( ) ;
2021-12-09 16:25:14 +00:00
}
2022-03-15 19:11:54 +00:00
} ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
const isInstalled = exports . isInstalled = ( n , v ) => installed [ n ] && ! ( v && installed [ n ] . installedVersion !== v ) ;
exports . getInstalled = ( ) => ( { ... installed } ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
const commitManifest = ( ) => fs . writeFileSync ( manifestPath , JSON . stringify ( installed , null , 2 ) ) ;
2021-12-09 16:25:14 +00:00
2022-03-15 19:11:54 +00:00
exports . install = ( name , def , { version } = { } ) => {
2021-12-09 16:25:14 +00:00
if ( isInstalled ( name , version ) ) {
2022-03-15 19:11:54 +00:00
if ( ! def ) events . emit ( 'installed-module' , {
name ,
current : 1 ,
total : 1 ,
succeeded : true
} ) ;
2021-12-09 16:25:14 +00:00
return ;
}
2022-03-15 19:11:54 +00:00
if ( def ) {
2022-03-18 22:49:04 +00:00
installed [ name ] = { installedVersion : 0 } ;
2022-03-15 19:11:54 +00:00
return commitManifest ( ) ;
2021-12-09 16:25:14 +00:00
}
2022-03-18 22:52:55 +00:00
downloadModule ( name , version ? ? remote [ name ] ? ? 0 ) ;
2022-03-15 19:11:54 +00:00
} ;
exports . installPendingUpdates = ( ) => {
2021-12-09 16:25:14 +00:00
if ( bootstrapping ) {
2022-03-18 22:49:04 +00:00
log ( 'Modules' , 'Bootstrapping...' ) ;
2021-12-09 16:25:14 +00:00
2022-04-04 17:41:32 +00:00
for ( const m in JSON . parse ( fs . readFileSync ( join ( paths . getResources ( ) , 'bootstrap' , 'manifest.json' ) ) ) ) { // Read [resources]/bootstrap/manifest.json, with "moduleName": version (always 0)
installed [ m ] = { installedVersion : 0 } ; // Set initial version as 0
2021-12-09 16:25:14 +00:00
}
2022-04-04 17:41:32 +00:00
return exports . checkForUpdates ( ) ;
2021-12-09 16:25:14 +00:00
}
2022-03-18 22:49:04 +00:00
events . emit ( 'no-pending-updates' ) ;
2022-03-15 19:11:54 +00:00
} ;