URLs in Notes client do not open

A couple of weeks ago, I noticed that hyperlinks in a Notes mail do no longer open. I did not pay much attention to it, because I am reading my mails mostly in the browser or on a smart device.

Today, I did some Google research. I came across an old technote that best describes the behaviour.

LO56433: CANNOT CLICK URLS IN NOTES 8.5.2 WITH OS BROWSER

What I found is that HKEY_CLASSES_ROOT\https\shell\open\command\ pointed to a non existing browser and HKCU\Software\Classes\http\shell\open\command\ was completely missing in the system registry.

Setting the entries to the DEFAULT iexplore.exe has fixed the issue.


Electron – reload all BrowserWindows on code change

electron-reload is the simplest way to load contents of all active BrowserWindows within electron when the source files are changed.

To add electron-reload to your existing project, type

 npm i electron-reload --save-dev 

Initialize this module with desired glob or file path to watch and let it refresh electron browser windows as targets are changed

const electron = require('electron');
const path = require('path');
const url = require('url');

const { app, BrowserWindow } = electron;

require('electron-reload')(__dirname);

let win;

app.on('ready', createWindow);

Here is a small video showing electron-reload in action.

You can download the source code for this article here.


midpoints LE4D 2.0 – some hints

On March, 28th, we released LE4D v2.0. If you are running LE4D v1.x, you must update to v2.0. Certificate renewal will no longer work with v1.x because of some changes on the Let’s Encrypt API endpoint.

Here are some additional hints.

Settings documents are disabled after design change

In v2.0, we added a new feature to toggle the status of setings documents. All new settings are disabled by default. And also, after the design replace, you have to enable them prior to run the agent.

LE4DDisabled

Error: No trusted certificates found

You might see the following error message on the Domino console:

29.03.2018 08:21:39   Agent Manager: Agent  error: Caused by:
29.03.2018 08:21:39   Agent Manager: Agent  error: com.ibm.jsse2.util.h: No trusted certificate found
29.03.2018 08:21:39   Agent Manager: Agent  error:         at com.ibm.jsse2.util.g.a(g.java:21)

This happens most likely after you have applied a FP or HF. In all cases we have seen, the cacerts is replaced with the default cacerts during FP/ HF install.

To fix this problem, you have to import the needed certificates again.

The certificates can be found here https://letsencrypt.org/certificates/

An “HowTo” about importing the certs can be found here http://abdata.ch/add-a-root-certificate-to-ibm-domino-jvm-keystore/

Error: Order’s status (“invalid”) was not pending

You might see the following error message on the Domino console:

28/03/2018 22:51:58   Agent Manager: Agent  error:         at lotus.domino.NotesThread.run(Unknown Source)
28/03/2018 22:51:58   Agent Manager: Agent printing: [ERROR] – Order’s status (“invalid”) was not pending
28/03/2018 22:51:58   Agent Manager: Agent printing: LE4D  – finished!

Due to the change in the underlying ACME protocol, Let’s Encrypt needs to re-validate the HTTP challenge on certificate renewal. Do do this, the challenge token must be accessible on the Domino server on port 80.

If you only have port 443 open, then the challenge will fail and you will see the error message.

Just for clarification. Port 80 is only needed for the first time challenge validation after the upgrade to LE4D v2.0. It is also needed, when you change the configuration and add a new host to the existing list of hostnames.

After the challenge has been validated, you can close port 80 again. It is not needed for certificate renewal.

 


[VMWARE ESXi] – [Errno 28] No space left on device #ibmchampion

I tried to update my ESXi 6.5 server yesterday and ran into this error “[Errno 28] No space left on device”.

Before I tell you, how I solved the issue, I would like to show, how I do my updates.

You can download update files from VMWare, but there is an easier way to keep your ESXi server up to date.

On an ESXi host, you need to activate remote command execution explicitly. This protects your server and prevents that you can log in to the host by using a remote shell. You can enable remote command execution from the direct console or from the vSphere Web Client.

Now you can open a shell using your preferred tool.

The “VMware Front Experience ” blog has a list of recent patches and updates available .

Click on the “image profile” link for the patch, you want apply.

A popup will open.

Execute the commands from the shell. In general, this is a no no-brainer. You apply the patches, and after that, you have to restart the ESXi server. Done!

But today, for some reason, I got this error message “[Errno 28] No space left on device”.

There are several “solutions” that all tell you to delete obsolete log files and the like. But none of this solutions really solve the problem. If you have not changed the log settings, then ESXi rotates logs.

Therefore, it is unlikely that you use all of the available disk space for logging.

Another solution suggested to change the file system. This can be dangerous. If something goes wrong, you will end up in a useless piece of hardware.

But I finally found the root cause. The system swap file has become very large, leaving no space to unpack the patch files.

What can you do? You can move the swap file to another datastore.

By default, the “Datastore” is set to none. This means that the system partition is being used. Change the setting and run the update command again. You can now update your server without any problems.

 


Plain simple introduction to mybatis #ibmchampion

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results.

MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.

MyBatis makes it easier to build better database-oriented applications more quickly and with less code.

SQL server

I am using Microsoft SQL Server 2017 in my example, but you are free to use any other SQL server.

I have Microsoft SQL Server 2017 installed on RHEL 7. The installation and configuration was a matter of just a couple of minutes. Here is what I did.
Configure the repository

sudo curl -o /etc/yum.repos.d/mssql-server.repo https://packages.microsoft.com/config/rhel/7/mssql-server-2017.repo

Install the server

sudo yum install -y mssql-server

Configure the server

sudo /opt/mssql/bin/mssql-conf setup

If you want to use a different port

sudo /opt/mssql/bin/mssql-conf set network.tcpport 1433

Enable and start the service

systemctl enable mssql-server
systemctl status mssql-server

Configure your firewall and reload the settings

sudo firewall-cmd --zone=public --add-port=1433/tcp --permanent
sudo firewall-cmd --reload

To access the server, you can use Microsoft SQl Management Studio, which has about 900 MB, or a more lightweight solution like SQuirrel

Database and Table

For my example, I created a database eknori and a table users. The table is dead simple with only an id, firstname and lastname for a user.

