Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
app
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

English | ็ฎ€ไฝ“ไธญๆ–‡ | เคนเคฟเคจเฅเคฆเฅ€

airtrash

Clone of Apple's AirDrop - easy P2P file transfer powered by stupidity


Build Status GitHub release

๐Ÿ”ฆ Screenshot

๐ŸŽฏ Goal

100 tiny steps to build cross-platform desktop application using Electron/Node.js/C++

It's simple tutorial/guide for absolute beginners to present some tips for creating desktop application. Unlike @electron/electron-quick-start, which presents the typical hello world. This project aims to focus on real-live scenario, where we will try to implement a complete product (like cross-platform Apple's AirDrop replacement).

๐Ÿ’ฝ Installation

Download from GitHub Releases and install it.

from source

To clone and run this repository you'll need Git and Node.js (and yarn) installed on your computer. From your command line:

# clone this repository
git clone https://github.com/maciejczyzewski/airtrash
# go into the repository
cd airtrash
# install dependencies
yarn
# run the app
yarn start

Note: If you're using Linux Bash for Windows, see this guide or use node from the command prompt.

macOS

The macOS users can install airtrash using brew cask.

brew update && brew cask install airtrash

(nice try, you can't)

๐Ÿ“ƒ Tutorial

Let's begin our journey.

1: starting from template

Clone and run for a quick way to see Electron in action. From your command line:

yarn is strongly recommended instead of npm.

# clone this repository
$ git clone https://github.com/electron/electron-quick-start
# go into the repository
$ cd electron-quick-start
# install dependencies
$ yarn
# run the app
$ yarn start

You should see:

And have this file structure:

.
โ”œโ”€โ”€ LICENSE.md        # - no one's bothered
โ”œโ”€โ”€ README.md         # - sometimes good to read
โ”œโ”€โ”€ index.html        # body: what you see
โ”œโ”€โ”€ main.js           # heart: electron window
โ”œโ”€โ”€ package-lock.json # - auto-generated
โ”œโ”€โ”€ package.json      # configuration/package manager
โ”œโ”€โ”€ preload.js        # soul: application behavior
โ””โ”€โ”€ renderer.js       # - do after rendering

0 directories, 8 files

2: using @electron-userland/electron-builder for packing things

Our next goal will be to build .dmg and .app files with everything packed up.

  1. Run: $ yarn add electron-builder --dev

  2. Modify package.json:

+   "name": "airtrash",
    "scripts": {
        "start": "electron .",
+       "pack": "electron-builder --dir",
+       "dist": "electron-builder",
+       "postinstall": "electron-builder install-app-deps"
    },
    ...
+    "build": {
+        "appId": "maciejczyzewski.airtrash",
+        "mac": {
+            "category": "public.app-category.utilities"
+        }
+    },
  1. Run: yarn dist

You should see:

$ electron-builder
  โ€ข electron-builder  version=21.2.0 os=17.7.0
  โ€ข loaded configuration  file=package.json ("build" field)
  โ€ข writing effective config  file=dist/builder-effective-config.yaml
  โ€ข packaging       platform=darwin arch=x64 electron=7.1.7 appOutDir=dist/mac
  โ€ข default Electron icon is used  reason=application icon is not set
  โ€ข building        target=macOS zip arch=x64 file=dist/airtrash-1.0.0-mac.zip
  โ€ข building        target=DMG arch=x64 file=dist/airtrash-1.0.0.dmg
  โ€ข building block map  blockMapFile=dist/airtrash-1.0.0.dmg.blockmap
  โ€ข building embedded block map  file=dist/airtrash-1.0.0-mac.zip
โœจ  Done in 59.42s.

And have this additional files:

3: adding @twbs/bootstrap to project

Let's add some popular package (like bootstrap) to understand how to do it.

  1. Run:
$ yarn add bootstrap --dev
$ yarn add normalize.css --dev # good practise
$ yarn add popper.js --dev # bootstrap needs this
$ yarn add jquery --dev # and this to be complete
  1. Enable nodeIntegration in main.js:
    webPreferences : {
+      nodeIntegration : true,
       preload : path.join(__dirname, 'app/preload.js'),
    }
  1. Modify index.html:
<head>
  <head>
-    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
-    <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
+    <link rel="stylesheet" href="node_modules/normalize.css/normalize.css" />
+    <link
+      rel="stylesheet"
+      href="node_modules/bootstrap/dist/css/bootstrap.min.css"
+    />
  ...
  </head>
  <body>
  ...
+    <script>
+      window.$ = window.jquery = require('jquery');
+      window.popper = require('popper.js');
+      require('bootstrap');
+    </script>
  </body>
</html>

4: customizing window/interface

In Electron you can modify the window interface. Let's play with it.

  1. Change defaults (adding icon) in main.js:
  mainWindow = new BrowserWindow({
+   titleBarStyle: 'hiddenInset',
+   width : 625,
+   height : 400,
+   // resizable: false, # user's don't like this option
    webPreferences : {
      nodeIntegration : true,
      preload : path.join(__dirname, 'app/preload.js'),
+     icon : __dirname + '/icon.png'
    }
  })
  1. Because of titleBarStyle: 'hiddenInset', it need to be defined new draggable element in window. It can be achieved by adding to index.html:
+ <body style="-webkit-app-region: drag">

Result should be:

5: adding native extension (@nodejs/nan C++ library)

Refer to a quick-start Nan Boilerplate for a ready-to-go project that utilizes basic Nan functionality (Node Native Extension).

  1. Run:
$ yarn add node-gyp --dev
$ yarn add electron-rebuild --dev # to fix some common problems
  1. Modify:
    "scripts": {
        "start": "electron .",
        "pack": "electron-builder --dir",
        "dist": "electron-builder",
+       "build": "node-gyp build",
+       "configure": "node-gyp configure",
+       "postinstall": "electron-builder install-app-deps && \
+                       ./node_modules/.bin/electron-rebuild"
    },
    ...
    "build": {
+       "files": [
+           "**/*",
+           "build/Release/*"
+       ],
+       "nodeGypRebuild": true,
+       "asarUnpack": "build/Release/*",
        "appId": "maciejczyzewski.airtrash",
        "mac": {
            "icon": "icon.png",
            "category": "public.app-category.utilities"
        }
    },
  1. Add binding.gyp:
{
  "targets": [
    {
      "target_name": "airtrash",
      "sources": [
        "airtrash.cc",
        "src/api.cc",
      ],
      "include_dirs" : [
        "<!(node -e \"require('nan')\")"
      ]
    }
  ],
}
  1. Add these files: airtrash.cc:
#include "src/api.h"

using v8::FunctionTemplate;

#define NAN_REGISTER(name) \
  Nan::Set(target, Nan::New(#name).ToLocalChecked(), \
           Nan::GetFunction(Nan::New<FunctionTemplate>(name)).ToLocalChecked());


NAN_MODULE_INIT(InitAll) {
  NAN_REGISTER(return_a_string);
}

NODE_MODULE(airtrash, InitAll)

src/api.cc:

#include "api.h"

void return_a_string(const Nan::FunctionCallbackInfo<v8::Value> &args) {
  std::string val_example = "haha, just a string ;-)"
  args.GetReturnValue().Set(
      Nan::New<v8::String>(val_example).ToLocalChecked());
}

src/api.h:

#ifndef NATIVE_EXTENSION_GRAB_H
#define NATIVE_EXTENSION_GRAB_H

#include <nan.h>

NAN_METHOD(return_a_string);

#endif
  1. Test in main.js:
var NativeExtension = require("bindings")("airtrash");
console.log(NativeExtension.return_a_string());
// => haha, just a string ;-)

6: we don't have threads, how to handle this

Recommended reading: Node.js multithreading: What are Worker Threads and why do they matter?

  1. Run: $ yarn add worker-farm --dev

  2. Create file app/push.js:

var NativeExtension = require('bindings')('airtrash');

module.exports = (input, callback) => {
  console.log("PUSH", input.address, input.path)
  NativeExtension.push(input.address, input.path)
  callback(null, input)
}
  1. Then when we run this:
const workerFarm = require("worker-farm");
const service_push = workerFarm(require.resolve("./push"));
service_push(data,
               function(err, output) {
                 new Notification(
                     "Transmission Closed!",
                     {body : output.path + " from " + output.address})
               });
console.log("hello!");

Result should be (random order of lines):

hello!
PUSH 192.168.0.?:9000
<killing service signal>
Transmission Closed!

7: idea behind simple P2P (naive but should work)

It should define 3 main functions:

  • scan: iterates (like nmap) through defined ranged of ports for whole local network and sends message to test if they are used by our application (welcome token).
  • push: starts a server (called here node) in new process with shared filed (if someone sends correct request, it starts new thread for this connection)
  • pull: connects to server and downloads the data

To be a real P2P, nodes should be propagated (without user action) through network (additionally only parts of files, not whole).

8: still writing...

9: still writing...

10: still writing...

๐Ÿค Contribute PRs Welcome

If you are interested in participating in joint development, PR and Forks are welcome!

๐Ÿ“œ License

MIT Copyright (c) Maciej A. Czyzewski

About

๐Ÿ“ก 100 tiny steps to build cross-platform desktop application using Electron/Node.js/C++

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published