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!