CREATE TABLE [dbo].[users](
	[id] [varchar](50) NOT NULL,
	[firstname] [varchar](max) NULL,
	[lastname] [varchar](max) NOT NULL,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

myBatis

With SQl server running, we can now start to create our project to work with the database.

You can download the source code here.

Since we are communicating with the database, we have to configure the details of the database. config.xml is the file used for the XML-based configuration. By using this file, you can configure various elements.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 
<configuration>
    <typeAliases>
        <typeAlias alias="User" type="de.eknori.sql.model.User"/>        
    </typeAliases>
 
    <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
                <property name="url" value="jdbc:sqlserver://YourServer:1433;databaseName=eknori"/>
                <property name="username" value="sql-user"/>
                <property name="password" value="sql-pwd"/>
            </dataSource>
       </environment>
    </environments>
    
    <mappers>
       <mapper resource="de/eknori/sql/mybatis/mapper/User.xml"/>
    </mappers>
</configuration>

Within the environments element, we configure the environment of the database that we use in our application.

In MyBatis, you can connect to multiple databases by configuring multiple environment elements. To configure the environment, we are provided with two sub tags namely transactionManager and dataSource.

  • JDBC, the application is responsible for the transaction management operations, such as, commit, roll-back, etc…
  • MANAGED, the application server is responsible to manage the connection life cycle. It is generally used with the Web Applications

The dataSource tag is used to configure the connection properties of the database, such as driver-name, url, user-name, and password of the database that we want to connect.

Instead of specifying the absolute class name everywhere, we can use typeAliases, a shorter name for a Java type.
The mapper element is used to configure the location of mapper xml files, which contain the mapped SQL statements. Our file is User.xml.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="User">

    <resultMap id="result" type="User">
        <result property="id" column="id"/>
        <result property="firstname" column="firstname"/>   
        <result property="lastname" column="lastname"/>  
    </resultMap>
 
    <select id="selectAll" resultMap="result">
        SELECT * FROM users;
    </select>
    
 	<select id="selectById" parameterType="int" resultMap="result">
        SELECT * FROM users WHERE id = #{id}
    </select>
    
 	<insert id="insert" parameterType="User">
        INSERT INTO users (id, firstname, lastname) VALUES (#{id},#{firstname}, #{lastname});
    </insert>
    
    <update id="update" parameterType="User">
        UPDATE users
        SET firstname = #{firstname}
        SET lastname = #{lastname}
        WHERE id = #{id} 
  	</update>
 
	<delete id="delete" parameterType="int">
        DELETE from users WHERE id = #{id}
    </delete>
</mapper>

This configuration abstracts almost all of the JDBC code, and reduces the burden of setting of parameters manually and retrieving the results.

We only need some simple methods and POJO to send and receive data to and from the database.

Here is our User object.


package de.eknori.sql.model;

public class User {

	private int		id;
	private String	firstname;
	private String	lastname;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstname;
	}

	public void setFirstName(String name) {
		firstname = name;
	}

	public String getLastname() {
		return lastname;
	}

	public void setLastname(String lastname) {
		this.lastname = lastname;
	}

	@Override
	public String toString() {
		return "id: " + id + " FirstName: " + firstname + " LastName: " + lastname;
	}
}

userDAO contains the methods that use the mapped SQL statements from User.xml


package de.eknori.sql.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import de.eknori.sql.model.User;

public class UserDAO {

	private SqlSessionFactory sqlSessionFactory = null;

	public UserDAO(SqlSessionFactory sqlSessionFactory) {
		this.sqlSessionFactory = sqlSessionFactory;
	}

	public List<User> selectAll() {
		List<User> list = null;
		SqlSession session = sqlSessionFactory.openSession();

		try {
			list = session.selectList("User.selectAll");
		} finally {
			session.close();
		}

		return list;

	}

	public User selectById(int id) {
		User user = null;
		SqlSession session = sqlSessionFactory.openSession();
		try {
			user = session.selectOne("User.selectById", id);

		} finally {
			session.close();
		}
		return user;
	}

	public int insert(User user) {
		int id = -1;
		SqlSession session = sqlSessionFactory.openSession();

		try {
			id = session.insert("User.insert", user);
		} finally {
			session.commit();
			session.close();
		}
		return id;
	}

	public void update(User user) {
		@SuppressWarnings("unused")
		int id = -1;
		SqlSession session = sqlSessionFactory.openSession();

		try {
			id = session.update("User.update", user);

		} finally {
			session.commit();
			session.close();
		}
	}

	public void delete(int id) {

		SqlSession session = sqlSessionFactory.openSession();

		try {
			session.delete("User.delete", id);
		} finally {
			session.commit();
			session.close();
		}
	}
}

Finally we have ConnectionFactory that reads config.xml and establishes a connection to the database.


package de.eknori.sql.mybatis;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class ConnectionFactory {

	private static SqlSessionFactory sqlSessionFactory;

	static {
		try {

			String resource = "de/eknori/sql/mybatis/config.xml";
			Reader reader = Resources.getResourceAsReader(resource);

			if (sqlSessionFactory == null) {
				sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
			}

		} catch (FileNotFoundException fileNotFoundException) {
			fileNotFoundException.printStackTrace();
		} catch (IOException iOException) {
			iOException.printStackTrace();
		}
	}

	public static SqlSessionFactory getSqlSessionFactory() {
		return sqlSessionFactory;
	}
}

And, action!

We are now ready to read and write data from and to the database


package de.eknori.sql;

import java.util.List;

import de.eknori.sql.dao.UserDAO;
import de.eknori.sql.model.User;
import de.eknori.sql.mybatis.ConnectionFactory;

public class Main {

	private static User	user	= null;
	private static int	id		= 999;

	public static void main(String args[]) {

		UserDAO userDAO = new UserDAO(ConnectionFactory.getSqlSessionFactory());

		List<User> users = userDAO.selectAll();
		users.forEach(element -> System.out.println(element));

		userDAO.delete(id);

		user = new User();
		user.setId(id);
		user.setFirstName("Ulrich");
		user.setLastname("Krause");
		userDAO.insert(user);

		user = userDAO.selectById(id);

		System.out.println(user);

	}
}

That is easy, isn’t it?

myBatis has more options to configure your project like Annotations and Dynamic SQL, but I found the configuration via .xml mapper files the easiest one to start with.

If you want to use other SQL servers instead, you only have to configure config.xml accordingly and download and import the correct driver.


[Notes 9.0.1 FP10] – “Script contains errors. Would you like to save it anyway.” #ibmchampion

When editing code in the LS editor, code will become corrupt.

  • Steps to reproduce:
    Create a new application “scripteditor.nsf” in the local data directory.
  • Open the application “View -> Design
  • Navigate to “Code -> Script Libraries” and create a new LotusScript library “test
  • Insert the following code into the Declaration section and save the library. Code is just a sample; you can use any other code as well.
Private Const ERR_OPEN_IDV_DB = 3000
Private Const ERR_OPEN_BCK_DB = 3001
Private Const ERR_MISSING_SERVER_NAME = 3002
Private Const ERR_MISSING_USER_NAME = 3003
Private Const ERR_MISSING_FILE_NAME = 3004
Private Const ERR_MISSING_PWD = 3005
Private Const ERR_DOC_MARKED_INACTIVE = 3006
Private Const ERR_DOC_MARKED_ACTIVE = 3007

Private Const ERR_OPEN_IDV_DB_MSG = "Unable to open IDVault database "
Private Const ERR_OPEN_BCK_DB_MSG = "Unable to open backup database"
Private Const ERR_MISSING_SERVER_NAME_MSG = "ServerName must not be empty"
Private Const ERR_MISSING_USER_NAME_MSG = "UserName must not be empty"
Private Const ERR_MISSING_FILE_NAME_MSG = "IdFilePathName must not be empty"
Private Const ERR_MISSING_PWD_MSG = "Password must not be empty"
Private Const ERR_DOC_MARKED_INACTIVE_MSG = "Document is already marked inactive"
Private Const ERR_DOC_MARKED_ACTIVE_MSG = "Document is already marked active"
  • Click on the “root entry” of the library = test

  • Place the cursor at the end of a line of code and hit the return key

  • Save the library; click the “save” icon or “File -> Save

You will get an error message

Click a couple of time in the code. After a while you will see that codelines start to duplicate and also codelines are truncated.

I was able to recreate the issue with applications that have been created years ago as well as new applications that were created using 901FP10. The error is reproducible by other customers, too.

The issue is NOT reproducible with 901FP9

I have created a PMR# 92011,031,724 with IBM today.

UPDATE: IBM has confirmed the bug. Tracked under SPR# KHLEAWNPZ6 (APAR #LO93728)


Crash: Java_com_ibm_oti_vm_VM_getClassNameImpl+0x63 (java_lang_class.cpp:281, 0x00007FFFFE6C87A3 [jclse7b_29+0x87a3])

After upgrading to Domino 901FP9, the latest jRebel version crashed the server. But not all server with this combination of software releases. With FP10 it got worse; now the crash was reproducible on all servers.

I created a PMR with IBM and the issue is tracked under SPR# OSAMAVRQKN

The above crash is easily recreatable locally without Domino or jRebel involved with JAVA 8 release.

On Windows:

  • set JREDIR= …
  •  %JREDIR%\bin\java -Dcom.ibm.oti.vm.bootstrap.library.path=%JREDIR%\bin\default;%JREDIR%\bin -version

On Linux:

  • export JREDIR= …
  • $JREDIR/bin/java -Dcom.ibm.oti.vm.bootstrap.library.path=$JREDIR/lib/amd64/default:$JREDIR/lib/amd64 -version

I did further testing with various java releases.

The first non-working level reproducing this crash for each java release is given below:

  • JAVA 6/26 SR5
  • JAVA 7 SR4
  • JAVA 7.1 GA
  • JAVA 8 GA

Above test results indicate that crash doesn’t happen at JAVA 6/26 SR4 or below. Crash doesn’t occur at JAVA 7 SR3 or below. JAVA 7.1 and JAVA 8 releases reproduce this crash from General Availability onwards.

Complete JAVA 6 release doesn’t reproduce this crash and runs fine including the latest build.

Today I got a response from the J9VM support team.

In old versions of JDK, by default we are using nocompressedrefs (meaning -Xnocompressedrefs). So jre/lib/amd64/default is the one where we find all the JVM native libraries.

In newer versions of JDK, by  default we are using compressedrefs (meaning -Xcompressedrefs). So jre/lib/amd64/compressedrefs is the one where we find all the JVM native libraries.

Therefore it would cause native library mis-match if you use -Dcom.ibm.oti.vm.bootstrap.library.path=jre/lib/amd64/default for -Xcompressedrefs.

The following commands will fail (becaue it would have native library mis-match):

  • <Path>/jre/bin/java -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/default   -version
  • <Path>/jre/bin/java -Xcompressedrefs -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/default -version
  • <Path>/jre/bin/java -Xnocompressedrefs  -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/compressedrefs -version

The following commands will be OK (becaue it would not have native  library mis-match):

  • <Path>/jre/bin/java -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/compressedrefs -version
  • <Path>/jre/bin/java -Xcompressedrefs -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/compressedrefs -version
  • <Path>/jre/bin/java -Xnocompressedrefs  -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/default -version

 


Electron – Cross Platform Desktop Apps Made Easy (Part 6) #ibmchampion

Following part 1 – 5 of the Electron tutorial you’re probably ready with the design, preparation and debugging of your app.

So you are ready to create your first release. You have no clue, how to do that? No worries!

In this article you are going to learn how to create a release of your Electron app for different platforms (Windows, Mac, Linux) and in all architectures (x32, x64) using the electron packager module.

Lets prepare our projects folder first. For this article, I copied the part5 folder and renamed it to part6. Also change the ‘name‘ parameter in package.json to part6

 

{
  "name": "part6",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "electron ."
  },
  "author": "Ulrich Krause",
  "license": "MIT",
  "devDependencies": {
    "electron": "^1.8.2"
  }
} 

