1+ import express , { Express , Request , Response , NextFunction } from 'express'
2+ import cors from "cors" ;
3+ import { resolve } from "path" ;
4+ import { unlink , createWriteStream } from "fs" ;
5+ const progress = require ( "request-progress" ) ;
6+ const path = require ( "path" ) ;
7+ const request = require ( "request" ) ;
8+
9+ interface ProgressState {
10+ percent : number ;
11+ speed : number ;
12+ size : {
13+ total : number ;
14+ transferred : number ;
15+ } ;
16+ time : {
17+ elapsed : number ;
18+ remaining : number ;
19+ } ;
20+ success ?: boolean | undefined ;
21+ fileName : string ;
22+ }
23+
24+ const options : cors . CorsOptions = { origin : "*" } ;
25+ const requiredModules : Record < string , any > = { } ;
26+ const port = process . env . PORT || 4000 ;
27+ const dataDir = __dirname ;
28+ type DownloadProgress = Record < string , ProgressState > ;
29+ const downloadProgress : DownloadProgress = { } ;
30+ const app : Express = express ( )
31+ app . use ( express . static ( dataDir + '/renderer' ) )
32+ app . use ( cors ( options ) )
33+ app . use ( express . json ( ) ) ;
34+
35+ /**
36+ * Execute a plugin module function via API call
37+ *
38+ * @param modulePath path to module name to import
39+ * @param method function name to execute. The methods "deleteFile" and "downloadFile" will call the server function {@link deleteFile}, {@link downloadFile} instead of the plugin function.
40+ * @param args arguments to pass to the function
41+ * @returns Promise<any>
42+ *
43+ */
44+ app . post ( '/api/v1/invokeFunction' , ( req : Request , res : Response , next : NextFunction ) : void => {
45+ const method = req . body [ "method" ] ;
46+ const args = req . body [ "args" ] ;
47+ switch ( method ) {
48+ case "deleteFile" :
49+ deleteFile ( args ) . then ( ( ) => res . json ( Object ( ) ) ) . catch ( ( err : any ) => next ( err ) ) ;
50+ break ;
51+ case "downloadFile" :
52+ downloadFile ( args . downloadUrl , args . fileName ) . then ( ( ) => res . json ( Object ( ) ) ) . catch ( ( err : any ) => next ( err ) ) ;
53+ break ;
54+ default :
55+ const result = invokeFunction ( req . body [ "modulePath" ] , method , args )
56+ if ( typeof result === "undefined" ) {
57+ res . json ( Object ( ) )
58+ } else {
59+ result ?. then ( ( result : any ) => {
60+ res . json ( result )
61+ } ) . catch ( ( err : any ) => next ( err ) ) ;
62+ }
63+ }
64+ } ) ;
65+
66+ app . post ( '/api/v1/downloadProgress' , ( req : Request , res : Response ) : void => {
67+ const fileName = req . body [ "fileName" ] ;
68+ if ( fileName && downloadProgress [ fileName ] ) {
69+ res . json ( downloadProgress [ fileName ] )
70+ return ;
71+ } else {
72+ const obj = downloadingFile ( ) ;
73+ if ( obj ) {
74+ res . json ( obj )
75+ return ;
76+ }
77+ }
78+ res . json ( Object ( ) ) ;
79+ } ) ;
80+
81+ app . use ( ( err : Error , req : Request , res : Response , next : NextFunction ) : void => {
82+ res . status ( 500 ) ;
83+ res . json ( { error : err ?. message ?? "Internal Server Error" } )
84+ } ) ;
85+
86+ app . listen ( port , ( ) => console . log ( `Application is running on port ${ port } ` ) ) ;
87+
88+
89+ async function invokeFunction ( modulePath : string , method : string , args : any ) : Promise < any > {
90+ console . log ( modulePath , method , args ) ;
91+ const module = require ( /* webpackIgnore: true */ path . join (
92+ dataDir ,
93+ "" ,
94+ modulePath
95+ ) ) ;
96+ requiredModules [ modulePath ] = module ;
97+ if ( typeof module [ method ] === "function" ) {
98+ return module [ method ] ( ...args ) ;
99+ } else {
100+ return Promise . resolve ( ) ;
101+ }
102+ }
103+
104+ function downloadModel ( downloadUrl : string , fileName : string ) : void {
105+ const userDataPath = process . env . APPDATA || ( process . platform == 'darwin' ? process . env . HOME + '/Library/Preferences' : process . env . HOME + "/.local/share" )
106+
107+ const destination = resolve ( userDataPath , fileName ) ;
108+ console . log ( "Download file" , fileName , "to" , destination ) ;
109+ progress ( request ( downloadUrl ) , { } )
110+ . on ( "progress" , function ( state : any ) {
111+ downloadProgress [ fileName ] = {
112+ ...state ,
113+ fileName,
114+ success : undefined
115+ } ;
116+ console . log ( "downloading file" , fileName , ( state . percent * 100 ) . toFixed ( 2 ) + '%' ) ;
117+ } )
118+ . on ( "error" , function ( err : Error ) {
119+ downloadProgress [ fileName ] = {
120+ ...downloadProgress [ fileName ] ,
121+ success : false ,
122+ fileName : fileName ,
123+ } ;
124+ } )
125+ . on ( "end" , function ( ) {
126+ downloadProgress [ fileName ] = {
127+ ...downloadProgress [ fileName ] ,
128+ success : true ,
129+ fileName : fileName ,
130+ } ;
131+ } )
132+ . pipe ( createWriteStream ( destination ) ) ;
133+ }
134+
135+ function deleteFile ( filePath : string ) : Promise < void > {
136+ const userDataPath = process . env . APPDATA || ( process . platform == 'darwin' ? process . env . HOME + '/Library/Preferences' : process . env . HOME + "/.local/share" )
137+ const fullPath = resolve ( userDataPath , filePath ) ;
138+ return new Promise ( ( resolve , reject ) => {
139+ unlink ( fullPath , function ( err ) {
140+ if ( err && err . code === "ENOENT" ) {
141+ reject ( Error ( `File does not exist: ${ err } ` ) ) ;
142+ } else if ( err ) {
143+ reject ( Error ( `File delete error: ${ err } ` ) ) ;
144+ } else {
145+ console . log ( `Delete file ${ filePath } from ${ fullPath } ` )
146+ resolve ( ) ;
147+ }
148+ } ) ;
149+ } )
150+ }
151+
152+ function downloadingFile ( ) : ProgressState | undefined {
153+ const obj = Object . values ( downloadProgress ) . find ( obj => obj && typeof obj . success === "undefined" )
154+ return obj
155+ }
156+
157+
158+ async function downloadFile ( downloadUrl : string , fileName : string ) : Promise < void > {
159+ return new Promise ( ( resolve , reject ) => {
160+ const obj = downloadingFile ( ) ;
161+ if ( obj ) {
162+ reject ( Error ( obj . fileName + " is being downloaded!" ) )
163+ return ;
164+ } ;
165+ ( async ( ) => {
166+ downloadModel ( downloadUrl , fileName ) ;
167+ } ) ( ) . catch ( e => {
168+ console . error ( "downloadModel" , fileName , e ) ;
169+ } ) ;
170+ resolve ( ) ;
171+ } ) ;
172+ }
0 commit comments