Skip to content

Commit

Permalink
updated readme
Browse files Browse the repository at this point in the history
  • Loading branch information
jeryongchan committed Feb 28, 2024
1 parent 9a1d05c commit 54e6fa6
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 83 deletions.
81 changes: 64 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,32 @@ The desktop app for interacting with MECA ecosystem. Uses Electron-React Boilerp

1. Clone the repo and install dependencies:

```bash
git clone https://github.com/sbip-sg/mec_anywhere_desktop.git
npm install
```
```bash
git clone https://github.com/sbip-sg/mec_anywhere_desktop.git
npm install
```

2. Install Docker Engine at https://docs.docker.com/engine/install/.

3. Build the Task Executor's docker image with:
```bash
docker build -t meca-executor -f task_executor/docker/Dockerfile .
```
```bash
docker build -t meca-executor -f task_executor/docker/Dockerfile .
```
4. Install IPFS Kubo from https://github.com/ipfs/kubo.git
### Starting Development
Ensure that Docker Engine is installed and Docker Daemon is started.
1. Ensure that Docker Engine is installed and Docker Daemon is started.
Start the app in the `dev` environment:
2. Ensure that IPFS is installed and IPFS Daemon is started.
```bash
npm start
```
3. Start the app in the `dev` environment:
```bash
npm start
```
### Packaging for Production
Expand All @@ -36,13 +41,28 @@ To package apps for the local platform (Docker Daemon require separate installat
npm run package
```
Note:
- In Windows environments, the packaged application is output as an .exe file within the /release/build directory. It is important to highlight that, in the absence of code signing, the application may not execute as intended on distribution due to Windows security protocols.
- In Linux, the packaged app is output as an AppImage. First, grant execution permissions to the AppImage file
```bash
chmod +x MECAnywhere-4.6.0.AppImage
```
Then, install FUSE to enable AppImage support.:
```bash
sudo apt update
sudo apt install libfuse2
```
# Main
The main process executes the entry point script defined in the package.json (as 'main'). It manages the application's lifecycle, and has native access to desktop functionality, such as menus, dialogs, and tray icons. More specifically, the main process for this app is responsible for the following:
- Manage lifecycle events (e.g. 'ready', 'closed'. Refer to the official docs for a complete list: *https://www.electronjs.org/docs/latest/api/app*)
- Create BrowserWindows (see *Renderer* below), Menu, Tray.
- Communicating with the MECA SDK to handle task processing via sockets (See ```sdk\README.md```).
- Communicating with the Docker Daemon to orchestrate the execution of Task Executor containers.
- Communicating with the MECA SDK to handle task processing via sockets (see ```sdk\README.md```).
- Communicating with the Docker Daemon with Dockerode to orchestrate the execution of Task Executor containers during Node.js runtime (see ```task_executor\README.md```).
- Communicating with IPFS to upload and download tasks.
- In Linux, the default download directory is `/home/$USER/.MECA/ipfsFiles`
- In Windows, the default download directory is `%LOCALAPPDATA%\.MECA\ipfsFiles`
- Handle persistent storage (mnemonics, keypairs, settings etc) with electron-store and safeStorage.

### Some technical notes:
Expand All @@ -61,19 +81,40 @@ The main process executes the entry point script defined in the package.json (as
import secp256k1 from '../../node_modules/secp256k1';
```

Read more about the rationale behind native modules here:
https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/1042

- **Inter-Process Communication**

In general, for security purpose, Inter-process communication (IPC) is required to communicate between the main process and the renderer process. The general idea involves establishing dedicated channels for these processes to monitor. Through the use of a preload script, we can then safely expose privileged APIs to the renderer process.


- **Installation issues**

Sometimes post installation complains about react-redux being a native module and should be in ```/release/app```. This error might only surface when doing a fresh ```npm install``` instead of doing a single ```npm install```. It could be due:
- newly installed module(s) being a native module instead of react-redux. In this case try moving the newly added modules into /release/app and do a clean reinstall.
- some unknown dependency issues. In this case, delete package.lock and do a clean reinstall.


- **ESM incompatiblity**

When installing a new module and using it in main process, sometimes one might face:
```bash
Error [ERR_REQUIRE_ESM]: require() of ES Module fix-path/index.js from main.ts not supported.
``````
My understanding is that in development, instead of using webpack (which allows usage of *import* in renderer process), electronmon is used to run Electron in the main process. (This is why we don't have a `webpack.config.main.dev.ts` but only `webpack.config.main.prod.ts`.) Under the hood, electronmon will convert all *import* to *require*. Thus if the installed module is purely ESM, the above error will pop. While Electron itself has started supporting ESM since v28, this feature is still not made compatible with this our electron-react-boilerplate at the point of writing this. One solution is to downgrade the module, for example now we are using a downgraded version of *ipfs-http-client* v50 (commonJS) instead of v60 (ESM only).
# Renderer
Each Electron app spawns a separate renderer process for each open BrowserWindow. BrowserWindow can display web content and use web technologies like HTML, CSS, and JavaScript to build the UI of the application. There are 2 BrowserWindows in our app:
- MainWindow: Uses React and Material UI to render webpages, just like any other React projects.
- WorkerWindow: This window is hidden and is solely responsible for task processing logic.
Routes are managed under App.tsx. Each route is wrapped with a Transition component for smooth transition animation.
```
```bash
<Route
path="/login"
element={
Expand Down Expand Up @@ -135,7 +176,7 @@ Currently the listed tasks are placeholders, future implementation require readi
The main layout, which can be viewed after logging into the app, consists of the top menu bar, the left drawer, and the right drawer. We will discuss some key components below.
- **Host Sharing Widget**: Responsible for allowing host to select settings for resource allocation (e.g. number of cores and memory) as well as option whether to allocate GPU. The widget view comprises *PreSharingEnabledComponent* and *PostSharingEnabledComponent*, which are respectively responsible for the states before and after resource sharing is enabled.
- **Export Key**: Reveals the mnemonics and allows copy to clipboard.
- **Export Key**: Reveals the mnemonics and allows copy to clipboard. Might be removed in the future due to deprecation of ```did```
- **Light/Dark Mode**: Toggles between light and dark theme. Theming is based on Material UI and is set in ```src\renderer\utils\theme.tsx```.
Expand Down Expand Up @@ -169,4 +210,10 @@ const dataEntry = reduxStore.getState().dataEntryReducer;
### services
This directory simple handles the external api calls.
This directory simple handles the external api calls.
## Other notes
- When developing in linux, sometimes closing the app doesn't completely shut down the electron processes, thus requiring one to manually kill the processes.
- Use electron-store for persistent storage, redux for global state management, and React's useState() hook for component-level state.
- Under `webpack.config.base.ts`, there is an alias of react pointing to `./src/node_modules/react'`. This is because the *@metamask/sdk* introduces another instance of React, causing conflicts. The alias ensures the project uses only one instance of React.
4 changes: 2 additions & 2 deletions package-lock.json

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

53 changes: 24 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
{
"name": "mecanywhere",
"description": "A foundation for scalable desktop apps",
"keywords": [
"MEC",
"multi-access edge computing",
"blockchain",
"electron",
"boilerplate",
"react",
"typescript",
"ts",
"sass",
"webpack",
"hot",
"reload"
"go"
],
"homepage": "https://github.com/electron-react-boilerplate/electron-react-boilerplate#readme",
"bugs": {
"url": "https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/electron-react-boilerplate/electron-react-boilerplate.git"
},
"license": "MIT",
"author": {
"name": "Electron React Boilerplate Maintainers",
"email": "[email protected]",
"url": "https://electron-react-boilerplate.js.org"
"name": "Singapore Blockchain Innovation Programme",
"email": "[email protected]",
"url": "https://sbip.sg"
},
"contributors": [
{
"name": "Amila Welihinda",
"email": "[email protected]",
"url": "https://github.com/amilajack"
"name": "Chan Jer Yong",
"email": "[email protected]"
},
{
"name": "Lim Jun Xue",
"email": "[email protected]"
},
{
"name": "Hu Guo Yu",
"email": "[email protected]"
},
{
"name": "Ștefan-Dan Ciocîrlan",
"email": "[email protected]"
}

],
"main": "./src/main/main.ts",
"scripts": {
Expand Down Expand Up @@ -288,15 +291,7 @@
},
"extraResources": [
"./assets/**"
],
"publish": {
"provider": "github",
"owner": "electron-react-boilerplate",
"repo": "electron-react-boilerplate"
}
},
"collective": {
"url": "https://opencollective.com/electron-react-boilerplate-594"
]
},
"devEngines": {
"node": ">=14.x",
Expand Down
8 changes: 4 additions & 4 deletions release/app/package-lock.json

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

12 changes: 6 additions & 6 deletions release/app/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "electron-react-boilerplate",
"version": "4.6.0",
"description": "A foundation for scalable desktop apps",
"name": "mecanywhere",
"version": "1.0.0",
"description": "A blockchain-based edge computing platform",
"license": "MIT",
"author": {
"name": "Electron React Boilerplate Maintainers",
"email": "[email protected]",
"url": "https://github.com/electron-react-boilerplate"
"name": "Singapore Blockchain Innovation Programme",
"email": "[email protected]",
"url": "https://sbip.sg"
},
"main": "./dist/main/main.js",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions src/common/channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const Channels = {
SELECTED_FOLDER: 'selected-folder',
TEST_READ_FILE: 'test-read-file',
DELETE_FOLDER: 'delete-folder',
CHECK_FOLDER_EXISTS: 'check-folder-exists'
};

export default Channels;
16 changes: 8 additions & 8 deletions src/main/dockerIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import Channels from '../common/channels';

const docker = new Dockerode();

interface Event {
reply(_channel: string, _success: boolean, _message?: string): void;
}
// interface Event {
// reply(_channel: string, _success: boolean, _message?: string): void;
// }

export const removeExecutorContainer = async (
event: Event,
event,
containerName: string
) => {
docker.listContainers(
Expand Down Expand Up @@ -77,7 +77,7 @@ export const removeExecutorContainer = async (
};

export const runExecutorContainer = async (
event: Event,
event,
containerName: string
) => {
try {
Expand Down Expand Up @@ -249,7 +249,7 @@ export const runExecutorGPUContainer = async (event, containerName) => {
}
};

export const checkDockerDaemonRunning = (event: Event) => {
export const checkDockerDaemonRunning = (event) => {
docker.ping((err, data) => {
if (err) {
console.error('Docker daemon is not running', err);
Expand All @@ -265,7 +265,7 @@ export const checkDockerDaemonRunning = (event: Event) => {
});
};

export const checkContainerExists = (event: Event, containerName: string) => {
export const checkContainerExists = (event, containerName: string) => {
docker.listContainers({ all: true }, (err, containers) => {
if (err) {
console.error('Error listing containers:', err);
Expand All @@ -281,7 +281,7 @@ export const checkContainerExists = (event: Event, containerName: string) => {
});
};

export const checkContainerGPUSupport = (event: Event, containerName: string) => {
export const checkContainerGPUSupport = (event, containerName: string) => {
docker.listContainers({ all: true }, (err, containers) => {
if (err) {
console.error('Error listing containers:', err);
Expand Down
45 changes: 30 additions & 15 deletions src/main/ipfsIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const client = create({ url });
export const openFileDialog = async (event) => {
let win = BrowserWindow.fromWebContents(event.sender);
if (win) {
const { canceled, filePaths } = await dialog.showOpenDialog(win, {
const { canceled, filePaths } = await dialog.showOpenDialog(win, { // win here is to ensure user can only interact with the dialog
properties: ['openFile'] // Allows users to select files only
});
if (canceled) {
Expand Down Expand Up @@ -175,27 +175,42 @@ export const readFirstLineOfFileInFolder = async (event, cid: string) => {
return "Error";
}
};
export const deleteFolder = async (event, cid: string) => {
const folderPath = `${ipfsFilesDir}/${cid}`;

try {
await fs.remove(folderPath);
console.log(`Successfully deleted local folder at ${folderPath}`);
return true;
} catch (error) {
console.error(`Error deleting local folder at ${folderPath}:`, error);
return false;
}
};
export const deleteFolder = async (event, cid: string) => {
const folderPath = `${ipfsFilesDir}/${cid}`;

try {
await fs.remove(folderPath);
console.log(`Successfully deleted local folder at ${folderPath}`);
return true;
} catch (error) {
console.error(`Error deleting local folder at ${folderPath}:`, error);
return false;
}
};

export const checkFolderExists = async (event, cid: string) => {
// Construct the full path to the folder
const folderPath = `${ipfsFilesDir}/${cid}`;

try {
const exists = await fs.pathExists(folderPath);
console.log(`Folder ${cid} exists: ${exists}`);
return exists;
} catch (error) {
console.error(`Error checking existence of folder ${cid}:`, error);
return false;
}
};

const generateLargeFile = async (filePath: string, sizeInMB: number) => {
// Delete the file first if it exists
await fs.remove(filePath);
// await fs.remove(filePath);

return new Promise((resolve, reject) => {
const stream = fs.createWriteStream(filePath);
const oneMB = 1024 * 1024; // Bytes
const chunkSize = 1024; // Adjust chunk size as needed
const oneMB = 1024 * 1024;
const chunkSize = 1024;
const totalChunks = (sizeInMB * oneMB) / chunkSize;
let written = 0;

Expand Down
Loading

0 comments on commit 54e6fa6

Please sign in to comment.