Tuesday, April 14, 2020

Introduction

Hello all you fellow code junkies!

I am a seasoned software developer / contractor / consultant (some call me an old goat) with over 30 years of experience under my belt. I literally have "been there, done that, got the t-shirt". You can find me on linkedin or visit my website.

As a contractor I have a variety of clients with differing requirements. Most of my work had evolved into building what we were calling “internet or web enabled applications”. I found myself writing a lot of the framework code over and over for each new project. I realized the need for a simple yet powerful framework for building Single Page Applications. I also wanted a common framework that could be used to build a website (web application) or be deployed to a mobile device or be installed as a desktop app.

Please follow along my journey here to build a simple yet powerful collection of libraries to achieve the proverbial "One Code Base - Deploy Anywhere" solution.



Note: I have recently updated the SmokeHouse/Applewood project on GitHub to the latest code version that  I am referencing in this blog. Please update your local repositories.

As I complete future modules I will add links here. Please check back often to see my new posts and follow my progress.

Note: This project has been complete and in use for over a year. I have decided to update the core project with newer libraries and document my journey here so others may benefit.

Cheers,
Daryl

Debugging Electron App

Launching Electron

Now that we have exposed Electron inside our Aurelia app it would be nice to be able to modify our source and have all the same debugging features available that Aurelia affords us.