Electron Packager is a command line tool and Node.js library that bundles Electron-based application source code with a renamed Electron executable and supporting files into folders ready for distribution. Note that packaged Electron applications can be relatively large (40-60 MB).

Electron Packager is known to run on the following host platforms:

  • Windows (32/64 bit)
  • OS X
  • Linux (x86/x86_64)

It generates executables/bundles for the following target platforms:

  • Windows (also known as win32, for both 32/64 bit)
  • OS X (also known as darwin) / Mac App Store (also known as mas)
    Note for OS X / MAS target bundles: the .app bundle can only be signed when building on a host OS X platform.
  • Linux (for x86, x86_64, and armv7l architectures)

In order to build and package your app, you need to install electron-packager first. You can install it globally or as a dev dependency. We will install electron-packager globally with

npm install electron-packager -g

On OS X, you’ll need root access!

To build an application for a platform you’ll need to execute the following command in terminal / command prompt.

electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]

To build a package from the current directory and for the current platform execute.

electron-packager .

electron-packager will do the following:

  • Use the current directory for the sourcedir
  • Infer the appname from the productName in package.json
  • Infer the appVersion from the version in package.json
  • Infer the platform and arch from the host, in this example, darwin platform and x64 arch.
  • Download the Windows x64 build of Electron (and cache the downloads in ~/.electron)
  • Build the Windows part6.app
  • Place part6.app in part6/part6-win32-x64/ (since an out directory was not specified, it used the current working directory)

To build packages for all hosts and platforms, you can execute

electron-packager . --all

Please keep in mind that’s recommendable to build every platform on it’s respective platform i.e build the Windows version of your app in a Desktop with Windows as operative system.
Although for some platforms is possible to build for other platforms i.e you can build the Linux and Windows versions in a Windows computer, you’ll be unable to create a Mac application in a Windows platform, therefore you need to build it in a Mac environment.

Creating distributables like installers

Now lets create an installer for the application. I am doing this on a Mac, but it also works on any other supported platform.
On my Mac, I have electron-packager installed as a dev dependency ( I do not have root access to the machine 🙁 )

npm install electron-packager --save-dev

Also, we need to install electron-builder. Execute

npm install electron-builder --save-dev

In package.json, electron-packager and electron-builder will be added during installation. ( lines 39, 40 )

Create a new script in the scripts section of package.json. ( line 7 ) and also add lines 12 – 36. package.json should now look like this

{
  "name": "part6",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "dist": "build",
    "start": "electron ."
  },
  "author": "Ulrich Krause",
  "license": "MIT",
  "build": {
    "dmg": {
       "contents": [
        {
          "x": 110,
          "y": 150
        },
        {
          "x": 240,
          "y": 150,
          "type": "link",
          "path": "/Applications"
        }
      ]
    },
    "linux": {
      "target": [
        "AppImage",
        "deb"
      ]
    },
    "win": {
      "icon": "build/icon.ico"
    }
  },
  "devDependencies": {
    "electron": "^1.8.2",
    "electron-builder": "^20.0.6",
    "electron-packager": "^11.0.1"
  }
}

If you do not use the source for this article make sure that you at least copy the build folder to your project folder. It contains the icons that are used during the build process. You will get an error about missing icons if the icons are not present.

Next, we can start the build with

npm run dist

You should see similar output in your terminal

midpoints-macbook-ulrich:part6 ulrich$ npm run dist

> part6@1.0.0 dist /Volumes/DATA/Temp/part6
> build

electron-builder version=20.0.6
loaded configuration file=package.json (“build” field)
description is missed in the package.json appPackageFile=/Volumes/DATA/Temp/part6/package.json
writing effective config file=dist/electron-builder.yaml
no native production dependencies
packaging platform=darwin arch=x64 electron=1.8.2 appOutDir=dist/mac
skipped macOS application code signing reason=cannot find valid “Developer ID Application” identity …
building target=macOS zip arch=x64 file=dist/part6-1.0.0-mac.zip
building target=DMG arch=x64 file=dist/part6-1.0.0.dmg
building block map blockMapFile=dist/part6-1.0.0.dmg.blockmap
midpoints-macbook-ulrich:part6 ulrich$

You can ignore the warning about code signing for the moment.

electron builder now has created a DMG file. You can now install part6-1.0.0.dmg on your Mac.


(screenshot shows “part5”, I know 🙂 )

If you want to use a different icon for your application, replace build/icon.icns with your own. The filename MUST be icon.icns!

We only have scratched the surface; electron-builder has more options. For a complete list, read

Also, I found electron-forge. Electron Forge isa complete tool for building modern Electron applications. It unifies the existing (and well maintained) build tools for Electron development into a simple, easy to use package so that anyone can jump right in to Electron development.

That’s it for today.


Electron – Cross Platform Desktop Apps Made Easy (Part 5) #ibmchampion

Today, I want to show, how we can use Electron’s Notification Api for Windows, Linux and macOS. All three operating systems provide means for applications to send notifications to the user.

Electron conveniently allows developers to send notifications with the HTML5 Notification API, using the currently running operating system’s native notification APIs to display it.

Lets write some code. From the past 4 parts of this tutorial you already know, that we start with a new, empty project. So, create a new folder in your projects folder, run npm init and npm install electron --save-dev to create package.json and add Electron as a dependency. Also add 2 new files; app.js and main.html.

Here is, how your project should look like.

{
  "name": "part5",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "electron ."
  },
  "author": "Ulrich Krause",
  "license": "MIT",
  "devDependencies": {
    "electron": "^1.8.2"
  }
}
const {
    app,
    BrowserWindow
} = require('electron')

// Keep a global reference of the window object, if you 
// don't, the window will be closed automatically when 
// the JavaScript object is garbage collected.
let win

