Skip to content

Commit

Permalink
feat(electron): new electron bridge (#290)
Browse files Browse the repository at this point in the history
* feat(electron): add downloaded data

* chore(data): change soil moisture color map

* feat(electron): download progress

* feat(electron): improve security by disabling node integration

* fix(typos): typos
  • Loading branch information
pwambach authored Mar 11, 2020
1 parent bf2c756 commit 35b0cad
Show file tree
Hide file tree
Showing 30 changed files with 375 additions and 190 deletions.
73 changes: 10 additions & 63 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"start": "webpack-dev-server",
"clean": "rm -rf ./dist",
"build": "npm run clean && webpack --display=errors-only -p",
"build:electron": "npm run clean && webpack --config webpack.config.electron.js --display=errors-only -p",
"test": "npm run stylint && npm run eslint && npm run license-check",
"eslint": "eslint . --ext .ts,.tsx,.js",
"stylint": "stylint",
Expand All @@ -21,7 +20,7 @@
"electron:start": "electron .",
"electron:install": "electron-builder install-app-deps",
"electron:clean": "rm -rf ./dist-electron",
"electron:build": "npm run electron:clean && npm run electron:install && npm run build:electron && electron-builder -mwl --x64 --config electron-builder.json",
"electron:build": "npm run electron:clean && npm run electron:install && npm run build && electron-builder -mwl --x64 --config electron-builder.json",
"upload-storage": "gsutil rsync -r -x \".DS_Store\" ./storage gs://esa-cfs-storage/$npm_package_version && gsutil -m setmeta -r -h \"Cache-Control: no-cache\" gs://esa-cfs-storage/$npm_package_version/"
},
"repository": {
Expand Down Expand Up @@ -65,7 +64,7 @@
"conventional-changelog-cli": "^2.0.31",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.2",
"electron": "^8.0.1",
"electron": "^8.1.1",
"electron-builder": "^22.3.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
Expand Down
33 changes: 33 additions & 0 deletions src/electron/download-delete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const fs = require('fs');
const path = require('path');
const {app} = require('electron');

const {getDownloadedIds} = require('./get-downloaded-ids');

/**
* Removes the folder matching the given id from the offline directoy
*/
module.exports = function deleteId(browserWindow, id) {
// check if id contains '/', '\', '..' or ':'
if (id.match(/:|\/|\\|\.\./)) {
throw new Error('deleteId: Invalid id');
}

const downloadsPath = app.getPath('downloads');
const pathToDelete = path.join(downloadsPath, id);

if (!fs.statSync(pathToDelete).isDirectory() || id.length < 5) {
throw new Error('deleteId: Path to delete does not exist', pathToDelete);
}

console.log('Deleting', pathToDelete);

fs.rmdir(pathToDelete, {recursive: true}, err => {
if (!err) {
browserWindow.webContents.send(
'offline-update',
JSON.stringify(getDownloadedIds())
);
}
});
};
41 changes: 20 additions & 21 deletions src/electron/download-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ const path = require('path');
const zip = require('cross-zip');
const {app} = require('electron');

const {getDownloadedIds} = require('./get-downloaded-ids');

// keep track of all active downloads
const activeDownloads = {};

/**
* Intercepts all browser downloads in the given window
*/
Expand All @@ -20,6 +25,8 @@ module.exports.addDownloadHandler = function(browserWindow) {
const tmpFilePath = path.join(downloadsPath, `${Date.now()}.zip`);
item.setSavePath(tmpFilePath);

activeDownloads[item.getURL()] = 0;

console.log(`Downloading file ${item.getURL()} to ${item.savePath}`);

item.on('updated', (event, state) => {
Expand All @@ -29,7 +36,13 @@ module.exports.addDownloadHandler = function(browserWindow) {
if (item.isPaused()) {
console.log('Download is paused');
} else {
console.log(`Received bytes: ${item.getReceivedBytes()}`);
const progress = item.getReceivedBytes() / item.getTotalBytes();
activeDownloads[item.getURL()] = progress;
browserWindow.webContents.send(
'progress-update',
JSON.stringify(activeDownloads)
);
console.log(`Download progress: ${progress}`);
}
}
});
Expand All @@ -43,29 +56,15 @@ module.exports.addDownloadHandler = function(browserWindow) {
'offline-update',
JSON.stringify(getDownloadedIds())
);

delete activeDownloads[item.getURL()];
browserWindow.webContents.send(
'progress-update',
JSON.stringify(activeDownloads)
);
} else {
console.log(`Download failed: ${state}`, item.savePath);
}
});
});
};