Lets start by modifying the Aurelia run script so that we can launch Electron from the Aurelia CLI.
  1. Open the run.js file located in the aurelia_project\tasks folder
  2. Add the following reference

    import * as childProcess from 'child_process';
  3. Now add the following code to launch Electron

    let elect = gulp.series(
        build,
        done => {
          childProcess
            .exec('npm start')
            .on("close", () => {
              // User closed the app. Kill the host process.
              process.exit();
            })
      
          done();
        }
    );
  4. Next we modify the run code to use the electron launcher

    let job;

    if (CLIOptions.hasFlag('electron')) {
        job = elect
    else {
        job = serve
    }

    let run = gulp.series(
      job,
      done => { watch(reload); done(); }
    );
  5. The file should now look like this


  6. Now we can launch Electron by including our new --electron flag to the CLI command
  7. Run command 'nvm use 10.14.0'
  8. Run command 'au run --watch --server --electron'
This should launch our project in an electron window


You can open the dev tools and carry on developing just like we do when using Chrome.

Refreshing Electron App

One of the really nice features of developing in Chrome is the use of BrowserSync to keep the Chrome window refreshed automatically with changes we make in source.

There is an electron reload package that we can add that watches files for changes and re-loads the electron window ... perfect ... well almost. Since our solution is getting fairly complex we are monitoring and copying a lot of files every time the Aurelia watcher detects a change. Unfortunately watching our app directory causes multiple reloads of the electron app for every change we make. This isn't ideal. The good news is we can manipulate the reloader by adding a single file to our copy command and then watching the single file for a change.
  1. Create a new file in the \root folder called reload ... It doesn't need an extension because it is just a dummy file used to trigger the reloader.

  2. Next we want to include the dummy reload file in our push files script that copies files to the electron app directory when a change is triggered.
  3. Open the aurelia.json file in the aurelia_project folder
  4. Add "reload": "" to the end of the sources node in the pushFiles node as shown below

  5. Now we need to add the electron-reload package to our project
  6. Open a command prompt in the /root folder of our project.
  7. Run command 'nvm use 10.14.0'
  8. Run command 'npm install electron-reload --save-dev'
  9. Next we need to load the package at run time.
  10. Open the file main.js located in the /app directory
  11. Add the following code

    // load the electron reloader
    try {
        const path = require('path');
        //watch the reload file for changes
        require('electron-reload')(path.join(__dirname,'reload'), {
            electron: path.join(__dirname'node_modules''.bin''electron')
        });
    catch(e) {}
  12. The file should now look like this


       
That's it! Save all your work and re-launch the au run command line with the electron switch. Make some changes in source and voila ... the electron window will refresh and you should see your changes!

Thursday, March 5, 2020

Exposing Electron inside Aurelia App

Expose Electron inside Aurelia App


So far we have been able to load the Aurelia app inside an Electron wrapper and successfully build and deploy a desktop solution. In order to take the app to the next level and interact with the desktop we need to be able to expose the Electron API inside our Aurelia app.

If you have tried to include Electron in the Aurelia bundle you will quickly discover that this doesn't work. The reason is because Electron is an environment dependent package ... much like Cordova. It is a set of binaries tailored to the local install based on your machine OS. However the good news is that like Cordova, it exposes a javascript API to cross the bridge into the native OS binaries. But how do we expose that API inside Aurelia?

You may remember that Cordova generated an API in the form of a cordova.js file. We then created a bootstrap file to include the browser version of this file into our Aurelia app. Cordova exposes it's API as globals so we can just go ahead and use it inside the Aurelia app without the need to import any modules.

Electron Main and Child Processes

In order to help clarify our mission, we need some fundamental understanding of how electron works.

Main process -> When we launch our electron app we begin by running the main.js file in the app root. That file uses require to load the electron object model. We then use the electron model to open a Browser Window in which we launch the Aurelia app. You can think of the main process as the equivalent of a console window running node ... same as when we run the Aurelia app in debug mode at dev time.

Child process -> The child process loads our Aurelia app. This is a separate or spawned process running isolated from the main process. You can think of the child process as equivalent to the chrome browser hosting the application ... same as when we run the Aurelia app in debug mode at dev time.

There is no way for the Child process to communicate or pass events back to the Main process directly, however Electron has provided us with some tools to do exactly that!

These tools are readily available in the Main process, however we need to find a way to load them in the child process and this is where the difficulty resides.

Include Electron in Aurelia App-Bundle

Aurelia bundles all of the app dependencies into 2 javascript files. A vendor-bundle and an app-bundle. The bundler uses all the dependencies defined in the root package.json file. If you recall we cannot include electron in this file ... the builder complains. This is because the electron node package does not expose a "hard copy" javascript API ... instead the electron binaries produce a dynamic object model at runtime.

So we are left with trying to include electron in the app-bundle but fundamentally we have the same problem. The solution is to construct a polyfill that uses require to load an electron object model, then export that model as an AMD object that can be bundled in the app-bundle.

Electron Polyfill

  1. Add a new file called electron.js to the client directory
  2. Open the file in the editor and enter the following code
const _electron = window.nodeRequire
                  window.nodeRequire("electron") : null;
export default _electron;

We use window.nodeRequire here ... remember we had to add a chunk of script to the root/index.html page that resolves a bootstrapping conflict?

    <script>
      // following needed by electron
      // to eleiminate bootstrapping conflict
      if (typeof require !== 'undefined') {
        window.nodeRequire = require;
        delete window.require;
        delete window.exports;
        delete window.module;
      }
    </script>

The code form index.html exposes the require module used in the main process as window.nodeRequire. It has access to the node_modules folder where the electron binaries reside. The Aurelia loader uses an instance of require from the child process which only includes modules loaded by the Aurelia bundler and since electron is not included in the bundle the child instance of require cannot find the electron module.

The code from our electron polyfill uses require from the main process to load the electron object model then it exports that model as an AMD module that Aurelia can include in the app-bundle ... pretty slick!

Keep in mind that during dev when we are running the app in a chrome browser the window.nodeRequire does not get instantiated therefore electron will be set to null. This makes sense because the electron environment isn't running. However when we launch the electron app windows.nodeRequire is active and we can require electron and expose it through the app bundle!

Now you can simply import electron into any Aurelia module as follows

import electron from 'electron';
console.log('***** Electron =>'electron);

You can see the result in the developer tools window


If you inspect the same code running in dev in the browser you will see


By creating the polyfill in the client directory and naming it 'electron' the Aurelia bundler picks it up automatically and exposes it in the Aurelia require stack as 'electron' ... It doesn't get much easier than that!


Saturday, February 29, 2020

Notarize IOS Apps

Notarizing your IOS App

With the release of MacOS 10.14.5 all signed applications require notarization. If you don't notarize your app during the build process the app will not run on your Mac.

I discovered this after my builds would no longer run on the Mac ... I thought I had broken the code somewhere only to discover that it ran fine in the dev environment just simply would not run after installation on the desktop.

The solution is to Notarize your code during the build process. There is a good tutorial here

Create entitlements file

  1. In the build folder create a new file called entitlements.mac.plist
  2. Paste the following into the file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
  </dict>
</plist>


Your folder should look like this


Update package.json file

  1. Open package.json located in the project root
  2. Find the "build" node
  3. Add the following to the "build/mac" node
      "hardenedRuntime"true,
      "gatekeeperAssess"false,
      "entitlements""build/entitlements.mac.plist",
      "entitlementsInherit""build/entitlements.mac.plist"

The "build" node should now look like this


NOTE: The "appId must be changed to reflect your Apple Developer ID!


Create App Specific password

You must have a valid developer id issued by Apple in orer to sign your app. If you do not have one there is a good tutorial here

You also need an app specific password in order to notarize your app. There is a good tutorial here

  1. Open appleid.apple.com in your browser and login using your apple id.
  2. Generate a new password for this app.
  3. Make sure you copy it and save it somewhare safe when it is displayed ... this is the ONLY time you will ever see this password from Apple!

Create notarize.js file

  1. Create a new folder in the project root called "notarize"
  2. Create a new file in this folder called "notarize.js"
  3. Paste the following into this file
require('dotenv').config();
const { notarize } = require('electron-notarize');

exports.default = async function notarizing(context) {
  const { electronPlatformNameappOutDir } = context;  
  if (electronPlatformName !== 'darwin') {
    return;
  }

  const appName = context.packager.appInfo.productFilename;

  //app-specific password generated by Apple 
  const password = 'xxxx-xxxx-xxxx-xxxx';

  return await notarize({
    appBundleId: 'com.yourcompany.applewood',
    appPath: `${appOutDir}/${appName}.app`,
    appleId: 'yourappledevid',
    appleIdPassword: password
  });
};

NOTE: 
  1. The appBundleId must match the appId from the "build" node (as shown above)
  2. The appleId must be your Apple Developer Id
  3. The password must be the app specific password that you just generated

WARNING: This file contains your app specific password and your developer id from Apple. You should not commit this to any public source repository! If you do others can use your ID and password to sign and notorize code that will become attached to your profile!


Hook for Notarizing

Next you need to insert the notarize script into the build chain.

  1. Open package.json fiel from the root directory
  2. Insert the following into the "build" node directly after the "appId"
"afterSign""notarize/notarize.js",
  1. Insert the following after the "win" node. This will stop the builder from signing the dmg file.
    "dmg": {
       "sign"false
    }

Your file should look like this




Install electron-notorize package

  1. Open a command prompt in the root folder of our project.
  2. Run command 'nvm use 10.14.0'
  3. Run command 'npm install electron-notarize --save-dev'

Building the app

  1. Open a command prompt in the root folder of our project.
  2. Run command 'nvm use 10.14.0'
  3. Run comand 'au build'
  4. Run command 'npm run dist'
Now sit back and be patient ... the notarization process can be a lengthy one depending on your internet connection and how busy apples's servers are ... basically the process uploads your app to apple servers ... which then scan your app for infractions ... then they do their magic and return a notarzed app ... assuming all is well!


Tuesday, February 26, 2019

Push Aurelia => Electron build

Now that we have added an Electron skeleton app to our project it is time to integrate the Aurelia project into the Electron project so that we can build for the desktop.

Merge Aurelia => Electron

The first step will be merging the Aurelia project into the Electron project so that when we run the Electron project we will be launching the Aurelia project, much like we did with Cordova.

The good news is ... it is relatively simple to do. The Electron project loads the app/main.js file which in turn loads the app/index.html file. All we have to do is copy our index.html file from the root and it's associated files into the app directory and voila ... Aurelia running inside Electron ... sweet !

There is one small gotcha which we will fix first. The integration of Aurelia and Electron causes some DOM conflicts with require, export and module keywords. There is a simple fix to avoid this from happening. We add a script to the index.html file that corrects the DOM definitions for Aurelia. This script runs after Electron has loaded so it does not impact the Electron functionality.
This is a known issue that effects most node based libraries. You can read about it here.
  1. Open index.html in the editor from the project root
  2. Add the following immediately before the <script src="cordova.js"></script> tag


  3. Your index.html file should look like this

That's it. Next we need to push the Aurelia files to the Electron project

Push Aurelia => Electron

The first thing we need to do is address a small shortcoming in the Aurelia copy-files routine that we used previously to copy files into the Cordova project. We want to continue copying the files to the Cordova project as well as copy them to the Electron project. The copy-files routine does not support multiple destinations and does not allow us to add the same file more than once. This creates a challenge for us. We could duplicate the copy-files routine but that violates the DRY mantra. The best solution is to modify the copy-files routine to support multiple destinations.
  1. Open aurelia_project/tasks/copy-files.js in your editor
  2. Change the code to the following. You can download the file here.

This modified routine will now accept an array of destinations for each instruction.
Next we need to add our new destinations to the aurelia_project/aurelia.json file
  1. Open aurelia_project/aureia.json file in your editor
  2. Change "platforms/browser/www/cordova*.js": "scripts" to  ["scripts", "app/scripts"]
  3. Change "platforms/browser/www/plugins/**/*.js": "scripts/plugins" to ["scripts/plugins" , "app/scripts/plugins"]
  4. Your file should look like this

Remember, the copy-files routine runs "pre build" and we are using it to ensure the Cordova scripts and plugins are included in the build.

NOTE: As of this writing I have logged a feature request with the Aurelia CLI team to add this functionality to the Aurelia project. If it is picked up by the team and included in a release then you will not have to make this change to copy-files.js on new projects in the future!

We need to add two more files to the copy-files routine for Electron. We need the platforms/browser/www/config.xml and the cordova.js shim file from the root so that the Cordova browser platform can run inside the Electron app.
  1. Open aurelia_project/aureia.json file in your editor
  2. Add "platforms/browser/www/config.xml": ["app"] to the copyFiles node
  3. Add "cordova.js": ["app"] to the copyFiles node
  4. Your file should look like this

Next we want to to push the Aurelia "post build" files to the Electron app directory.
  1. Open aurelia_project/aureia.json file in your editor
  2. Add "app" to the pushFiles/targets node
  3. Your file should look like this

Next we need to do a little housekeeping.

  1. Open the .gitignore file and make the following changes

  2. We are only interested in saving the app/main.js file and the app/package.json file. All other files and folders are dynamic and do not need to be stored in the source code repository.

That is it for changes. Let's test our Electron app!

  1. Open a command prompt
  2. Run command 'npm start'
  3. The Electron app should open on your desktop

  4. Have a look in dev tools. Notice that app has loaded the Cordova libraries and started Aurelia
    Add caption

Now let's build an installable package

  1. Open a command prompt
  2. Run command 'npm run dist'
  3. The compiler will build an installable for your local environment. This means an exe file for Windows machines or a dmg package for Macs.

  4. The installable package is located in the dist directory.
    Add caption
  5. Go ahead and install it and then launch the app from the desktop.
    Add caption


Now I will swing over to the Mac and test our build for the Apple platform.

  1. Open a terminal in the project root
  2. Run command 'npm run dist'

  3. Look in the dist folder

  4. Install the dmg and open the app

  5. You should see the Cordova plugins load and the Aurelia app has started without any errors logged

We now have a single code base that can run in a browser, Android device, IOS Device, Windows desktop and MAC desktop ... mission accomplished!

My next blog will recap the various steps needed to build the solutions as well as a complete list of the multitude of command line instructions for this project. I will also introduce some automation to help you simplify the command structure and improve productivity.