function createWindow() {
    win = new BrowserWindow({
        width: 800,
        height: 600
    })
    win.loadURL('file://' + __dirname + '/main.html')
}

app.on('ready', createWindow)
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Desktop Notification</title>
</head>

<body>
    <h1>Desktop Notification</h1>
</body>

</html>

Avoid platform-dependent code

Before we start with our notification, here are some things, you need to know, when creating Electron applications for different platforms. There are some platform specific elements that you already might know from other programming languages.
Temp dir, path delimiter, just to name a few.

According to Electron’s ‘Coding Style

Avoid platform-dependent code:

  1. Use path.join() to concatenate filenames.
  2. Use os.tmpdir() rather than /tmp when you need to reference the temporary directory.

This being said, we will now make some modifications to app.js. We add some core Node.js modules ( lines 2, 3 ) to handle path and URL. Then we use methods from those modules to build the URL for main.html in line 23

// add core Node.js modules
const url = require('url')
const path = require('path')

const {
    app,
    BrowserWindow
} = require('electron')

// Keep a global reference of the window object, if you 
// don't, the window will be closed automatically when 
// the JavaScript object is garbage collected.
let win

function createWindow() {
    win = new BrowserWindow({
        width: 640,
        height: 480
    })
    
    // use url and path to make loading of main.html
    // platform independend
    win.loadURL(url.format({
        pathname: path.join(__dirname, 'main.html'),
        protocol: 'file:',
        slashes: true
    }))
}

app.on('ready', createWindow)

Next, we will add code to handle window and application closing, because macOS behaves a little bit different than Windows or Linux. Read the code comments for explaination.

// add core Node.js modules
const url = require('url')
const path = require('path')

const {
    app,
    BrowserWindow
} = require('electron')

// Keep a global reference of the window object, if you 
// don't, the window will be closed automatically when 
// the JavaScript object is garbage collected.
let win

function createWindow() {
    win = new BrowserWindow({
        width: 640,
        height: 480
    })

    // use url and path to make loading of main.html
    // platform independend
    win.loadURL(url.format({
        pathname: path.join(__dirname, 'main.html'),
        protocol: 'file:',
        slashes: true
    }))

    // Emitted when the window is closed.
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    win.on('closed', function () {
        win = null
    })
}

app.on('ready', createWindow)