/**
* Get downloaded Ids from the downloads folder content
*/
function getDownloadedIds() {
const dirContent = fs
.readdirSync(app.getPath('downloads'), {
withFileTypes: true
})
.filter(entry => entry.isDirectory())
.map(entry => entry.name);

const layers = dirContent.filter(name => !name.startsWith('story'));
const stories = dirContent.filter(name => name.startsWith('story'));

return {
layers,
stories
};
}
22 changes: 22 additions & 0 deletions src/electron/get-downloaded-ids.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const fs = require('fs');
const {app} = require('electron');

/**
* Get downloaded Ids from the downloads folder content
*/
module.exports.getDownloadedIds = function() {
const dirContent = fs
.readdirSync(app.getPath('downloads'), {
withFileTypes: true
})
.filter(entry => entry.isDirectory())
.map(entry => entry.name);

const layers = dirContent.filter(name => !name.startsWith('story'));
const stories = dirContent.filter(name => name.startsWith('story'));

return {
layers,
stories
};
};
12 changes: 8 additions & 4 deletions src/electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ app.allowRendererProcessReuse = true;
let windows = [];

function createWindow() {
// cerate a new browser window
// create a new browser window
const window = new BrowserWindow({
width: 1024,
height: 768,
webPreferences: {nodeIntegration: true}
width: 1400,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});

// save window's reference
Expand Down
33 changes: 33 additions & 0 deletions src/electron/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const path = require('path');
const {contextBridge, remote, ipcRenderer} = require('electron');
const app = remote.app;

// Returns the currently set "Downloads" folder joined with the given path parts
function getDownloadsPath(...parts) {
return path.join(app.getPath('downloads'), ...parts);
}

// Downloads the content at the given URL
// the download will be handled by the electron 'will-download' handler)
function downloadUrl(url) {
remote.getCurrentWebContents().downloadURL(url);
}

// Delete the offline folder of the given layer or story id
function deleteId(id) {
const deleteRemoteFn = remote.require('./download-delete');
const browserWindow = remote.BrowserWindow.getFocusedWindow();
deleteRemoteFn(browserWindow, id);
}

// The context of the preload script and the browser windows context are both
// isolated for security reasons (contextIsolation: true).
// That's why we have to expose values and functions via the context bridge.
// see https://www.electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content
contextBridge.exposeInMainWorld('cfs', {
isElectron: true,
getDownloadsPath,
downloadUrl,
deleteId,
addIpcListener: (channel, callback) => ipcRenderer.on(channel, callback)
});
17 changes: 17 additions & 0 deletions src/scripts/actions/set-download-progress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {DownloadProgress} from '../types/download-progress';

export const SET_DOWNLOAD_PROGRESS = 'SET_DOWNLOAD_PROGRESS';

export interface SetDownloadProgressAction {
type: typeof SET_DOWNLOAD_PROGRESS;
progress: DownloadProgress;
}

const setDownloadProgressAction = (
progress: DownloadProgress
): SetDownloadProgressAction => ({
type: SET_DOWNLOAD_PROGRESS,
progress
});

export default setDownloadProgressAction;
4 changes: 2 additions & 2 deletions src/scripts/components/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import GlobeNavigation from '../globe-navigation/globe-navigation';
import {EsaLogo} from '../icons/esa-logo';
import TimeSlider from '../time-slider/time-slider';
import DataSetInfo from '../data-set-info/data-set-info';

import Story from '../story/story';
import StoriesSelector from '../stories-selector/stories-selector';
import PresentationSelector from '../presentation-selector/presentation-selector';
import ShowcaseSelector from '../showcase-selector/showcase-selector';
import Globes from '../globes/globes';
// @ts-ignore
import {isElectron, connectToStore} from 'electronHelpers'; // this is an webpack alias
import {isElectron, connectToStore} from '../../libs/electron/index';

import translations from '../../i18n';

Expand Down
Loading

0 comments on commit 35b0cad

Please sign in to comment.