SlideShare una empresa de Scribd logo
1 de 26
Descargar para leer sin conexión
Micro-apps with Node.js,
browsers, phones
(Cordova) and electron"
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
Motivation – Device like GUI
IoT CTI
Small
Apps
Solution - Node.js !
 Single page application(SPA)
 Server written in Node.js
 Presentation in Browser
 Remotely Accessible
 Deploy to Cloud
Well this “Ok”
This is a bit better
Teaser: we can do better, but that’s for later
• 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 !
micro-app-framework - components
<TITLE>
<PAGE_WIDTH>
<PAGE_HEIGHT>
Configuration
• serverPort
• title
• scrollBars
• tls
• authenticate
• authinfo
Methods
• getDefaults()
• getTemplateReplacements()
• startServer(server)
• handleSupportingPages(request, response)
Files
• server.js
• page.html.template
• config.json
• package.json
<html>
<head>
<script src="/socket.io/socket.io.js"></script>
<title><TITLE></title>
</head>
<body style="overflow-x:hidden;overflow-y:hidden;">
<script>
var socket = new io.connect('<URL_TYPE>://' +
window.location.host);
socket.on('data', function(data) {
var parts = data.split(":");
var topic = parts[0];
var value = parts[1];
var targetTD = document.getElementById(topic);
if (null != targetTD) {
targetTD.innerHTML=value;
}
})
</script>
<table BORDER="10" width="100%" style="font-size:25px">
<tbody>
<DASHBOARD_ENTRIES>
</tbody>
</table>
</body>
</html>
{
"title": “Cottage Data",
"serverPort": 3000,
"mqttServerUrl": "<add your mqtt server here>",
"dashboardEntries": [ {"name": "Inside temp", "topic": "house/temp2"},
{"name": "Outside temp", "topic": "house/lacrossTX141/20/temp"},
{"name": "Timestamp", "topic": "house/time"} ]
}
config.json
page.html.template
var fs = require('fs');
var mqtt = require('mqtt');
var socketio = require('socket.io');
const BORDERS = 55;
const HEIGHT_PER_ENTRY = 34;
const PAGE_WIDTH = 320;
var eventSocket = null;
var latestData = {};
var Server = function() {
}
Server.getDefaults = function() {
return { 'title': 'House Data' };
}
var replacements;
Server.getTemplateReplacments = function() {
if (replacements === undefined) {
var config = Server.config;
var height = BORDERS;
var dashBoardEntriesHTML = new Array();
for (i = 0; i < config.dashboardEntries.length; i++) {
dashBoardEntriesHTML[i] = '<tr><td>' + config.dashboardEntries[i].name + ':</td><td id="' +
config.dashboardEntries[i].topic + '">pending</td></tr>';
height = height + HEIGHT_PER_ENTRY;
}
replacements = [{ 'key': '<TITLE>', 'value': Server.config.title },
{ 'key': '<UNIQUE_WINDOW_ID>', 'value': Server.config.title },
{ 'key': '<DASHBOARD_ENTRIES>', 'value': dashBoardEntriesHTML.join("") },
{ 'key': '<PAGE_WIDTH>', 'value': PAGE_WIDTH },
{ 'key': '<PAGE_HEIGHT>', 'value': height }];
}
return replacements;
}
Server.startServer = function(server) {
var topicsArray = new Array();
var config = Server.config;
for (i = 0; i < config.dashboardEntries.length; i++) {
topicsArray.push(config.dashboardEntries[i].topic);
}
var mqttOptions;
if (Server.config.mqttServerUrl.indexOf('mqtts') > -1) {
mqttOptions = { key: fs.readFileSync(path.join(__dirname, 'mqttclient', '/client.key')),
cert: fs.readFileSync(path.join(__dirname, 'mqttclient', '/client.cert')),
ca: fs.readFileSync(path.join(__dirname, 'mqttclient', '/ca.cert')),
checkServerIdentity: function() { return undefined }
}
}
var mqttClient = mqtt.connect(Server.config.mqttServerUrl, mqttOptions);
eventSocket = socketio.listen(server);
eventSocket.on('connection', function(client) {
for (var key in latestData) {
var value = latestData[key];
if (value.trim().indexOf(" ") === -1) {
value = Math.round(value * 100) / 100;
}
eventSocket.to(client.id).emit('data', key + ":" + value);
}
});
mqttClient.on('connect',function() {
for(nextTopic in topicsArray) {
mqttClient.subscribe(topicsArray[nextTopic]);
}
});
mqttClient.on('message', function(topic, message) {
var timestamp = message.toString().split(",")[0];
var parts = message.toString().split(":");
if (1 < parts.length) {
var value = parts[1].trim();
latestData[topic] = value;
if (value.trim().indexOf(" ") === -1) {
value = Math.round(value * 100) / 100;
}
eventSocket.emit('data', topic + ':' + value );
}
});
}
if (require.main === module) {
var path = require('path');
var microAppFramework = require('micro-app-framework');
microAppFramework(path.join(__dirname), Server);
}
server.js
Good enough, create bunch of micro-apps
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
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
micro-app-electron-launcher
 https://github.com/mhdawson/micro-app-electron-launcher
 npm install micro-app-electron-launcher
 vi config.json
 npm start
 Future: create native binary
{ "apps": [
{ "name": "home dashboard",
"hostname": "X.X.X.X",
"port": "8081",
"options": { "x": 3350, "y": 10, "resizable": false }
},
{ "name": "phone",
"hostname": "X.X.X.X",
"port": "8083",
"options": { "x": 15, "y": 1850, "sizable": false }
},
{ "name": "Alert Dashboard",
"hostname": "X.X.X.X",
"port": "8084",
"options": { "x": 3065, "y": 10, "sizable": false }
},
{ "name": "totp",
"tls": true,
"hostname": "X.X.X.X",
"port": "8082",
"auth": "asdkweivnaliwerld8welkasdfiuwerasdkllsdals9=",
"options": { "x": 2920, "y": 10, "sizable": false }
}
]
}
'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;
});
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]);
}
});
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
micro-app-cordova-launcher
 https://github.com/mhdawson/micro-app-cordova-launcher/
 Install android SDK
 npm install -g cordova
 cordova create launcher myorg "Micro App Launcher"
 cordova platform add android
 patch for untrusted domains
 update www directory which project contents
 update domain limitations
 cordova build --release android -> apk
 Sign the application -> signed apk
 Install on phone
{ "apps": [
{ "name": "home",
"hostname": "X.X.X.X",
"port": "8081"
},
{ "name": "cottage",
"hostname": "X.X.X.X",
"port": "8081"
},
{ "name": "phone",
"hostname": "X.X.X.X",
"port": "8083"
},
{ "name": "totp",
"tls": true,
"hostname": "X.X.X.X",
"port": "8082",
"auth": "XXXXXXXXXXXXXX",
"options": { "x": 2920, "y": 10, "sizable": false }
}
]
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' *;script-src * 'unsafe-eval'">
<meta id='theViewport' name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Micro-app Launcher</title>
</head>
<body onresize="doResize()">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="aes.js"></script>
<script type="text/javascript" src="index.js"></script>
<table appWindow cellpadding="0" cellspacing="0">
<tr><td><table cellpadding="0" cellspacing="0" id="buttons"></table></td></tr>
<tr><td><table cellpadding="0" cellspacing="0" id="frames"></table></td></tr>
</table>
</body>
</html>
Index.html
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
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();
}
});
}
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);
}
// 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();
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….
Copyrights and Trademarks
© IBM Corporation 2016. All Rights Reserved
IBM, the IBM logo, ibm.com are trademarks or registered
trademarks of International Business Machines Corp.,
registered in many jurisdictions worldwide. Other product and
service names might be trademarks of IBM or other companies.
A current list of IBM trademarks is available on the Web at
“Copyright and trademark information” at
www.ibm.com/legal/copytrade.shtml
Node.js is an official trademark of Joyent. IBM SDK for Node.js is not formally
related to or endorsed by the official Joyent Node.js open source or
commercial project.
Java, JavaScript and all Java-based trademarks and logos are trademarks or
registered trademarks of Oracle and/or its affiliates.
Apache Cordova is an official trademark of the Apache Software Foundation
npm is a trademark of npm, Inc.