// Quit when all windows are closed.
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
app.on('window-all-closed', function () {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
app.on('activate', function () {
    if (mainWindow === null) {
        createWindow()
    }
})

With these modifications, you now have a boilerplate that you can use when creating new Electron applications. You can also use electron-quick-start from GitHub which is similar.

Notifications

HINT: if you are working on Windows, the following sample will not work with all versions. It should work on Win 7 & 8, but might fail on Windows 10. Especially, if you have ‘Windows 10 fall creators update’ installed. This is a known issue. Notifications on Windows only work, if the application is packaged and installed on the target platform. Packaging and Installation is planned to be a topic for an upcoming article. If you are on Linux or macOS, you can keep on reading.

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Desktop Notification</title>
</head>

<body>
    <h1>Desktop Notification</h1>
    <button type="button" name="button" onclick="doNotify()">Notify me</button>
    <script src="main.js"></script>
</body>

</html>

We add a button to our main.html ( line 11 ) that will call doNotify() when clicked and also add a script tag ( line 12 ). We then create a new main.js file in our project folder. main.js will contain the doNotify() function.

function doNotify() {
    const notification = {
        title: 'Basic Notification',
        body: 'This is an Electron notification'
    }
    const myNotification = new window.Notification(notification.title, notification)
}

I have created this example on a Windows 10 machine. As it is not showing the notification there, I moved my project folder to my Mac, and I get

And when I click the ‘Notify me‘ button, the notification appears.

This is just a simple example. For more information see https://electronjs.org/docs/api/notification.

You can download the source code for this part of the tutorial here.


Electron – Cross Platform Desktop Apps Made Easy (Part 4) #ibmchampion

Today, I want to show how you can use a system file dialog in your Electron application to select a file in the filesystem and display its content.

As always, we will create a new application. Create a new folder in your projects directory, change into the folder and from a terminal window initialize the application with npm init. Then execute npm install electron --save-dev to install electron and add it as a dependency to package.json.

Don’t forget to add a start script to package.json. This will let you start your application from the command line by simply running the npm start command.

Your package.json should look similar like this.

{
  "name": "part4",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "electron ."
  },
  "author": "Ulrich Krause",
  "license": "MIT",
  "devDependencies": {
    "electron": "^1.8.2"
  }
}

Next create a new file, app.js. That is the main file of our application.

On application start, we want to open a system file dialog when the main.html has been loaded. We can then browse and select a file in the filesystem. When the OK button is clicked, the application will load the file and display its content.

const {app, BrowserWindow, ipcMain} = require('electron') 
const url = require('url') 
const path = require('path') 

let win  

function createWindow() { 
   win = new BrowserWindow({width: 800, height: 600}) 
   win.loadURL('file://' + __dirname + '/main.html') 
}  

ipcMain.on('openFile', (event, path) => { 
   const {dialog} = require('electron') 
   const fs = require('fs') 
   dialog.showOpenDialog(function (fileNames) { 

      if(fileNames === undefined) { 
         console.log("No file selected");    
      } else { 
         readFile(fileNames[0]); 
      } 
   });
   
   function readFile(filepath) { 
      fs.readFile(filepath, 'utf-8', (err, data) => { 
         
         if(err){ 
            alert("An error ocurred reading the file :" + err.message) 
            return 
         } 
         
         // handle the file content 
         event.sender.send('fileData', data) 
      }) 
   } 
})  
app.on('ready', createWindow)

Have you noticed the let win in line 9? We have not used it before. But it is an important detail in an Electron app. Without this line your win object ( the main window of your application ) would be destroyed as soon as the garbage collection recyles the object, and your application will die. If you use let win, you are save.

When the main process loads main.html, the ipcRenderer on main.htmll sends the ‘openFile‘ message to the main process ( line 11 in main.html ).
In line 12 of app.js, this message is catched and the file dialog opens. You can then browse and select a file. Once you have confirmed your selection, the content of the file is being read into data and then send to the renderer in line 33 along with the ‘fileData‘ message.

<!DOCTYPE html> 
<html> 
   <head> 
      <meta charset = "UTF-8"> 
      <title>File read using system dialogs</title> 
   </head> 
   
   <body> 
      <script type = "text/javascript"> 
         const {ipcRenderer} = require('electron') 
         ipcRenderer.send('openFile', () => { 
         }) 
         
         ipcRenderer.on('fileData', (event, data) => { 
            document.write(data) 
         }) 
      </script> 
   </body> 
</html>

When the renderer receives the ‘fileData‘ message in line 14, it will write the content of data to the document.

This is only a simple example, but I think, that you got the idea of how to use system dialogs in your Electron application. Try to create a button, that opens the dialog on click. It is not that difficult 🙂

you can download the source code for this part of the tutorial here.


Electron – Cross Platform Desktop Apps Made Easy (Q & A) #ibmchampion

I got some very good feedback on the first 3 articles. So, thank you very much for kind words, critics and suggestions! Much appreciated.

Also, I got a couple of questions. Instead of answering the questions offline, I thought it is a good idea to add a Q&A article to the tutorial. I will add more content as questions are coming in.

I am NOT an expert in Javascript, Node.js or Electron. I will answer the questions as best I can. Feel free to add comments.

Q: I have never worked with Node.js and npm. What the heck is it?

Node.js is an open source, cross-platform runtime environment for developing server-side and networking applications. Node.js applications are written in JavaScript, and can be run within the Node.js runtime on OS X, Microsoft Windows, and Linux.

Node.js also provides a rich library of various JavaScript modules which simplifies the development of web applications using Node.js to a great extent.

Node.js = Runtime Environment + JavaScript Library

NPM is a package manager for Node.js packages, or modules if you like. It can be compared to Linux, where you use apt-get, yum and the like to add or update functions to the core Linux system. In Java, you would add .jar files from the Maven repository to your project to make specific classes and method available without reinventing the wheel. www.npmjs.com hosts thousands of free packages to download and use.

A package in Node.js contains all the files you need for a module. Modules are JavaScript libraries you can include in your project.

The NPM program is installed on your computer when you install Node.js. NPM creates a folder named “node_modules“, where the package will be placed. All packages you install in the future will be placed in this folder.

NPM = online repositories for node.js packages/modules which are searchable on search.nodejs.org

NPM = command line utility to install Node.js packages, do version management and dependency management of Node.js packages.

Q: I am using Visual Studio Code. Do I really need an additional terminal window to launch applications?

No, you don’t need an additional terminal window. Visual Studio Code comes with an integrated terminal.
To activate click on “View -> Integrated Terminal”  or use Strg + Backtick ( Strg + ö when you have a German keyboard layout ) to toggle the integrated terminal on/off.

You can also open an additional instance of the terminal by clicking on the plus sign.

For more details on how to configure the integrated terminal, I recommend reading this article.

Q: Why no semicolons?

I thought it was still best to include them to avoid potential problems.

That’s a common misconception, here is a response to it: http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding

Is there a coding standard for Node.js which explains how semicolons should be used?

Oh yeah, there are a lot of coding standards. And that’s the problem. 🙂
I think I’ll just explain why I moved away from using semicolons. It’s easier to see potential errors this way.

I mean, if you use semicolons, one semicolon missing could go unnoticed. Because your eye is trained to treat them as end of line noise. Here is an example of an error that you could have:

function test() {
  var foo = '123';
  var bar = '456';
  var example = a_long_line_without_ending_semicolon
                                              //   ^^^ 
                                              // this would usually go unnoticed

  (function () {
    console.log(example); 
  })(example);
}

But if you don’t use them, the semicolon at the end of the line never matters.

Semicolons in JavaScript are optional

Here is an article that covers the topic in deep.

Q: Why you sometimes use curly braces with const and sometimes not?

To instantiate an Electron object, you would normally write

const app = require('electron').app
const BrowserWindow = require('electron').BrowserWindow
const ipcMain = require('electron').ipcMain

That is a lot of redundancy in code. A better way is to assign the entire Electron framework to an object und use this object to extract the Electron object that we want.

const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const ipcMain = electron.ipcMain

Slightly better, isn’t it?

Chrome 49 added destructuring assignment to make assigning variables and function parameters much easier and we can just write

const electron = require ('electron');
const {app, BrowserWindow, ipcMain} = electron;

or

const {app, BrowserWindow, ipcMain} = require ('electron');

as the shortest possible. You can use the latter, if you have a final list of objects. The first one is handy, if you want to get access to other Electron objects in your code. You simply reference to electron then.

Q: The menu is also visible in the Preferences page; how can I hide it?

In main.js, menu is build and added to the application in lines 18 and 19.

const {remote, ipcRenderer} = require('electron')
const {Menu} = remote.require('electron')

const template = [
    {
       label: 'Electron',
       submenu: [
        {
            label: 'Preferences',
            click: function() {
                ipcRenderer.send('show-prefs')
            }
        }
       ]
    }
]

var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

The static method Menu.setApplicationMenu(menu) sets menu as the application menu on macOS. On Windows and Linux, the menu will be set as each window’s top menu.

This explains, why the menu is also visible in the Preferences page. How can it be removed?

You can use autoHideMenuBar:true. This will auto hide the menu bar. The downside is that you can still toggle the visibility when the Alt key is pressed.

const electron = require ('electron');
const {app, BrowserWindow, ipcMain} = electron;
app.on('ready', function() {
  var mainWindow = new BrowserWindow({
      width:800,
      height:600
  });
mainWindow.loadURL('file://' + __dirname + '/main.html');
mainWindow.openDevTools();

var prefsWindow = new BrowserWindow({
  width:400,
  height:400,
  show:false,
  autoHideMenuBar:true
});

prefsWindow.loadURL('file://' + __dirname + '/prefs.html');

ipcMain.on('show-prefs', function() {
  prefsWindow.show()
});

ipcMain.on('hide-prefs', function() {
  prefsWindow.hide()
});
})

 


Electron – Cross Platform Desktop Apps Made Easy (Part 3) #ibmchampion

Today, we are going to create a multi window application using Electron. I assume that you already have Node.js installed.

Let us start and create a new folder in our projects directory. ( C:\projects\electron\multi-window ). Then open the folder in the command window.

Next use npm init to create the package.json file and install Electron and save it to our dev dependency with npm install electron --save-dev

Change the package.json so we can start our application using npm start later on.

Your package.json should now look like this.

{
  "name": "multi-window",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "dependencies": {
  "electron": "^1.8.2"
},
  "devDependencies": {},
  "scripts": {
  "start": "electron ."
},
  "author": "",
  "license": "ISC"
}

We can now start creating our first window. Create a new file app.js in the multi-window folder and open it in your preferred text editor.

The sample code below shows the minimal code that is needed to create the main window, give it a defined height and width and load the main.html file from the current directory using the file protocol.

const electron = require ('electron');
const {app, BrowserWindow} = electron;

app.on('ready', function() {
var mainWindow = new BrowserWindow({
   width:800,
   height:600
  })
  mainWindow.loadURL('file://' + __dirname + '/main.html')
})

And here is our simple main.html file

<html>
    <head>
        <title>Multi Window Application
        </title>
    </head>
    <body>       
       <h1>Main</h1>
    </body>
</html>

At this point, we can start our application with npm start. You should get

A useful thing to do when you’re working on an app is to open up the dev tool. We can do this by adding the following line to our app.js code.

mainWindow.openDevTools()

When you now start your application, the dev tools also open at startup.

You can switch the tools off / on from the menu bar  “View -> Toggle Developer Tools” or using a keyboard shortcut command. If you deploy your application in production, comment out the line of code.

Create a custom menu bar

Lets create our own menu bar now. To do that, we add a new script tag to our main.html file.

<html>
    <head>
        <title>Multi Window Application
        </title>
    </head>
    <body>
        <h1>Main</h1>
        <script>require('./main.js')</script>
    </body>
</html>

Now that we are requiring the main.js in our main.html file, lets go ahead and create the main.js file. The file is going to handle all the javasript for our main window.

The first thing we want to do is to create a menu object. We can do that by typing

const {Menu} = require('electron')

which would perfectly fine, if we were in the app.js. app.js is the main process, and since we have loaded the main.js in the browser, we are in the renderer process.

So we can’t just simply require(‘menu’) here, we have to require the menu from the main process. To do that, we create a remote

const {remote} = require('electron')

and using this remote, we are able to call the require function, which will require the menu from the main process

const {Menu} = remote.require('electron')

Just be aware of this whenever you see remote. there are a couple of ways to create a menue. We will use a helper function that is on the Menu class

var menu = Menu.buildFromTemplate()

and you can just pass in a structured javascript object with the menu structure that you want. The code will create a main menu entry and a submenu; we will use the onClick event handler later.

const template = [
  {
   label: 'Electron',
   submenu: [
    {
     label: 'Preferences',
     click: function() {}
    }
   ]
  }
]

Then pass template as an argument to the function and attach the menu to our application.

var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

Lets go ahead and make that click do something. We could require a remote browser window in the onClick event itself, but it is not that efficient. A better way to do it is to manage all your windows in the main process.

Let us create a new window in app.js (lines 12 to 17).
We add a new prefsWindow the same way, we have added our mainWindow. The important thing here is show.false. When the app is ready, the prefsWindow is not yet shown.

const electron = require ('electron');
const {app, BrowserWindow} = electron;

app.on('ready', function() {
var mainWindow = new BrowserWindow({
      width:800,
      height:600
  })
  mainWindow.loadURL('file://' + __dirname + '/main.html')
  mainWindow.openDevTools()

  var prefsWindow = new BrowserWindow({
      width:400,
      height:400,
      show:false
  })
  prefsWindow.loadURL('file://' + __dirname + '/prefs.html')
})

Now we have that prefsWindow that is created but still hidden. And so we need a way to call and show that window when the Preferences menu item is clicked.

InterProcess Communication

To do that we use a thing called IPC ( InterProcess Communication ) which is very similar to remote. It allows us to send call back and forth between the main process and the renderer process.

Our main.js will look like this now

const {remote, ipcRenderer} = require('electron')
const {Menu} = remote.require('electron')

const template = [
    {
       label: 'Electron',
       submenu: [
        {
            label: 'Preferences',
            click: function() {
                ipcRenderer.send('show-prefs')
            }
        }
       ]
    }
]

var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

We have added a new varaiable ipcRenderer and use the ipRenderer to send a message when the menu item is clicked.

This will send a message up to our app, which we can catch in app.js

const electron = require ('electron');
const {app, BrowserWindow, ipcMain} = electron;

app.on('ready', function() {
  var mainWindow = new BrowserWindow({
      width:800,
      height:600
  })
mainWindow.loadURL('file://' + __dirname + '/main.html')
mainWindow.openDevTools()

var prefsWindow = new BrowserWindow({
  width:400,
  height:400,
  show:false
})
prefsWindow.loadURL('file://' + __dirname + '/prefs.html')

ipcMain.on('show-prefs', function() {
  prefsWindow.show()
})
})

When you now start your application and click the “Preferences” menu item, a new window will open.

If you close the prefsWindow clicking the X, you will get an error when you try to open it again using the menu. The X will destroy the prefsWindow object and so it is no longer available.

Next we will send messages back from the prefsWindow to the mainWindow. In our prefs.html we create a new script tag ( lines 7 to 15 ) and by now, we add the script directly instead of loading another .js file.

<html>
  <head>
     <title>Multi Window Application</title>
  </head>
  <body>
  <h1>Preferences</h1>
     <script>
        const {ipcRenderer} = require('electron')
        var button = document.createElement('button')
        button.textContent = 'Hide'
        button.addEventListener('click', function() {
            ipcRenderer.send('hide-prefs') 
        })
        document.body.appendChild(button)
     </script>
  </body>
</html>

add the following (highlighted) snippet to app.js. The code will listen for the hide-prefs message from the prefsWindows and closes the windows.

ipcMain.on('show-prefs', function() {
  prefsWindow.show()
})

ipcMain.on('hide-prefs', function() {
  prefsWindow.hide()
})
})

