2. About Michael Dawson
Loves the web and building software (with Node.js!)
Senior Software Developer @ IBM
IBM Runtime Technologies Node.js Technical Lead
Node.js collaborator and CTC member
Active in LTS, build, benchmarking , api
and post-mortem working groups
Contact me:
michael_dawson@ca.ibm.com
Twitter: @mhdawson1
https://www.linkedin.com/in/michael-dawson-6051282
6. This is a bit better
Teaser: we can do better, but that’s for later
7. • Code available from GitHub and published to npm.
• https://github.com/mhdawson/micro-app-framework
• Configuration
• Authentication
• Encryption (SSL)
• Templates
• Pop-ups
micro-app-framework is born !
12. But some things still bug me
Desktop
– Browser Bar
– Pop-ups
– Having to re-open all those windows
– Having to position the windows
– Remembering URL
Phone
– Single browser with tabs
– UI issues
– Browser Bar
– Pop-ups
– Having to open browser/then tab
– Remembering URL
13. Electron – Desktop solution
electron.atom.io
Build cross platform desktop apps
– With JavaScript, HTML and CSS
Uses Node.js, Chromium and V8 !
Happiness
– No pop-ups
– No browser bar
– Position on startup
– No URL to remember
– Binary package possible
– No URL to remember
16. 'use strict';
var http = require('http');
var https = require('https');
var os = require('os');
var util = require('util');
var path = require('path');
var CryptoJS = require('crypto-js');
var prompt = require('prompt');
// get configuration options
prompt.start();
prompt.get({ properties: { password: { hidden: true } } },
(err, passwordPrompt) => {
var config = require(path.join(__dirname, 'config.json'));
var decryptConfigValue = function(value) {
var passphrase = passwordPrompt.password + passwordPrompt.password;
return CryptoJS.AES.decrypt(value, passphrase).toString(CryptoJS.enc.Utf8);
}
// object used to keep global reference to window objects alive
// until window is closed
var windows = new Object();
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
// for now don't verify the certificates as we know they
// simply the self-signed certificate for our server
app.on('certificate-error', (event, webContents, url, error,
certificate, callback ) => {
event.preventDefault();
callback(true);
});
// OS X specific stuff as recommended in the electron start guide
app.on('window-all-closed', () => {
// When all windows are closed, then quit
if ('darwin' !== process.platform ) {
app.quit();
}
});
function createWindow (appConfig) {
// setup based on configured options
var httpHandler = http;
var urlPrefix = "http://";
if (appConfig.tls === true) {
httpHandler = https;
urlPrefix = "https://";
}
// setup the options for the window that will be created
var windowOptions = appConfig.options;
if (windowOptions === undefined) {
windowOptions = new Object();
}
if (windowOptions.webPreferences === undefined) {
windowOptions.webPreferences = new Object();
}
if (windowOptions.webPreferences.nodeIntegration === undefined) {
// disable Node integration by default as its more secure
// to not allow the application to access the environment
windowOptions.webPreferences.nodeIntegration = false;
}
var extraHeadersString = '';
var extraHeadersObject;
if (appConfig.auth !== undefined) {
// the app must use basic authentication so set up the required
// objects need to add the authentication header to the requests
extraHeadersObject = { 'Authorization': 'Basic ' +
new Buffer(decryptConfigValue(appConfig.auth)).toString('base64') };
extraHeadersString = 'Authorization: ' + extraHeadersObject.Authorization;
}
// first make the request to get the size of the window for the app
var req = httpHandler.request({ 'hostname': appConfig.hostname,
'port': appConfig.port,
path: '/?size',
rejectUnauthorized: false,
headers: extraHeadersObject }, (res) => {
var sizeData = '';
res.on('data', (chunk) => {
sizeData = sizeData + chunk;
});
17. res.on('end', () => {
var sizes = JSON.parse(sizeData);
windowOptions.width = sizes.width;
windowOptions.height = sizes.height + platformHeightAdjust;
var mainWindow = new BrowserWindow(windowOptions);
windows[mainWindow] = mainWindow;
// work around what looks like a bug in respecting the config
if (windowOptions.resizable !== undefined) {
mainWindow.setResizable(windowOptions.resizable);
}
// we want minimal window without the menus
mainWindow.setMenu(null);
// ok all set up open the window now
mainWindow.loadURL(urlPrefix + appConfig.hostname + ':' +
appConfig.port + '?windowopen=y',
{ extraHeaders: extraHeadersString });
// clean up
mainWindow.on('closed', () => {
windows[mainWindow] = null;
});
app.on('ready', () => { createWindow(appConfig) });
// os specific stuff recommended by electron quickstart
app.on('activate', () => {
if (null === mainWindow) {
createWindow(appConfig);
};
});
});
});
req.end();
};
// launch all of the configured applications
for (var i = 0; i < config.apps.length; i++) {
createWindow(config.apps[i]);
}
});
18. Cordova – Mobile solution
https://cordova.apache.org/
Uses Node.js !
Build cross platform mobile apps
– With JavaScript, HTML and CSS
Happiness
– Better UI experience
– apk (and equivalent for ios)
– No URL to remember
– No browser bar
– No pop-ups
– No URL to remember
21. const BUTTON_ROW_SIZE = 50;
const FRAME_ADJUST = 10;
var currentApp;
function showApp(event) {
for (var i = 0; i < event.data.config.apps.length; i++) {
if (event.data.showId !== i) {
$('#frame' + i).hide();
$('#framebutton' + i).show();
} else {
$('#frame' + i).show();
$('#framebutton' + i).hide();
currentApp = i;
}
}
}
function showNext() {
var nextApp = currentApp + 1;
if (nextApp >= config.apps.length) {
nextApp = 0;
}
showApp({ data: {config: config, showId: nextApp}});
}
function showPrevious() {
var nextApp = currentApp - 1;
if (nextApp < 0 ) {
nextApp = config.apps.length - 1;
}
showApp({ data: {config: config, showId: nextApp}});
}
var decryptConfigValue = function(value, pass) {
var passphrase = pass + pass;
return CryptoJS.AES.decrypt(value, passphrase).toString(CryptoJS.enc.Utf8);
}
Index.js
22. var config;
function readConfig(launchApps) {
window.resolveLocalFileSystemURL(cordova.file.applicationDirectory + "www/config.json", function(configFile) {
configFile.file(function(theFile) {
var fileReader = new FileReader();
fileReader.onloadend = function(event) {
try {
// parsing directly with JSON.parse resulted in errors, this works
config = eval("(" + event.target.result + ")");
} catch (e) {
alert('Bad configuration file:' + e.message);
throw (e);
}
launchApps();
}
fileReader.readAsText(theFile);
}, function() {
alert('Cannot read configuration file');
});
}, function(err) {
alert('Configuration file does not exist');
});
}
var authNeeded = false;
function readConfigAndAuth(launchApps) {
readConfig(function() {
// first check if there is a need to authenticate
for (var i = 0; i < config.apps.length; i++) {
if (config.apps[i].auth != undefined) {
authNeeded = true;
}
}
if (authNeeded) {
$("#buttons").html('<tr height=' + BUTTON_ROW_SIZE + 'px"><td>Password:' +
'<input id="authpassword" type="password"></input>' +
'<button id="authbutton">go</button></td></tr>');
$('#authbutton').click(launchApps);
} else {
launchApps();
}
});
}
23. var startHeight;
var startWidth;
var app = {
// constructor
initialize: function() {
this.bindEvents();
},
bindEvents: function() {
document.addEventListener('deviceready', this.start, false);
},
// load and run the micro-apps
start: function() {
startHeight = window.innerHeight;
startWidth = window.innerWidth;
readConfigAndAuth(function() {
try {
var pass;
if (authNeeded) {
pass = $('#authpassword').val();
}
var viewport= document.querySelector('meta[name="viewport"]');
window.resizeTo(startWidth, startHeight);
viewport.content = 'width=device-width minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0'
// create the frames for the applications
var frames = new Array();
for (var i = 0; i < config.apps.length; i++) {
frames[i] = '<table id="frame' + i + '"></table>';
}
$("#frames").hide();
$("#frames").html('<tr><td>' + frames.join('n') + '</td></tr>');
$("#frames").height(startHeight - BUTTON_ROW_SIZE - FRAME_ADJUST*2);
$("#frames").width(startWidth - FRAME_ADJUST);
$("#frames").show();
// basic setup of the frames
for (var i = 0; i < config.apps.length; i++) {
var frameId = '#frame' + i;
$(frameId).hide();
$(frameId).height(startHeight - BUTTON_ROW_SIZE - FRAME_ADJUST*2);
$(frameId).width(startWidth - FRAME_ADJUST);
// enable swipe to move through the configured apps
$(frameId).bind('swipeleft', showNext);
$(frameId).bind('swiperight', showPrevious);
}
24. // ok now fill in the content for the frames
var frameButtons = new Array();
for (var i = 0; i < config.apps.length; i++) {
var method = 'http://';
if (config.apps[i].tls) {
method = 'https://';
};
if (config.apps[i].auth !== undefined) {
method = method + decryptConfigValue(config.apps[i].auth, pass) + '@';
};
var frameId = '#frame' + i;
var content = '<tr><td><iframe height="100%" width="100%" src="' +
method +
config.apps[i].hostname +
':' +
config.apps[i].port +
'?windowopen=y' +
'" frameborder="0" scrolling="yes"></iframe></td></tr>';
$(frameId).html(content);
frameButtons[i] = '<td><button id="framebutton' + i + '" type="button">' + config.apps[i].name + '</button></td>';
}
// setup the buttons
$("#buttons").html('<tr height=' + BUTTON_ROW_SIZE + 'px">' + frameButtons.join('') + '</tr>');
for (var i = 0; i < config.apps.length; i++) {
$('#framebutton' + i).click({config: config, showId: i}, showApp);
}
// enable swipe to move through the configured apps
$('#buttons').bind('swipeleft', showNext);
$('#buttons').bind('swiperight', showPrevious);
showApp({ data: {config: config, showId: 0}});
} catch (e) {
alert('Failed to start micro-app-launcher ' + e.message);
throw (e);
}
});
}
};
app.initialize();
25. Where to deploy micro-app server ?
To the Cloud of course
http://www.ibm.com/cloud-computing/bluemix/
Lots of add on services to
– Watson
– Twillio (sms)
– Database
– Any many many more….