Más contenido relacionado

La actualidad más candente

CiklumJavaSat_15112011:Alex Kruk VMForce
CiklumJavaSat_15112011:Alex Kruk VMForceCiklumJavaSat_15112011:Alex Kruk VMForce
CiklumJavaSat_15112011:Alex Kruk VMForceCiklum Ukraine
 
Authentication
AuthenticationAuthentication
Authenticationsoon
 
Mad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screeningsMad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screeningschicagonewsonlineradio
 
Yearning jQuery
Yearning jQueryYearning jQuery
Yearning jQueryRemy Sharp
 
Google
GoogleGoogle
Googlesoon
 
Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Remy Sharp
 
Rushed to Victory Gardens' stage, An Issue of Blood is more effusion than play
Rushed to Victory Gardens' stage, An Issue of Blood is more effusion than playRushed to Victory Gardens' stage, An Issue of Blood is more effusion than play
Rushed to Victory Gardens' stage, An Issue of Blood is more effusion than playchicagonewsyesterday
 
After max+phonegap
After max+phonegapAfter max+phonegap
After max+phonegapyangdj
 
Parse: A Mobile Backend as a Service (MBaaS)
Parse: A Mobile Backend as a Service (MBaaS)Parse: A Mobile Backend as a Service (MBaaS)
Parse: A Mobile Backend as a Service (MBaaS)Ville Seppänen
 