Now you can close the prefsWindow with a click on the button and reopen it from the menu.

You can download the full code for this part of the tutorial from here.


Electron – Cross Platform Desktop Apps Made Easy (Part 2) #ibmchampion

In part one I explained what Electron is and why we want to use it to build cross-platform applications.

In this article, I will show you the tools needed for development. You will also learn about the architecture of an Electron application. We will then build our first application.

Getting started

If not already done, you need to install Node.js on your machine. As with any programming language, platform, or tool that doesn’t come bundled with Windows, getting up and running with Node.js takes some initial setup before you can start hacking away. In my experience, though Node.js has a far better installation experience on Windows than virtually any other language, platform, or tool that I’ve tried to use – just run the installer, and you’re good to go.

Here’s the abbreviated guide, highlighting the major steps:

  1. Open the official page for Node.js downloads and download Node.js for Windows by clicking the “Windows Installer” option
    Run the downloaded Node.js .msi Installer – including accepting the license, selecting the destination, and authenticating for the install.
    There is also an installer for Mac. You are running Linux? Take a look at the “How to install Node.js on Linux” tutorial.
  2. This requires Administrator privileges, and you may need to authenticate
  3. To ensure Node.js has been installed, run node -v in your terminal – you should get something like v8.9.4
  4. Update your version of npm with npm i -g npm
  5. This requires Administrator privileges, and you may need to authenticate
  6. Congratulations – you’ve now got Node.js installed, and are ready to start building!

To create/edit the source code for your application, use your favorite text editor. I’m going to use Visual Studio Code which is built on… you guessed it… Electron!

Optional, you might want to install Git or any other SCM of your choice.

Electron Application Architecture

To start with Electron development, create a folder on your local machine that holds the project files. I am using c:/projects/electron as the root for my Electron projects.

A simple Electron application has the following structure:

  • index.html
  • main.js
  • package.json
  • render.js

The file structure is similar to the one we use when creating web pages.

  • index.html which is an HTML5 web page serving one big purpose: our canvas
  • main.js creates windows and handles system events. It handles the app’s main processes
  • package.json is the startup script for our app. It will run in the main process and it contains information about our app
  • render.js handles the app’s render processes

You may have a few questions about the main process and render process. What the heck are they and how can I get along with them?
Glad you asked. Hang on to your hat ’cause this may be new territory if you’re coming from browser JavaScript realm!

What is a process?

When you see “process”, think of an operating system level process. It’s an instance of a computer program that is running in the system.

When you start your Electron app and check the Windows Task Manager or Activity Monitor for macOS, you can see the processes associated with your  app.

Each of these processes run in parallel. But the memory and resources allocated for each process are isolated from the others.

Main process

The main process controls the life of the application. It has the full Node.js API built in and it opens dialogs, and creates render processes. It also handles other operating system interactions and starts and quits the app.

By convention, this process is in a file named main.js. But it can have whatever name you’d like.

Render process

The render process is a browser window in your app. Unlike the main process, there can be one to many render processes and each is independent.

Because every render process is separate, a crash in one won’t affect another. This is thanks to Chromium’s multi-process architecture.

If all processes run concurrently and independently, one question remains. “How can they be linked?

For this, there’s an interprocess communication system or IPC. You can use IPC to pass messages between main and render processes. I will explain IPC in an upcoming article.

Too much theory? OK then …

Create a simple Electron application

Create a new folder first-app in your Electron project folder c:/projects/electron.

Open the first-app folder with Visual Studio Code. Also open a new cmd window / terminal.

Next run npm init from the commad window

C:\projects\electron\first-app>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (first-app)
version: (1.0.0)
description: Sample Electron Application
entry point: (index.js) main.js
test command:
git repository:
keywords:
author: Ulrich Krause
license: (ISC) MIT
About to write to C:\projects\electron\first-app\package.json:
{
"name": "first-app",
"version": "1.0.0",
"description": "Sample Electron Application",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Ulrich Krause",
"license": "MIT"
}
Is this ok? (yes)

Just follow the steps and fill in the information that is needed. I only changed the “main” value from index.js to main.js.
If everything looks good, confirm the last question. This will create a package.json file in the first-app folder. The file is also available in VS Code.

Open package.json, remove "test": "echo \"Error: no test specified\" && exit 1" and add "start": "electron ." in the script section.

Your file content should now look like this.

Run npm install --save electron. This will download and install Electron and add it as a dependency to our package.json file.

C:\projects\electron\first-app>npm install --save electron

> electron@1.8.2 postinstall C:\projects\electron\first-app\node_modules\electron
> node install.js

Downloading SHASUMS256.txt
[============================================>] 100.0% of 3.43 kB (3.43 kB/s)
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN first-app@1.0.0 No repository field.

+ electron@1.8.2
added 152 packages in 44.765s

That’s it for now, so lets close the file and lets create our main.js file, which is our main process file.

Here we gonna bring in a couple of things, of course Electron.

const electron = require('electron');

We also wanna bring in a couple of core modules. Bring in the URL module, which is a core node.js module.

const url = require('url');

And then also the path module

const path = require('path');

Next we grab some stuff from Electron. We need the app object and we also need the BrowserWindow object.

const {app, BrowserWindow} = electron;

Next thing we wanna to is to create a variable representing our main window

let win;

Lets work on the main window now

In Electron, what we have to do first is listen for the app to be ready. We do that by saying

// run create window function
app.on('ready', createWindow);

And once the app is ready, we run a function createWindow and this is where we want to create our window

win = new BrowserWindow ({width:800,height:600});

