Skip to content

Commit 7ddd18d

Browse files
authored
Merge pull request #251 from open-source-labs/dev
Merge to master for final build.
2 parents e72be95 + 22c8d60 commit 7ddd18d

33 files changed

+1056
-319
lines changed

.babelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
"presets": [
88
"@babel/preset-env",
99
"@babel/preset-react"
10-
]
10+
],
1111
}

__tests__/composerTests.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,7 @@ describe('GraphQL Composer', () => {
102102

103103
it('Renders a <div>', () => {
104104
expect(wrapper.type()).toEqual('div');
105-
// expect(wrapper.text()).toEqual('Mega: Markets');
106-
// expect(wrapper.find('strong').text()).toMatch('Mega');
107105
});
108-
109-
it('lskdf', () => {
110-
expect(5).toBe(5)
111-
})
112106
})
113107
})
114108
})

main.js

Lines changed: 227 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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');
88
const path = require('path');
99
const 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
1313
const { autoUpdater } = require('electron-updater');
1414
const log = require('electron-log');
1515
// TouchBarButtons are our nav buttons(ex: Select All, Deselect All, Open Selected, Close Selected, Clear All)
1616
const { 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
1932
autoUpdater.logger = log;
2033
autoUpdater.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 = /json/.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

Comments
 (0)