Deploying
DeployingDeploying
Deployingsoon
 
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...Codemotion
 
Parse cloud code
Parse cloud codeParse cloud code
Parse cloud code維佋 唐
 
HTML5 APIs - Where no man has gone before! - Altran
HTML5 APIs - Where no man has gone before! - AltranHTML5 APIs - Where no man has gone before! - Altran
HTML5 APIs - Where no man has gone before! - AltranRobert Nyman
 
droidQuery: The Android port of jQuery
droidQuery: The Android port of jQuerydroidQuery: The Android port of jQuery
droidQuery: The Android port of jQueryPhDBrown
 
Building Android apps with Parse
Building Android apps with ParseBuilding Android apps with Parse
Building Android apps with ParseDroidConTLV
 
async/await in Swift
async/await in Swiftasync/await in Swift
async/await in SwiftPeter Friese
 
HTML5 & The Open Web - at Nackademin
HTML5 & The Open Web -  at NackademinHTML5 & The Open Web -  at Nackademin
HTML5 & The Open Web - at NackademinRobert Nyman
 

La actualidad más candente (19)

CiklumJavaSat_15112011:Alex Kruk VMForce
CiklumJavaSat_15112011:Alex Kruk VMForceCiklumJavaSat_15112011:Alex Kruk VMForce
CiklumJavaSat_15112011:Alex Kruk VMForce
 
Authentication
AuthenticationAuthentication
Authentication
 
Mad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screeningsMad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screenings
 
Yearning jQuery
Yearning jQueryYearning jQuery
Yearning jQuery
 
Google
GoogleGoogle
Google
 
Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)
 
Rushed to Victory Gardens' stage, An Issue of Blood is more effusion than play
Rushed to Victory Gardens' stage, An Issue of Blood is more effusion than playRushed to Victory Gardens' stage, An Issue of Blood is more effusion than play
Rushed to Victory Gardens' stage, An Issue of Blood is more effusion than play
 
After max+phonegap
After max+phonegapAfter max+phonegap
After max+phonegap
 
Discontinuing Reader Matches
Discontinuing Reader MatchesDiscontinuing Reader Matches
Discontinuing Reader Matches
 
Parse: A Mobile Backend as a Service (MBaaS)
Parse: A Mobile Backend as a Service (MBaaS)Parse: A Mobile Backend as a Service (MBaaS)
Parse: A Mobile Backend as a Service (MBaaS)
 
Intro to Parse
Intro to ParseIntro to Parse
Intro to Parse
 
Deploying
DeployingDeploying
Deploying
 
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK -  Nicola Iarocci - Co...
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
 
Parse cloud code
Parse cloud codeParse cloud code
Parse cloud code
 
HTML5 APIs - Where no man has gone before! - Altran
HTML5 APIs - Where no man has gone before! - AltranHTML5 APIs - Where no man has gone before! - Altran
HTML5 APIs - Where no man has gone before! - Altran
 
droidQuery: The Android port of jQuery
droidQuery: The Android port of jQuerydroidQuery: The Android port of jQuery
droidQuery: The Android port of jQuery
 
Building Android apps with Parse
Building Android apps with ParseBuilding Android apps with Parse
Building Android apps with Parse
 
async/await in Swift
async/await in Swiftasync/await in Swift
async/await in Swift
 
HTML5 & The Open Web - at Nackademin
HTML5 & The Open Web -  at NackademinHTML5 & The Open Web -  at Nackademin
HTML5 & The Open Web - at Nackademin
 

Destacado

Building Cross Platform Apps with Electron
Building Cross Platform Apps with ElectronBuilding Cross Platform Apps with Electron
Building Cross Platform Apps with ElectronChris Ward
 
JavaScript and Desktop Apps - Introduction to Electron
JavaScript and Desktop Apps - Introduction to ElectronJavaScript and Desktop Apps - Introduction to Electron
JavaScript and Desktop Apps - Introduction to ElectronBrainhub
 