Next thing is to load the HTML file into our browser window. We don’t have the HTML file yet, so lets create it.

That’s all we want to do for the HTML right now. Back to main.js and we’re going to take the win object and call

win.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes:true
}))

This will simply pass whatever the current directory is plus the index.html, using the file protocol into the loadURL method.

Your main.js file should now have the following content

 

Actually, now we can try out our application for the first time.

run npm start from the command line and here we go.

Since we don’t havn’t create our own menu items or anything, we have the default menue, which has File and Edit options as well as View where we can toggle DevTools and stuff.

Congratulations! You have successfully created and run your first Electron application.

In the next part of this tutorial, we will dig deeper into Electron and add some functionallity to our application.


Electron – Cross Platform Desktop Apps Made Easy (Part 1) #ibmchampion

We are all victims of a revolution where building apps and websites becomes easier every single day.
Electron is definitely a part of this revolution. and in case you still don’t know what is Electron and which apps are using it….

In this part one of a series of blog posts, I want to explain the basics of Electron.

So, what exactly is this Electron thing anyway?

Electron is a framework for creating native applications with all the emerging technologies including JavaScript, HTML and CSS. Basically, Electron takes care of the hard parts so that you can focus on the core of the application and revolutionize its design.

Designed as an open-source framework, Electron combines the best web technologies and is a cross-platform – meaning that it is easily compatible with Mac, Windows and Linux.

It comes with automatic updates, native menus and notifications as well as crash reporting, debugging and profiling.

Electron (formerly known as Atom Shell) is an open-source framework created by Cheng Zhao, and now developed by GitHub.

  • On 11 April in 2013, Electron was started as Atom Shell.
  • On 6 May 2014, Atom and Atom Shell became open-source with MIT license.
  • On 17 April 2015, Atom Shell was renamed to Electron.
  • On 11 May 2016, Electron reached version 1.0.
  • On 20 May 2016, Electron allowed submitting packaged apps to the Mac App Store.
  • On 2 August 2016, Windows Store support for Electron apps was added.

Electron is build on three core components.

Chromium. An open-source browser project that aims to build a safer, faster, and more stable way for all users to experience the web. This site contains design documents, architecture overviews, testing information, and more to help you learn to build and work with the Chromium source code.

V8. Google’s open source high-performance JavaScript engine, written in C++ and used in Google Chrome, the open source browser from Google, and in Node.js, among others. It implements ECMAScript as specified in ECMA-262, and runs on Windows 7 or later, macOS 10.5+, and Linux systems that use IA-32, ARM, or MIPS processors. V8 can run standalone, or can be embedded into any C++ application.

Node.js. A JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js’ package ecosystem, npm, is the largest ecosystem of open source libraries in the world.

What are some successful applications built with Electron?

Electron is the main GUI framework behind several notable open-source projects including GitHub’s Atom and Microsoft’s Visual Studio Code source code editors, just to name a few. You can find a verbose list of applications built with Electron here.

Also, IBMs Watson Workspace is available as Electron application since mid 2017.  ( Source: DNUG )

Why would I want to build a desktop application in the first place?

Web application development has come so far. It seems weird, right ?

But it turns out, that actually there are a few reasons, why you want to build desktop applications even in 2018.

Here is a couple of the reasons:

The first one is perhaps your application requirements has a need to run in the background. You don‘t want to rely on your browser being up because your browser might crash,  and if your browser crashes, that background application dies.

The other thing is you might require file system access. The things that make browsers so powerful with web applications and so usable is because of the security model in browsers.
You‘re downloading arbitrary code from the internet and you are executing it on your machine. Browsers have to be extremely sandboxed in order to people will trust them. And as a result of that, things like file system access are things that you are completely locked out.

Perhaps your application requirements also require direct API access to something.
If you download a web application, you cannot tell this application to initiate a connection from my browser to some API over there. You can‘t do that.
So, if you need to do this kind of connection, you have to do it from your local machine. This is why we have i.e a POSTMAN application.

Maybe, your application requires access to your hardware. For example, you have a bluetooth card or want to play with Sphero, or you have a smart-card reader. That kind of stuff, you can‘t do from a browser.
You need access to local API that speak to your local hardware in order that you can make those connections.

But why else would you want to write an application that works on the desktop today?

Perhaps you have a requirement for on premises access. It might not make sense to install a web application with a webserver and stuff if a firewall would stop access.

The other reason is you might require binary protocol access. If you need to make a connection to a MySQL database, you need to make those connections using mysql drivers that compile down to C that you can use as a client library.

And the other thing is that some applications just feel right on the desktop. That is why we (all) have installed Slack app on our machines instead of using the web application.

Another this is GAMES. The desktop is still the predominant place to download and run games.

That is why I think that there is still a place for desktop applications; even in the year 2018.

Why would I want to build a desktop application in Electron?

There are some reasons for that , too

One of the things, electron gives you, is that you only have to learn one framework, and what I mean by that is, you just have to learn electrons API.
It is relatively small. And you can reuse all your JS, HTML and CSS that you‘ve been using for all theses years.

If you are on a MAC, you do not have to learn Cocoa, you do not have to learn the Windows APIs and whatever Linux is using these days for the desktop. You do not have to worry about any of that.

You just use Electron, write your code once and run it on Windows, MAC and Linux.

The other thing is , Electron gives you some amazing tight integration. You can do stuff like activating notifications. You have access to the task switcher. You have access to menues, You have access to global shortcuts. You have access to system level events, so you can detect when your computer is going to sleep, when your computer wakes up or your CPU is going nuts and do something about it.

And finally you get a lot of code reuse with electron.

If your application is a companion to a web application, there is a really good chance that you can take some of the assets that you are using in the frontend and with a little bit of work, transport them over to your electron application.

As a bonus, if your backend is running node.js there is also a really good chance that you can take some of the backend code that is running node.js and transplant it into your Electron application.

You can save a lot of time if you already have that companion web application.

There is actually more.

If you write an application in Electron, there is actually a lot of problems that traditional web developers have already solved over the years and you do not have the need to think about it anymore.

You get Chrome dev tools, for free, when you are start developing an Electron application.

The reason for that is that one of Electrons core components is the Chromium engine.
Your application windows are actually Chromium windows.
And this gives you Chrome dev tools within your application and you can debug your code right inside your application.

And Chrome dev tools is pretty much state-of-the-arte when it comes to debug web applications.

And this one, I think, is also important.

The desktop is a new frontier for developers. Especially web developers. Web developers have traditionally been locked out from the entire part of the desktop application development culture.

We now have the tools to take our skills that we have learned all these years and bring them to a completely new place where we have never been before.

In the next part, you will learn more about the structure of an Electron application. I will show you the parts needed to setup a development environment and how to build your first Electron application.

 


Ytria DatabaseEZ – Get application summary data #ibmchampion

A couple of days ago, I wrote about using domino-jna in a DOTS task to get information from the application summary buffer. Andre Hausberger from Ytria sent me a mail asking, why I am using Notes Peek to create a screenshot for the information I want to retrieve from the application.

My answer was, “I cannot find it in DatabaseEZ or ScanEZ. I can see some of the information, but I want to get access to all properties at once.  This is a good starting point to find out, where IBM stores values for new properties

After a short while, Andre sent me another message. “Set YtriaDbEZDebugInfo=3 in your client notes.ini, and your ready to go.

Set the variable and restart DatabaseEZ. Then select a server to analyse. When you now open the  Grid Manager ( crtl + j ), you can add additional colums to the grid.

The downside is that not all information is available right after adding the columns; you have to do a “Load complete database information”. And ( you can proof me wrong with this ), you can only analyse one server at a time. Not exactly what I wanted to do.

When you set the notes.ini variable, an additional action is added to the Options menu .

For each application on a selected server, the DUMP NSFSEARCH action will create a single document in a Notes application. The database must exist; there is no template for this. Just create a database from a blnk template.

You can store the dump of several servers in the same dump database. The server name is being set as the form name, so you can categorize / group the documents afterwards.

