@@ -4,17 +4,30 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
44// Import parts of electron to use
55// app - Control your application's event lifecycle
66// ipcMain - Communicate asynchronously from the main process to renderer processes
7- const { app, BrowserWindow, TouchBar, ipcMain } = require ( 'electron' ) ;
7+ const { app, BrowserWindow, TouchBar, ipcMain, dialog } = require ( 'electron' ) ;
88const path = require ( 'path' ) ;
99const url = require ( 'url' ) ;
10- // This allows electron to spin up this server to localhost:7000 when the app starts up
11- require ( './httpserver' ) ;
10+ const fs = require ( 'fs' ) ;
11+
1212// Import Auto-Updater- Swell will update itself
1313const { autoUpdater } = require ( 'electron-updater' ) ;
1414const log = require ( 'electron-log' ) ;
1515// TouchBarButtons are our nav buttons(ex: Select All, Deselect All, Open Selected, Close Selected, Clear All)
1616const { TouchBarButton, TouchBarSpacer } = TouchBar ;
1717
18+
19+ // basic http cookie parser
20+ const cookie = require ( 'cookie' ) ;
21+ // node-fetch for the fetch request
22+ const fetch2 = require ( 'node-fetch' ) ;
23+
24+ // GraphQL imports
25+ const ApolloClient = require ( 'apollo-client' ) . ApolloClient ;
26+ const gql = require ( 'graphql-tag' ) ;
27+ const { InMemoryCache } = require ( 'apollo-cache-inmemory' ) ;
28+ const { createHttpLink } = require ( 'apollo-link-http' ) ;
29+ const { ApolloLink } = require ( 'apollo-link' ) ;
30+
1831// configure logging
1932autoUpdater . logger = log ;
2033autoUpdater . logger . transports . file . level = 'info' ;
@@ -126,7 +139,7 @@ function createWindow() {
126139 webPreferences : {
127140 "nodeIntegration" : true ,
128141 "sandbox" : false ,
129- webSecurity : false ,
142+ webSecurity : true ,
130143 } ,
131144 icon : `${ __dirname } /src/assets/icons/64x64.png`
132145 } )
@@ -263,3 +276,213 @@ app.on('activate', () => {
263276 createWindow ( ) ;
264277 }
265278} ) ;
279+
280+ // export collection ipc
281+ ipcMain . on ( 'export-collection' , ( event , args ) => {
282+ let content = JSON . stringify ( args . collection ) ;
283+
284+ dialog . showSaveDialog ( ( fileName ) => {
285+ if ( fileName === undefined ) {
286+ console . log ( "You didn't save the file" ) ;
287+ return ;
288+ }
289+
290+ // fileName is a string that contains the path and filename created in the save file dialog.
291+ fs . writeFile ( fileName , content , ( err ) => {
292+ if ( err ) {
293+ console . log ( "An error ocurred creating the file " + err . message )
294+ }
295+ } ) ;
296+ } ) ;
297+ } )
298+
299+ ipcMain . on ( 'import-collection' , ( event , args ) => {
300+ dialog . showOpenDialog ( ( fileNames ) => {
301+ // reusable error message options object
302+ const options = {
303+ type : 'error' ,
304+ buttons : [ 'Okay' ] ,
305+ defaultId : 2 ,
306+ title : 'Error' ,
307+ message : '' ,
308+ detail : '' ,
309+ } ;
310+
311+ // fileNames is an array that contains all the selected
312+ if ( fileNames === undefined ) {
313+ console . log ( "No file selected" ) ;
314+ return ;
315+ }
316+
317+ // get first file path - not dynamic for multiple files
318+ let filepath = fileNames [ 0 ] ;
319+
320+ // get file extension
321+ const ext = path . extname ( filepath ) ;
322+
323+ // make sure if there is an extension that it is .txt
324+ if ( ext && ext !== '.txt' ) {
325+ options . message = 'Invalid File Type' ;
326+ options . detail = 'Please use a .txt file' ;
327+ dialog . showMessageBox ( null , options ) ;
328+ return ;
329+ }
330+
331+ fs . readFile ( filepath , 'utf-8' , ( err , data ) => {
332+ if ( err ) {
333+ alert ( "An error ocurred reading the file :" + err . message ) ;
334+ return ;
335+ }
336+
337+ // parse data, will throw error if not parsable
338+ let parsed ;
339+ try {
340+ parsed = JSON . parse ( data ) ;
341+ } catch {
342+ options . message = 'Invalid File Structure' ;
343+ options . detail = 'Please use a JSON object' ;
344+ dialog . showMessageBox ( null , options ) ;
345+ return ;
346+ }
347+
348+ if ( parsed ) {
349+ // validate parsed data type and properties
350+ if ( typeof parsed !== 'object' ||
351+ ! parsed [ 'id' ] ||
352+ ! parsed [ 'name' ] ||
353+ ! parsed [ 'reqResArray' ] ||
354+ ! parsed [ 'created_at' ] ) {
355+ options . message = 'Invalid File' ;
356+ options . detail = 'Please try again.' ;
357+ dialog . showMessageBox ( null , options ) ;
358+ return ;
359+ }
360+ }
361+
362+ // send data to chromium for state update
363+ event . sender . send ( 'add-collection' , { data} ) ;
364+ } ) ;
365+ } ) ;
366+ } )
367+
368+
369+ // ipcMain listener that
370+ ipcMain . on ( 'http1-fetch-message' , ( event , arg ) => {
371+ const { method, headers, body } = arg . options ;
372+
373+ fetch2 ( headers . url , { method, headers, body } )
374+ . then ( ( response ) => {
375+ const headers = response . headers . raw ( ) ;
376+ // check if the endpoint sends SSE
377+ // add status code for regular http requests in the response header
378+
379+ if ( headers [ 'content-type' ] [ 0 ] . includes ( 'stream' ) ) {
380+ // invoke another func that fetches to SSE and reads stream
381+ // params: method, headers, body
382+ event . sender . send ( 'http1-fetch-reply' , { headers, body : { error : 'This Is An SSE endpoint' } } )
383+ }
384+ else {
385+ headers [ ':status' ] = response . status ;
386+
387+ const receivedCookie = headers [ 'set-cookie' ] ;
388+ headers . cookies = receivedCookie ;
389+
390+ const contents = / j s o n / . test ( response . headers . get ( 'content-type' ) ) ? response . json ( ) : response . text ( ) ;
391+ contents
392+ . then ( body => {
393+ event . sender . send ( 'http1-fetch-reply' , { headers, body } )
394+ } )
395+ . catch ( error => console . log ( 'ERROR' , error ) )
396+ }
397+ } )
398+ . catch ( error => console . log ( error ) )
399+ } )
400+
401+ ipcMain . on ( 'open-gql' , ( event , args ) => {
402+ const reqResObj = args . reqResObj ;
403+
404+ // populating headers object with response headers - except for Content-Type
405+ const headers = { } ;
406+ reqResObj . request . headers . filter ( item => item . key !== 'Content-Type' ) . forEach ( ( item ) => {
407+ headers [ item . key ] = item . value ;
408+ } ) ;
409+
410+ // request cookies from reqResObj to request headers
411+ let cookies ;
412+ if ( reqResObj . request . cookies . length ) {
413+ cookies = reqResObj . request . cookies . reduce ( ( acc , userCookie ) => {
414+ return acc + `${ userCookie . key } =${ userCookie . value } ; ` ;
415+ } , "" )
416+ }
417+ headers . Cookie = cookies ;
418+
419+ // afterware takes headers from context response object, copies to reqResObj
420+ const afterLink = new ApolloLink ( ( operation , forward ) => {
421+ return forward ( operation ) . map ( response => {
422+ const context = operation . getContext ( ) ;
423+ const headers = context . response . headers . entries ( ) ;
424+ for ( let headerItem of headers ) {
425+ const key = headerItem [ 0 ] . split ( '-' ) . map ( item => item [ 0 ] . toUpperCase ( ) + item . slice ( 1 ) ) . join ( '-' ) ;
426+ reqResObj . response . headers [ key ] = headerItem [ 1 ] ;
427+
428+ // if cookies were sent by server, parse first key-value pair, then cookie.parse the rest
429+ if ( headerItem [ 0 ] === 'set-cookie' ) {
430+ const parsedCookies = [ ] ;
431+ const cookieStrArr = headerItem [ 1 ] . split ( ', ' ) ;
432+ cookieStrArr . forEach ( thisCookie => {
433+ thisCookie = thisCookie . toLowerCase ( ) ;
434+ // index of first semicolon
435+ const idx = thisCookie . search ( / [ ; ] / g) ;
436+ // first key value pair
437+ const keyValueArr = thisCookie . slice ( 0 , idx ) . split ( '=' ) ;
438+ // cookie contents after first key value pair
439+ const parsedRemainingCookieProperties = cookie . parse ( thisCookie . slice ( idx + 1 ) ) ;
440+
441+ const parsedCookie = { ...parsedRemainingCookieProperties , name : keyValueArr [ 0 ] , value : keyValueArr [ 1 ] } ;
442+
443+ parsedCookies . push ( parsedCookie ) ;
444+ } )
445+ reqResObj . response . cookies = parsedCookies ;
446+ }
447+ }
448+
449+ return response ;
450+ } ) ;
451+ } ) ;
452+
453+ // creates http connection to host
454+ const httpLink = createHttpLink ( { uri : reqResObj . url , headers, credentials : 'include' , fetch : fetch2 , } ) ;
455+
456+ // additive composition of multiple links
457+ const link = ApolloLink . from ( [
458+ afterLink ,
459+ httpLink
460+ ] ) ;
461+
462+ const client = new ApolloClient ( {
463+ link,
464+ cache : new InMemoryCache ( ) ,
465+ } ) ;
466+
467+ const body = gql `${ reqResObj . request . body } ` ;
468+ const variables = reqResObj . request . bodyVariables ? JSON . parse ( reqResObj . request . bodyVariables ) : { } ;
469+
470+ if ( reqResObj . request . method === 'QUERY' ) {
471+ client . query ( { query : body , variables } )
472+
473+ . then ( data => {
474+ event . sender . send ( 'reply-gql' , { reqResObj, data} ) } )
475+ . catch ( ( err ) => {
476+ console . error ( err ) ;
477+ event . sender . send ( 'reply-gql' , { error : err . networkError , reqResObj} ) ;
478+ } ) ;
479+ }
480+ else if ( reqResObj . request . method === 'MUTATION' ) {
481+ client . mutate ( { mutation : body , variables } )
482+ . then ( data => event . sender . send ( 'reply-gql' , { reqResObj, data} ) )
483+ . catch ( ( err ) => {
484+ console . error ( err ) ;
485+ } ) ;
486+ }
487+ } ) ;
488+
0 commit comments