Why and How You Should Move from PHP to Node.js
Why and How You Should Move from PHP to Node.jsWhy and How You Should Move from PHP to Node.js
Why and How You Should Move from PHP to Node.jsBrainhub
 
Electron - Build desktop apps using javascript
Electron - Build desktop apps using javascriptElectron - Build desktop apps using javascript
Electron - Build desktop apps using javascriptAustin Ogilvie
 
Electron. Build cross platform desktop apps with web technologies!
Electron. Build cross platform desktop apps with web technologies!Electron. Build cross platform desktop apps with web technologies!
Electron. Build cross platform desktop apps with web technologies!*instinctools
 
[CB16] Electron - Build cross platform desktop XSS, it’s easier than you thin...
[CB16] Electron - Build cross platform desktop XSS, it’s easier than you thin...[CB16] Electron - Build cross platform desktop XSS, it’s easier than you thin...
[CB16] Electron - Build cross platform desktop XSS, it’s easier than you thin...CODE BLUE
 

Destacado (6)

Building Cross Platform Apps with Electron
Building Cross Platform Apps with ElectronBuilding Cross Platform Apps with Electron
Building Cross Platform Apps with Electron
 
JavaScript and Desktop Apps - Introduction to Electron
JavaScript and Desktop Apps - Introduction to ElectronJavaScript and Desktop Apps - Introduction to Electron
JavaScript and Desktop Apps - Introduction to Electron
 
Why and How You Should Move from PHP to Node.js
Why and How You Should Move from PHP to Node.jsWhy and How You Should Move from PHP to Node.js
Why and How You Should Move from PHP to Node.js
 
Electron - Build desktop apps using javascript
Electron - Build desktop apps using javascriptElectron - Build desktop apps using javascript
Electron - Build desktop apps using javascript
 
Electron. Build cross platform desktop apps with web technologies!
Electron. Build cross platform desktop apps with web technologies!Electron. Build cross platform desktop apps with web technologies!
Electron. Build cross platform desktop apps with web technologies!
 
[CB16] Electron - Build cross platform desktop XSS, it’s easier than you thin...
[CB16] Electron - Build cross platform desktop XSS, it’s easier than you thin...[CB16] Electron - Build cross platform desktop XSS, it’s easier than you thin...
[CB16] Electron - Build cross platform desktop XSS, it’s easier than you thin...
 

Similar a Micro app-framework

Sencha Touch - Introduction
Sencha Touch - IntroductionSencha Touch - Introduction
Sencha Touch - IntroductionABC-GROEP.BE
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applicationsTom Croucher
 
Paris js extensions
Paris js extensionsParis js extensions
Paris js extensionserwanl
 
Firefox OS: HTML5 sur les stéroïdes - HTML5mtl - 2014-04-22
Firefox OS: HTML5 sur les stéroïdes - HTML5mtl - 2014-04-22Firefox OS: HTML5 sur les stéroïdes - HTML5mtl - 2014-04-22
Firefox OS: HTML5 sur les stéroïdes - HTML5mtl - 2014-04-22Frédéric Harper
 