The dump also contains documents for each directory, redirection or other files. You can set a filter on the $type colums to filter only Notes applications ($NOTEFILE).

After you have collected the data, use ScanEZ to analyze the data.

Open the dump database in ScanEZ, select one or all servers in the documents section and click the “Values” button. Then add all columns or only the relevant columns and clik OK

 

You will then get a grid with all information you selected

Set a value filter on the $type column and format the Options columns to show data as hexadecimal.

From there, filtering, sorting, searching, so in a word deep data analysis on some servers is an easy thing to do…

 


IBM / HCL support #ibmchampion

I would like to share my experience with the latest support request that I opened with IBM. The problem is only a small one, and you can work around it easily, but I thought that I should report it.

Not long after I tweeted about the issue, I was just testing on another machine to find out, if the problem is reproducible when I got a reply asking about the exact version of IBM Traveler that I upgraded from.

I replied. Did not know, who was asking. Thought, it was just another Traveler admin. Shortly after, I found out that the issue is not bound to any specific version of Traveler.

A day later, there was another reply to my tweet. I have copied the tweets from my timeline. See yourself.

Isn’t that great? Not only, the problem was reproducible by someone other than me. But also the fact that someone cares about a problem that had not yet officially been reported to IBM.

I found out later that the person replying to my tweets is working for IBM / HCL.

Yesterday, I created a support request. Today, I got a mail from support.

This is one of the best experiences that I ever had with support in all the years. Perhaps, HCL brings a new level of awareness to the whole support process. Maybe, they are looking for potential problems more proactive using modern communication channels.

For me as a customer, it is a good feeling to see that someone is listening ( and cares ).


[LE4D] – HTTP challenge validation fails #ibmchampion

We recently got feedback from customers where the HTTP challenge validation fails with no obvious reason. First of all, this issue is not limited to LE4D only.

Assume, you want Lets Encrypt to issue a certificate for foo.example.com.

You configure LE4D, run the agent and get the message

“HTTP JVM: org.shredzone.acme4j.exception.AcmeException: Failed to pass the challenge for domain foo.example.com, … Giving up.”

This indicates that the Lets Encrypt server cannot read the challenge token in the .well-known/acme-challenge directory.

There are a couple of reasons, why the token cannot be accessed

  • authentication required
  • server not listening on port 80 / 443
  • server is not the server for foo.example.com

In our case, no authentication is required, the server can be reached at port 80; we even could access the challenge token via a browser. So there is no obvious reason, why the validation should fail. But it does.

I contacted Lets Encrypt and the finally found an answer. The problem is with DNS and CAA.

CAA is a type of DNS record that allows site owners to specify which Certificate Authorities (CAs) are allowed to issue certificates containing their domain names. It was standardized in 2013 by RFC 6844 to allow a CA “reduce the risk of unintended certificate mis-issue.”. By default, every public CA is allowed to issue certificates for any domain name in the public DNS, provided they validate control of that domain name. That means that if there’s a bug in any one of the many public CAs’ validation processes, every domain name is potentially affected. CAA provides a way for domain holders to reduce that risk.

Lets Encrypt checks the CAA record prior to validating the challenge, If the check fails, also the validation fails.

How can you test, if you are running into a CAA error?

If you are on Linux, you can use dig to check your domain for CAA records ( use nslookup on Windows )

We checked the CAA record for example.com first

dig caa example.com

<<>> DiG 9.9.4-RedHat-9.9.4-51.el7_4.1 <<>> caa example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5006
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;example.com. IN CAA

;; AUTHORITY SECTION:
example.com. 180 IN SOA ns0.dnsmadeeasy.com. dns.dnsmadeeasy.com. 2008010137 43200 3600 1209600 180

;; Query time: 28 msec
;; SERVER: fd00::ca0e:14ff:fe6a:3932#53(fd00::ca0e:14ff:fe6a:3932)
;; WHEN: Sa Jan 20 18:39:04 EET 2018
;; MSG SIZE rcvd: 95

You can see that the query returns NOERROR. Even if the CAA record for example.com is empty, that is ok.
Lets Encrypt will receive the NOERROR status and next, it will try to get the challenge token.

Now, lets check foo.example.com

dig caa foo.example.com

<<>> DiG 9.9.4-RedHat-9.9.4-51.el7_4.1 <<>> caa foo.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 9535
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;foo.example.com. IN CAA

;; Query time: 2 msec
;; SERVER: fd00::ca0e:14ff:fe6a:3932#53(fd00::ca0e:14ff:fe6a:3932)
;; WHEN: Sa Jan 20 18:44:27 EET 2018
;; MSG SIZE rcvd: 45

Now we get a status of SERVFAIL.

Most often this indicates a failure of DNSSEC validation. If you get a SERVFAIL error, your first step should be to use a DNSSEC debugger like dnsviz.net.
If that doesn’t work, it’s possible that your nameservers generate incorrect signatures only when the response is empty.
And CAA responses are most commonly empty. For instance, PowerDNS had this bug in version 4.0.3 and below.

If you don’t have DNSSEC enabled and get a SERVFAIL, the second most likely reason is that your authoritative nameserver returned NOTIMP, which as described above is an RFC 1035 violation; it should instead return NOERROR with an empty response.
If this is the case, file a bug or a support ticket with your DNS provider.


Explore the hidden parts of an application #ibmchampion

Did you ever ask yourself, where Notes/Domino stores the information about specific application properties like DAOS and NIFNSF and how to get access to this information?

Most of the information about an application is accessible by methods and properties in the NotesDatabase class from LotusScript or Java. But there are also a lot of information that is not accessible. LotusScript / Java has never been enhanced to access this information, and I have a strong doubt that it will be enhanced in future releases.

Let’s find out, where we can find i.e. the number of documents that have a reference to an NLO file in the DAOS repository. In addition, we want to know, if an application is enabled for NIFNSF and what the size of the index is.

This information is stored in the summary information of an application. This area cannot be accessed with LotusScript or Java; you will need C-API. You have no clue about C-API? Well, no worries. I will first show you, where you can find the data. Afterwards, I will show you, how you can access it.

Notes Peek is a good way to start with.

In the screenshot, you see a part of the summary information of an application.

As I said before, this part of an application can only be accessed with C-API.

Karsten Lehmann has published a great Java library that let you use C-API calls from a Notes/Domino Java application. It can also be used with XPages. Aside from a great performance boost, you can benefit from callbacks.
Domino-JNA gives you access to numerous CAPI calls using Java. You do not have to know anything about CAPI.

As part of domino-jna, there is a class called “DirectoryScanner”. DirectoryScanner can be used to scan the content of the data directory of a Notes / Domino installation. It has a couple of parameters that let you configure which directory to start the scan with, which kind of applications ( *.NS?, *NT?, *.BOX ) to scan either in one directory or recursive.

DirectoryScanner returns the summary information for each application. The scan is lightning fast.

serv01 and serv02 host 560 applications. Look at the time it takes to get the scan done. Amazing, isn’t it?

Here is the piece of code that does the magic.

for (Server server : configProvider.getServersList()) {

logger.addLogEntry(strTask+" scanning data directory on server: "+ server.getServerName(),Log.LOG_INFO, true);

DirectoryScanner directoryScanner = new DirectoryScanner(server.getServerName(), "",
EnumSet.of(FileType.ANYNOTEFILE, FileType.RECURSE));

List summaryData = directoryScanner.scan();

// your code here ...
}

directoryScanner.scan() returns the summary information for every application.
You can then write this information to a Notes Database and use it to build your own catalog task for example.

The summary information also contains the values for NIFNSF if configured for an application. This would give you an overview, how many applications are NIFNSF enabled without accessing the server console.
Take a look at the “options” data. Great way to get all options for an application with a small ammount of code. Write some Java code to decipher the options. Not hard to do.

The cool thing about Notes/Domino is that you can write your own enhancements if you are missing a functionallity. domino-jna is a great example for this. Use CAPI calls from Java. No expert knowledge in C/C++ necessary.