[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVC[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVCAlive Kuo
 
Mozilla Web Apps - Super-VanJS
Mozilla Web Apps - Super-VanJSMozilla Web Apps - Super-VanJS
Mozilla Web Apps - Super-VanJSRobert Nyman
 
DevSum'15 : Microsoft Azure and Things
DevSum'15 : Microsoft Azure and ThingsDevSum'15 : Microsoft Azure and Things
DevSum'15 : Microsoft Azure and ThingsThomas Conté
 
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)Igor Bronovskyy
 
Developing your first application using FIWARE
Developing your first application using FIWAREDeveloping your first application using FIWARE
Developing your first application using FIWAREFIWARE
 
Cnam azure 2014 mobile services
Cnam azure 2014   mobile servicesCnam azure 2014   mobile services
Cnam azure 2014 mobile servicesAymeric Weinbach
 
JavaScript APIs - The Web is the Platform
JavaScript APIs - The Web is the PlatformJavaScript APIs - The Web is the Platform
JavaScript APIs - The Web is the PlatformRobert Nyman
 
Full stack development with node and NoSQL - All Things Open - October 2017
Full stack development with node and NoSQL - All Things Open - October 2017Full stack development with node and NoSQL - All Things Open - October 2017
Full stack development with node and NoSQL - All Things Open - October 2017Matthew Groves
 
Full Stack Development with Node.js and NoSQL
Full Stack Development with Node.js and NoSQLFull Stack Development with Node.js and NoSQL
Full Stack Development with Node.js and NoSQLAll Things Open
 
SharePoint Conference 2018 - APIs, APIs everywhere!
SharePoint Conference 2018 - APIs, APIs everywhere!SharePoint Conference 2018 - APIs, APIs everywhere!
SharePoint Conference 2018 - APIs, APIs everywhere!Sébastien Levert
 
20110525[Taipei GTUG] titanium mobile簡介
20110525[Taipei GTUG] titanium mobile簡介20110525[Taipei GTUG] titanium mobile簡介
20110525[Taipei GTUG] titanium mobile簡介Justin Lee
 
jsSaturday - PhoneGap and jQuery Mobile for SharePoint 2013
jsSaturday - PhoneGap and jQuery Mobile for SharePoint 2013jsSaturday - PhoneGap and jQuery Mobile for SharePoint 2013
jsSaturday - PhoneGap and jQuery Mobile for SharePoint 2013Kiril Iliev
 
SharePoint Saturday Belgium 2018 - APIs, APIs everywhere!
SharePoint Saturday Belgium 2018 - APIs, APIs everywhere!SharePoint Saturday Belgium 2018 - APIs, APIs everywhere!
SharePoint Saturday Belgium 2018 - APIs, APIs everywhere!Sébastien Levert
 

Similar a Micro app-framework (20)

Sencha Touch - Introduction
Sencha Touch - IntroductionSencha Touch - Introduction
Sencha Touch - Introduction
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
 
Paris js extensions
Paris js extensionsParis js extensions
Paris js extensions
 
Firefox OS: HTML5 sur les stéroïdes - HTML5mtl - 2014-04-22
Firefox OS: HTML5 sur les stéroïdes - HTML5mtl - 2014-04-22Firefox OS: HTML5 sur les stéroïdes - HTML5mtl - 2014-04-22
Firefox OS: HTML5 sur les stéroïdes - HTML5mtl - 2014-04-22
 
[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVC[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVC
 
Mozilla Web Apps - Super-VanJS
Mozilla Web Apps - Super-VanJSMozilla Web Apps - Super-VanJS
Mozilla Web Apps - Super-VanJS
 
DevSum'15 : Microsoft Azure and Things
DevSum'15 : Microsoft Azure and ThingsDevSum'15 : Microsoft Azure and Things
DevSum'15 : Microsoft Azure and Things
 
Progressive What Apps?
Progressive What Apps?Progressive What Apps?
Progressive What Apps?
 
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
 
Developing your first application using FIWARE
Developing your first application using FIWAREDeveloping your first application using FIWARE
Developing your first application using FIWARE
 
Play!ng with scala
Play!ng with scalaPlay!ng with scala
Play!ng with scala
 
Cnam azure 2014 mobile services
Cnam azure 2014   mobile servicesCnam azure 2014   mobile services
Cnam azure 2014 mobile services
 
JavaScript APIs - The Web is the Platform
JavaScript APIs - The Web is the PlatformJavaScript APIs - The Web is the Platform
JavaScript APIs - The Web is the Platform
 
Full stack development with node and NoSQL - All Things Open - October 2017
Full stack development with node and NoSQL - All Things Open - October 2017Full stack development with node and NoSQL - All Things Open - October 2017
Full stack development with node and NoSQL - All Things Open - October 2017
 
Full Stack Development with Node.js and NoSQL
Full Stack Development with Node.js and NoSQLFull Stack Development with Node.js and NoSQL
Full Stack Development with Node.js and NoSQL
 
SharePoint Conference 2018 - APIs, APIs everywhere!
SharePoint Conference 2018 - APIs, APIs everywhere!SharePoint Conference 2018 - APIs, APIs everywhere!
SharePoint Conference 2018 - APIs, APIs everywhere!
 
20110525[Taipei GTUG] titanium mobile簡介
20110525[Taipei GTUG] titanium mobile簡介20110525[Taipei GTUG] titanium mobile簡介
20110525[Taipei GTUG] titanium mobile簡介
 
What Is Happening At The Edge
What Is Happening At The EdgeWhat Is Happening At The Edge
What Is Happening At The Edge
 
jsSaturday - PhoneGap and jQuery Mobile for SharePoint 2013
jsSaturday - PhoneGap and jQuery Mobile for SharePoint 2013jsSaturday - PhoneGap and jQuery Mobile for SharePoint 2013
jsSaturday - PhoneGap and jQuery Mobile for SharePoint 2013
 
SharePoint Saturday Belgium 2018 - APIs, APIs everywhere!
SharePoint Saturday Belgium 2018 - APIs, APIs everywhere!SharePoint Saturday Belgium 2018 - APIs, APIs everywhere!
SharePoint Saturday Belgium 2018 - APIs, APIs everywhere!
 

Más de Michael Dawson

Index 2018 talk to your code
Index 2018   talk to your codeIndex 2018   talk to your code
Index 2018 talk to your codeMichael Dawson
 
Index 2018 node.js what's next
Index 2018   node.js what's nextIndex 2018   node.js what's next
Index 2018 node.js what's nextMichael Dawson
 
N api - node interactive 2017
N api - node interactive 2017N api - node interactive 2017
N api - node interactive 2017Michael Dawson
 
N api-node summit-2017-final
N api-node summit-2017-finalN api-node summit-2017-final
N api-node summit-2017-finalMichael Dawson
 
Accelerate your digital transformation
Accelerate your digital transformationAccelerate your digital transformation
Accelerate your digital transformationMichael Dawson
 
Node.js Community Benchmarking WG update
Node.js Community  Benchmarking WG updateNode.js Community  Benchmarking WG update
Node.js Community Benchmarking WG updateMichael Dawson
 
A294 fips support in node
A294  fips support in nodeA294  fips support in node
A294 fips support in nodeMichael Dawson
 
A295 nodejs-knowledge-accelerator
A295   nodejs-knowledge-acceleratorA295   nodejs-knowledge-accelerator
A295 nodejs-knowledge-acceleratorMichael Dawson
 
A301 ctu madrid2016-monitoring
A301 ctu madrid2016-monitoringA301 ctu madrid2016-monitoring
A301 ctu madrid2016-monitoringMichael Dawson
 
Post mortem talk - Node Interactive EU
Post mortem talk - Node Interactive EUPost mortem talk - Node Interactive EU
Post mortem talk - Node Interactive EUMichael Dawson
 
Update from-build-workgroup
Update from-build-workgroupUpdate from-build-workgroup
Update from-build-workgroupMichael Dawson
 
Micro app-framework - NodeLive Boston
Micro app-framework - NodeLive BostonMicro app-framework - NodeLive Boston
Micro app-framework - NodeLive BostonMichael Dawson
 
Node liveboston welcome
Node liveboston welcomeNode liveboston welcome
Node liveboston welcomeMichael Dawson
 
Node home automation with Node.js and MQTT
Node home automation with Node.js and MQTTNode home automation with Node.js and MQTT
Node home automation with Node.js and MQTTMichael Dawson
 

Más de Michael Dawson (18)

Index 2018 talk to your code
Index 2018   talk to your codeIndex 2018   talk to your code
Index 2018 talk to your code
 
Index 2018 node.js what's next
Index 2018   node.js what's nextIndex 2018   node.js what's next
Index 2018 node.js what's next
 
N api - node interactive 2017
N api - node interactive 2017N api - node interactive 2017
N api - node interactive 2017
 
N api-node summit-2017-final
N api-node summit-2017-finalN api-node summit-2017-final
N api-node summit-2017-final
 
Accelerate your digital transformation
Accelerate your digital transformationAccelerate your digital transformation
Accelerate your digital transformation
 
Ask us anything v9
Ask us anything v9Ask us anything v9
Ask us anything v9
 
Node.js Community Benchmarking WG update
Node.js Community  Benchmarking WG updateNode.js Community  Benchmarking WG update
Node.js Community Benchmarking WG update
 
Cascon intro
Cascon introCascon intro
Cascon intro
 
A294 fips support in node
A294  fips support in nodeA294  fips support in node
A294 fips support in node
 
A295 nodejs-knowledge-accelerator
A295   nodejs-knowledge-acceleratorA295   nodejs-knowledge-accelerator
A295 nodejs-knowledge-accelerator
 
A301 ctu madrid2016-monitoring
A301 ctu madrid2016-monitoringA301 ctu madrid2016-monitoring
A301 ctu madrid2016-monitoring
 
Post mortem talk - Node Interactive EU
Post mortem talk - Node Interactive EUPost mortem talk - Node Interactive EU
Post mortem talk - Node Interactive EU
 
Update from-build-workgroup
Update from-build-workgroupUpdate from-build-workgroup
Update from-build-workgroup
 
Node fips
Node fipsNode fips
Node fips
 
Micro app-framework - NodeLive Boston
Micro app-framework - NodeLive BostonMicro app-framework - NodeLive Boston
Micro app-framework - NodeLive Boston
 
Node liveboston welcome
Node liveboston welcomeNode liveboston welcome
Node liveboston welcome
 
Node home automation with Node.js and MQTT
Node home automation with Node.js and MQTTNode home automation with Node.js and MQTT
Node home automation with Node.js and MQTT
 
Java one 2015 - v1
Java one   2015 - v1Java one   2015 - v1
Java one 2015 - v1
 

Último

W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT  - Elevating Productivity in Today's Agile EnvironmentHarnessing ChatGPT  - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT - Elevating Productivity in Today's Agile EnvironmentVictorSzoltysek
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension AidPhilip Schwarz
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...masabamasaba
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...masabamasaba
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplatePresentation.STUDIO
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Bert Jan Schrijver
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park masabamasaba
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareJim McKeeth
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...masabamasaba
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...Health
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfonteinmasabamasaba
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnAmarnathKambale
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrandmasabamasaba
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024VictoriaMetrics
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park masabamasaba
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...masabamasaba
 

Último (20)

Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT  - Elevating Productivity in Today's Agile EnvironmentHarnessing ChatGPT  - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK Software
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go Platformless
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
 

Micro app-framework

  • 1. Micro-apps with Node.js, browsers, phones (Cordova) and electron"
  • 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
  • 3. Motivation – Device like GUI IoT CTI Small Apps
  • 4. Solution - Node.js !  Single page application(SPA)  Server written in Node.js  Presentation in Browser  Remotely Accessible  Deploy to Cloud
  • 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 !
  • 8. micro-app-framework - components <TITLE> <PAGE_WIDTH> <PAGE_HEIGHT> Configuration • serverPort • title • scrollBars • tls • authenticate • authinfo Methods • getDefaults() • getTemplateReplacements() • startServer(server) • handleSupportingPages(request, response) Files • server.js • page.html.template • config.json • package.json
  • 9. <html> <head> <script src="/socket.io/socket.io.js"></script> <title><TITLE></title> </head> <body style="overflow-x:hidden;overflow-y:hidden;"> <script> var socket = new io.connect('<URL_TYPE>://' + window.location.host); socket.on('data', function(data) { var parts = data.split(":"); var topic = parts[0]; var value = parts[1]; var targetTD = document.getElementById(topic); if (null != targetTD) { targetTD.innerHTML=value; } }) </script> <table BORDER="10" width="100%" style="font-size:25px"> <tbody> <DASHBOARD_ENTRIES> </tbody> </table> </body> </html> { "title": “Cottage Data", "serverPort": 3000, "mqttServerUrl": "<add your mqtt server here>", "dashboardEntries": [ {"name": "Inside temp", "topic": "house/temp2"}, {"name": "Outside temp", "topic": "house/lacrossTX141/20/temp"}, {"name": "Timestamp", "topic": "house/time"} ] } config.json page.html.template
  • 10. var fs = require('fs'); var mqtt = require('mqtt'); var socketio = require('socket.io'); const BORDERS = 55; const HEIGHT_PER_ENTRY = 34; const PAGE_WIDTH = 320; var eventSocket = null; var latestData = {}; var Server = function() { } Server.getDefaults = function() { return { 'title': 'House Data' }; } var replacements; Server.getTemplateReplacments = function() { if (replacements === undefined) { var config = Server.config; var height = BORDERS; var dashBoardEntriesHTML = new Array(); for (i = 0; i < config.dashboardEntries.length; i++) { dashBoardEntriesHTML[i] = '<tr><td>' + config.dashboardEntries[i].name + ':</td><td id="' + config.dashboardEntries[i].topic + '">pending</td></tr>'; height = height + HEIGHT_PER_ENTRY; } replacements = [{ 'key': '<TITLE>', 'value': Server.config.title }, { 'key': '<UNIQUE_WINDOW_ID>', 'value': Server.config.title }, { 'key': '<DASHBOARD_ENTRIES>', 'value': dashBoardEntriesHTML.join("") }, { 'key': '<PAGE_WIDTH>', 'value': PAGE_WIDTH }, { 'key': '<PAGE_HEIGHT>', 'value': height }]; } return replacements; } Server.startServer = function(server) { var topicsArray = new Array(); var config = Server.config; for (i = 0; i < config.dashboardEntries.length; i++) { topicsArray.push(config.dashboardEntries[i].topic); } var mqttOptions; if (Server.config.mqttServerUrl.indexOf('mqtts') > -1) { mqttOptions = { key: fs.readFileSync(path.join(__dirname, 'mqttclient', '/client.key')), cert: fs.readFileSync(path.join(__dirname, 'mqttclient', '/client.cert')), ca: fs.readFileSync(path.join(__dirname, 'mqttclient', '/ca.cert')), checkServerIdentity: function() { return undefined } } } var mqttClient = mqtt.connect(Server.config.mqttServerUrl, mqttOptions); eventSocket = socketio.listen(server); eventSocket.on('connection', function(client) { for (var key in latestData) { var value = latestData[key]; if (value.trim().indexOf(" ") === -1) { value = Math.round(value * 100) / 100; } eventSocket.to(client.id).emit('data', key + ":" + value); } }); mqttClient.on('connect',function() { for(nextTopic in topicsArray) { mqttClient.subscribe(topicsArray[nextTopic]); } }); mqttClient.on('message', function(topic, message) { var timestamp = message.toString().split(",")[0]; var parts = message.toString().split(":"); if (1 < parts.length) { var value = parts[1].trim(); latestData[topic] = value; if (value.trim().indexOf(" ") === -1) { value = Math.round(value * 100) / 100; } eventSocket.emit('data', topic + ':' + value ); } }); } if (require.main === module) { var path = require('path'); var microAppFramework = require('micro-app-framework'); microAppFramework(path.join(__dirname), Server); } server.js
  • 11. Good enough, create bunch of micro-apps
  • 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
  • 14. micro-app-electron-launcher  https://github.com/mhdawson/micro-app-electron-launcher  npm install micro-app-electron-launcher  vi config.json  npm start  Future: create native binary { "apps": [ { "name": "home dashboard", "hostname": "X.X.X.X", "port": "8081", "options": { "x": 3350, "y": 10, "resizable": false } }, { "name": "phone", "hostname": "X.X.X.X", "port": "8083", "options": { "x": 15, "y": 1850, "sizable": false } }, { "name": "Alert Dashboard", "hostname": "X.X.X.X", "port": "8084", "options": { "x": 3065, "y": 10, "sizable": false } }, { "name": "totp", "tls": true, "hostname": "X.X.X.X", "port": "8082", "auth": "asdkweivnaliwerld8welkasdfiuwerasdkllsdals9=", "options": { "x": 2920, "y": 10, "sizable": false } } ] }
  • 15.
  • 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
  • 19. micro-app-cordova-launcher  https://github.com/mhdawson/micro-app-cordova-launcher/  Install android SDK  npm install -g cordova  cordova create launcher myorg "Micro App Launcher"  cordova platform add android  patch for untrusted domains  update www directory which project contents  update domain limitations  cordova build --release android -> apk  Sign the application -> signed apk  Install on phone { "apps": [ { "name": "home", "hostname": "X.X.X.X", "port": "8081" }, { "name": "cottage", "hostname": "X.X.X.X", "port": "8081" }, { "name": "phone", "hostname": "X.X.X.X", "port": "8083" }, { "name": "totp", "tls": true, "hostname": "X.X.X.X", "port": "8082", "auth": "XXXXXXXXXXXXXX", "options": { "x": 2920, "y": 10, "sizable": false } } ] }
  • 20. <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Security-Policy" content="default-src 'self' *;script-src * 'unsafe-eval'"> <meta id='theViewport' name='viewport' content='width=device-width, initial-scale=1.0'> <title>Micro-app Launcher</title> </head> <body onresize="doResize()"> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" /> <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script> <script type="text/javascript" src="cordova.js"></script> <script type="text/javascript" src="aes.js"></script> <script type="text/javascript" src="index.js"></script> <table appWindow cellpadding="0" cellspacing="0"> <tr><td><table cellpadding="0" cellspacing="0" id="buttons"></table></td></tr> <tr><td><table cellpadding="0" cellspacing="0" id="frames"></table></td></tr> </table> </body> </html> Index.html
  • 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….
  • 26. Copyrights and Trademarks © IBM Corporation 2016. All Rights Reserved IBM, the IBM logo, ibm.com are trademarks or registered trademarks of International Business Machines Corp., registered in many jurisdictions worldwide. Other product and service names might be trademarks of IBM or other companies. A current list of IBM trademarks is available on the Web at “Copyright and trademark information” at www.ibm.com/legal/copytrade.shtml Node.js is an official trademark of Joyent. IBM SDK for Node.js is not formally related to or endorsed by the official Joyent Node.js open source or commercial project. Java, JavaScript and all Java-based trademarks and logos are trademarks or registered trademarks of Oracle and/or its affiliates. Apache Cordova is an official trademark of the Apache Software Foundation npm is a trademark of npm, Inc.