jeudi 31 mai 2018

A PWA that sounds more App than Web

Filed under: G33k,Programmes — Étiquettes : , , — LiBe @ 16:27

A Progressive Web App is a set of rules that comply with the display of a WebApp in a standalone or minimalist view.

Pick up your web application, remove the bar address and the menu (optionally), and try to figure out how the user can interact with your application as if the client was not a browser.

What if the network connection drops ?
What if the screen width is as small as a smartphone ?
What if your user wants to touch the screen instead of clicking ?

These are the questions that lighthouse (chrome > chrome dev tools > Audits) is going to ask you before certifying a PWA (Progressive WebApp)

First of all, think mobile first :
1°) shrink the window resizing
Fix the display bugs that only appear on middle and small size screens.
Either find a workaround or define another layout.

Define the offline experience :
2°) Write a service worker like this one :
(« network or cache » strategy is most obvious one : use cache when possible instead of network)

const cacheName = btoa(self.location.toString())

self.addEventListener('fetch', event => {
const {request: {url}} = event
event.respondWith(
caches.open(cacheName)
.then(cache => cache.match(url, {ignoreSearch: true}))
.then(response => response || fetch(event.request))
)
})
self.addEventListener('install', event =>
event.waitUntil(
caches.open(cacheName).then(cache =>
self.fetch('asset-manifest.json')
.then(http => http.json())
.then(response => cache.addAll(Object.values(response)))
.then(() => cache.add('service-worker.js'))
.then(() => cache.add('index.html')))
.then(() => self.skipWaiting())))

Invoke the service worker from a script as follow :
navigator.serviceWorker.register("service-worker.js",{scope:"/theApplication/"})
Now, you should be able to render your app even without network : (notice the plane mode)
notice the plane mode. Yes, it still works.

Declare the whole as an application :
3°) Write a manifest.json file :

{
"short_name": "Infogare",
"name": "SNCF infogare",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},{
"src": "logo.png",
"sizes": "144x144 192x192 512x512 1024x1024",
"type": "image/png"
}
],
"start_url": "/infogare/",
"display": "standalone",
"theme_color": "#ddc15d",
"background_color": "#0d5da6",
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=org.toilelibre.libe.trains",
"id": "org.toilelibre.libe.trains"
}
]
}

Refer to it using this markup : <link rel= »manifest » href= »/infogare/manifest.json »/>

4°) Bonus step : add an install button :
follow this guide : https://developers.google.com/web/fundamentals/app-install-banners/
This is how it can look like on your app :
Sans titre

Here you are. No need to hire an Android specialist nor an Apple fashionista. Write once, deploy everywhere.


lundi 30 avril 2018

spring-boot : differences in my yaml files

Filed under: G33k,Programmes — Étiquettes : , , — LiBe @ 10:17

During the release process, there is a tricky part that can sometimes ask for production hot fixes :
« Are you sure that all the yaml configuration files are even between the various environments ? »

What are we talking about ?
Discrepancies between configuration files in each environment.
That happens more often than you think, and it is often a time where developers act like robots : compare file1 and file2, … then filek and filek+1, … then filen-1 and file n.

What you would like is a tool to get this comparison automatically made for you.

I have written (a small part of) that tool for you, and it is working-ish.
Please be a beta tester for me.

How to install :

      Install nodejs on your machine
      npm install -g yamlchecker (not mandatory)

How to use :

      Go to your resources directory
      If you installed the tool already :

      • yamlchecker application*.yml

      or else :

      • npx yamlchecker application*.yml

What you get :
2018-04-30_11-11-08

V means ‘overriden value’
X means ‘problem : value is brought by the default profile’

You will only see the keys that are overriden by at least one, but not all profiles.


samedi 31 mars 2018

Properties to JsonNode

Filed under: G33k,Programmes — Étiquettes : , — LiBe @ 07:21

How difficult do you find it to convert properties into a json object ?

It would require a lot of boilerplate work in java, but it is easier in kotlin.

This snippet reads a Map of properties paths and values and converts them into a json node (or a map of maps (of maps…) of values)
it can read dot separators as well as array indexes (a.b[2].c)

object PropertiesToJsonNode {

/*
a.b=1
a.c=3
c.d.e.f=2
c.d.g[0].h=3
c.d.g.1.h=4
=> {"a":{"b":1,"c":3},"c":{"d":{"e":{"f":2},"g":[{"h":3},{"h":4}]}}}

*/
fun from(map: Map<String, Any?>) =
map.split({key, value -> path(normalized(key), value)}).merge().listifyWhenPossible()

private fun Map<String, Any?>.split(splitFunction: (String, Any?) -> Map<String, Any?> ) : Array<Map<String, Any?>> =
this.entries.map { it.key to splitFunction(it.key, it.value) }.toMap().values.toTypedArray()

private fun normalized(key: String): String = key.replace(Regex("\\[([0-9]+)\\]\\."), ".$1.")

private fun Array<Map<String, Any?>>.merge() = this.reduce { acc, map -> mergeTwo(acc, map) }

private fun Map<String, Any?>.listifyWhenPossible() : Map<String, Any?> =
this.map { it.key to maybeTransformedToList(it.value)}.toMap()

@Suppress("UNCHECKED_CAST")
private fun maybeTransformedToList(value: Any?) = when {
value !is Map<*, *> -> value
(value as Map<String, Any?>).filterNot{ it.key.matches(Regex("^[0-9]+$"))}.isEmpty() ->
IntStream.rangeClosed(0, parseInt(value.keys.sortedDescending()[0])).boxed().map {
if(value["$it"] is Map<*, *>) (value["$it"] as Map<String, Any?>).listifyWhenPossible()
else value["$it"] }.toArray()
else -> value.listifyWhenPossible()
}

private fun path(key: String, value: Any?) =
nest(key.split(".").slice(1 until key.split(".").size), key.split(".")[0], value)

private fun nest(allOtherKeys: List<String>, currentKey: String, value: Any?) : Map<String, Any?> =
mapOf(Pair(currentKey, if(allOtherKeys.isEmpty()) value else
nest(allOtherKeys.slice(1 until allOtherKeys.size), allOtherKeys[0], value)))

private fun mergeTwo(first: Map<String, Any?>, second: Map<String, Any?>): Map<String, Any?> {
val presentInFirstOnly = first.keys.minus(second).map { it to first[it] }
val presentInSecondOnly = second.keys.minus(first).map { it to second[it]}
val intersection = first.keys.intersect(second.keys).map { it to mergeOneKey(first, second, it) }

return presentInFirstOnly.union( presentInSecondOnly).union(intersection).map { it.first.toString() to it.second}.toMap()
}

@Suppress("UNCHECKED_CAST")
private fun mergeOneKey(first: Map<String, Any?>, second: Map<String, Any?>, key: String)=
(if (first[key] is Map<*, *> && second[key] !is Map<*, *>) {first[key]} else
{if (first[key] !is Map<*, *> && second[key] is Map<*, *>) second[key] else
{
mergeTwo(first[key] as Map<String, Any>, second[key] as Map<String, Any>)
}})
}

mercredi 28 février 2018

aws lambda in pictures

Filed under: G33k,Programmes — Étiquettes : , — LiBe @ 22:27

A lambda function is a cost saver.
You can save
– some virtual machines (they are managed by the IaaS itself)
– some setup (the API interface is the code itself)
– some maintenance cost (the function will live forever and you control the downtime if you need some)

Do you want to build an API that cost you nothing when not called ?
Write one express-like app in javascript this way :

const doCallback = (data, callback) =>
callback(null, {body: data, isBase64Encoded: false, headers: {'Content-Type': 'application/json'}})

const handler = ({pathParameters, queryStringParameters, myParam}, context, callback) =>
pathParameters && pathParameters.myParam
doCallback(myAction(pathParameters.myParam), callback):
queryStringParameters && queryStringParameters.myParam ?
doCallback(myAction(queryStringParameters.myParam), callback):
typeof myParam === 'string' ?
doCallback(myAction(myParam), callback):
context.succeed(myAction(myParam))

module.exports = {handler}

This polymorphic handler is able to read myParam from several different HTTP verbs.

Use pathParameters in case of GET/POST with path variables
Use queryStringParameters in case of GET + optional queryParameters
Use myParam as request body in a POST call.

I have made a simple lambda function which draws a graph according to a list of coords pair
(example with https://x3b2fqbqf0.execute-api.eu-west-1.amazonaws.com/prod/chartify/0_5_1_7_2_4_3_4_4_4_5_1 : [0,5],[1,7][2,4][3,4][4,4][5,1])

If you want to start from something rather than from scratch and if you don’t need (yet) the serverless framework, take a look at that repository : http://github.com/libetl/chartify

It works standalone, no need to code inside AWS Cloud9, and the build target in the package.json already prepares a zip archive that you can send to the Lambda console.


mercredi 31 janvier 2018

A gtfs server in 5 minutes

Filed under: G33k,Programmes — Étiquettes : , , — LiBe @ 22:37

Want to be able to serve the schedules of any railway / bus company ?

Some providers already exist (starting by google of course) and allow you to do so… with a pricing table and a very low free tier quota.
There are two ways to get rid of this limitation : buy the paid plan (which often comes with pricing by use) or build your own service.

How to make a server from gtfs data ? quite straightforward : read the zip, parse the csv files and normalize it in javascript data (index it with relevant ids).
Then, store that data as a global var and serve it

For the curious folks, here lies the substance : https://gist.github.com/libetl/eedb72b27de3b888c001d0ab14eb3c66#file-readgtfsdumps-js
It also indexes every station by latitude longitude so that a mobile device can recover the name of the nearest station thanks to its gps coordinates.

After that : when a request comes from an express route, extract some data out of it and output it.
How to transform that data into schedules ? Here is one way to do that : https://gist.github.com/libetl/eedb72b27de3b888c001d0ab14eb3c66#file-timetable-js
No find, only one filter operation in a small dataset : you guessed it, the operation will be really fast.

I have tried myself for a gtfs object that contains 3 different gtfs dumps in France.
It never takes more that 40 ms to achieve.

You can see my working example on a free server at heroku : https://train-schedules-server.herokuapp.com

Also checkout the source git if you need to fork it : https://github.com/libetl/train-schedules-server
(the dumps list is in the sources.js file)


jeudi 28 décembre 2017

react native fab : round button

Filed under: G33k,Programmes — Étiquettes : , , — LiBe @ 11:13

Quite hard to integrate a Floating action button into react native.
Especially when your app is fusion = has react-native-web but can also be displayed on a desktop.

You cannot react-native link on a webapp and you cannot render anything based on native implementation.

What can you do to easily have a FAB ready to go everywhere (and even on an iPhone) ?

I have made an implementation of the floating action button and I have called it « round button ».
The purpose is to be clickable, to be visible and to not depend of the external layout (to be displayable over the screen, on top of the current display)

I could give you screenshots but better thing is to get a gist and a storybook link

Here is a comprehensive usage of that button : a button which transforms into another when long pressed :

<RoundButton align='left top' longPressText='⚙' longPressColor='#0d5da6' longPressFontColor='#FFFFFF' text='↻'
color='#dfc81f' fontColor='#FFFFFF' onClick={() => console.log('You clicked on ↻')}
onLongClick={() => console.log('You clicked on ⚙')}/>

mercredi 29 novembre 2017

Benchmarkjs : a very small tool to compare speed

Filed under: G33k,Programmes — Étiquettes : , , — LiBe @ 22:51

Wanting to quickly figure out which way is the fastest ?

Want to see median times in µs ?

Download that module : https://gist.github.com/libetl/952e026f889be1cc7cdb3b42767ea845

And call benchmark that way : (copy/paste that code, change ‘forThisWork’ – what to measure, ‘theContestants ‘- who to measure, and ‘seedInit’ – what data should it use)

benchmark({theContestants: 
     {naive: seed => seed.reduce((max, value) => max > value ? max : value, 0),
      iterative: seed => {let max = seed[0]; for (let index = 1 ; index < seed.length ; index++) max = Math.max(max, seed[index])},
      sorted: seed => seed.slice().sort().slice(-1)[0]},
    forThisWork: (max, seed) => max(seed),
    howManyTimes: 1000,
    seedInit: () => new Array(500).fill(Math.random() * 10000),
    barSettings: {width: 100.0, gradient: ['\x1B[47m', '\x1B[42m', '\x1B[46m', '\x1B[45m', '\x1B[41m']},
    asText: true, logInConsole: true})

You will see this kind of graph :

benchmark


vendredi 13 octobre 2017

How to crash javac (jdk8) for free

Filed under: G33k,Programmes — Étiquettes : , — LiBe @ 18:56

This simple snippet can cause javac (from the JDK 8) to crash :

public class TestExtendConsumer {

    @FunctionalInterface public interface NumberConsumer< E extends Number> extends java.util.function.Consumer< E>{void accept(E number);}

    private static < E extends Number> void extend(E number) {
        System.out.println(number);
    }

    public static void main(String[] args) {
        NumberConsumer numberConsumer = TestExtendConsumer::extend;
        numberConsumer.accept(1);

    }
}

Impressive.

$ javac TestExtendConsumer.java
An exception has occurred in the compiler (1.8.0_73). Please file a bug at the Java Bug Database (http://bugreport.java.com/bugreport/) after c                            hecking the database for duplicates. Include your program and the following diagnostic in your report.  Thank you.
java.lang.AssertionError
        at com.sun.tools.javac.util.Assert.error(Assert.java:126)
        at com.sun.tools.javac.util.Assert.check(Assert.java:45)
        at com.sun.tools.javac.code.Types.functionalInterfaceBridges(Types.java:659)
        at com.sun.tools.javac.comp.LambdaToMethod$LambdaAnalyzerPreprocessor$TranslationContext.(LambdaToMethod.java:1692)
        at com.sun.tools.javac.comp.LambdaToMethod$LambdaAnalyzerPreprocessor$ReferenceTranslationContext.(LambdaToMethod.java:2028)
        at com.sun.tools.javac.comp.LambdaToMethod$LambdaAnalyzerPreprocessor.visitReference(LambdaToMethod.java:1383)
        at com.sun.tools.javac.tree.JCTree$JCMemberReference.accept(JCTree.java:1973)
        at com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:58)
        at com.sun.tools.javac.tree.TreeTranslator.visitVarDef(TreeTranslator.java:153)
        at com.sun.tools.javac.comp.LambdaToMethod$LambdaAnalyzerPreprocessor.visitVarDef(LambdaToMethod.java:1441)
        at com.sun.tools.javac.tree.JCTree$JCVariableDecl.accept(JCTree.java:852)
        at com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:58)
        at com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:70)
        at com.sun.tools.javac.tree.TreeTranslator.visitBlock(TreeTranslator.java:162)
        at com.sun.tools.javac.comp.LambdaToMethod$LambdaAnalyzerPreprocessor.visitBlock(LambdaToMethod.java:1172)
        at com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:909)
        at com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:58)
        at com.sun.tools.javac.tree.TreeTranslator.visitMethodDef(TreeTranslator.java:145)
        at com.sun.tools.javac.comp.LambdaToMethod$LambdaAnalyzerPreprocessor.visitMethodDef(LambdaToMethod.java:1299)
        at com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:778)
        at com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:58)
        at com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:70)
        at com.sun.tools.javac.tree.TreeTranslator.visitClassDef(TreeTranslator.java:134)
        at com.sun.tools.javac.comp.LambdaToMethod$LambdaAnalyzerPreprocessor.visitClassDef(LambdaToMethod.java:1209)
        at com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:693)
        at com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:58)
        at com.sun.tools.javac.comp.LambdaToMethod$LambdaAnalyzerPreprocessor.analyzeAndPreprocessClass(LambdaToMethod.java:1162)
        at com.sun.tools.javac.comp.LambdaToMethod$LambdaAnalyzerPreprocessor.access$300(LambdaToMethod.java:1119)
        at com.sun.tools.javac.comp.LambdaToMethod.visitClassDef(LambdaToMethod.java:232)
        at com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:693)
        at com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:58)
        at com.sun.tools.javac.comp.LambdaToMethod.translate(LambdaToMethod.java:197)
        at com.sun.tools.javac.comp.LambdaToMethod.translate(LambdaToMethod.java:190)
        at com.sun.tools.javac.comp.LambdaToMethod.translateTopLevelClass(LambdaToMethod.java:217)
        at com.sun.tools.javac.main.JavaCompiler.desugar(JavaCompiler.java:1493)
        at com.sun.tools.javac.main.JavaCompiler.desugar(JavaCompiler.java:1356)
        at com.sun.tools.javac.main.JavaCompiler.compile2(JavaCompiler.java:901)
        at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:860)
        at com.sun.tools.javac.main.Main.compile(Main.java:523)
        at com.sun.tools.javac.main.Main.compile(Main.java:381)
        at com.sun.tools.javac.main.Main.compile(Main.java:370)
        at com.sun.tools.javac.main.Main.compile(Main.java:361)
        at com.sun.tools.javac.Main.compile(Main.java:56)
        at com.sun.tools.javac.Main.main(Main.java:42)

samedi 16 septembre 2017

Raw js imported as a real module

Filed under: G33k,Programmes — Étiquettes : , , , — LiBe @ 21:06

Modern javascript libraries are all made of modules. They can be plugged inside any project : web, server, batch or program.

That would be really straightforward to include any dependency as a single line of configuration inside a package.json file.

Unfortunately, some teams want to expose web javascript code in a raw format : no ‘export’, no ‘module.exports’, and even nothing injected inside ‘global’ or ‘window’. ES5-like format.

Naive var(s) and function(s) are declared. Private companies do so, and they minify/obfuscate their code.
Some teams even make a javascript code generator when the source code simply cannot be shared with other companies.

//this is a raw javascript file
var foo = "some important data"; 

function bar(){ 
  return "successful."; 
}; 
//This script is unmodifiable
//You are expected to use these symbols, without any access to them.

A simple option would suggest you to copy/paste the library inside your app.
Too bad it affects a lot the quality of your delivery : big chunks, troubles when the library is upgraded but not in your code, ‘package.json’ not explicit (some libs are not visible in the manifest although they are effectively included)

I have found another solution that I wish to share.
1°) Bundle that library in package.json (you can set the URL inside the version).
2°) Then import that lib in your code, but not with the ‘import’ or ‘require’ shortcut… write that boilerplate code :

//it is like an es6 import, excepted the fact that it also collects non exported variables and functions
const _imports = moduleImports => Object.assign({}, ...moduleImports.map(moduleImport => {
    //build a closure outside the non-structured code
    const theModule = new (eval('(function(){' + fs.readFileSync(`${moduleImport}.js`) + ';this.eval = (name) => eval(name)})'))()

    //pick the functions and var names
    const stringified = theModule.constructor.toString()
    const functions = (stringified.match(/function\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(/g)||[]).map(oneFunction => oneFunction.replace(/^function/g, '').replace('(', '').trim())
    const variables = (stringified.match(/(?:var|let|const)\s+([a-zA-Z_][.a-zA-Z_0-9]*)\s*=/g)||[]).map(oneFunction => oneFunction.replace(/^(var|let|const)\s/g, '').replace('=', '').trim())

    //resolve the references inside the module
    return Object.assign({}, ...functions.concat(variables).map(symbol => {try{return {[symbol]:theModule.eval(symbol)}}catch(e){}}).filter(symbol => symbol))
}))

3°) Call the _imports function where needed

// top of your js file
const { foo, bar } = _imports(['my-es5-module-1', 'my-es5-module-2'])
// your other imports
import { Component } from 'react'
// and it works...
console.log(foo)
console.log(bar())

There you go, use the invisible members of the lib like if they were really exported.
In this example, it also works when the lib is a set of javascript files that are dependent on each other but without import.

By the way,
Maybe you noticed that the layout of the blog has changed once again.
I am pretty sure that my visitors will prefer some rich content rather then a beautiful decoration, given the fact that the blog no longer talks about artistic concerns.
That is why I chose the most sober design I could find.


jeudi 31 août 2017

How to parse a phone number

Filed under: G33k,Programmes — Étiquettes : , , — LiBe @ 10:41

There are tons of different writings for a phone number. The simplest one is the spoke one. A list of numbers that can identify several targets in the world.
It is not unique because of the country indicator that is used for disambiguation, what we call the « international format ». Even after that, behind a phone number can be hidden several phones thanks to the extension.

To all of to who think the phone format is a quite conventionnal one, how would your program read this one ?
+1 400-CALL-0ME-NOW
or this one ?
49 345 67890
or this one ?
(0) 33 1 400 500 600,700

Google made an attempt to parse a phone number accurately thanks to the phone number util library. Among others, it is able to recognize any phone number format, to guess which operator it is, and to isolate the country prefix from the rest.

Trouble exists when you want to record the region indicator as well. It does not exist in every country and it is a tough work when the input is all in one block (no space).

I spent several hours to parse the number in a reliable way. And here is the result : https://gist.github.com/libetl/2122945b868e5c73198ab0a4f6f4fb85

Every input will get split into 4 parts, whatever the complexity of the format is :

country prefix, region prefix, trunk, extension.

Notice that the mobile prefix won’t be included in the region prefix, it usually identifies a device rather than a place.


Older Posts »

LiBe. Blog Artistique. V3. Aucun droit d'auteur sur les oeuvres ci-présentes. Peau Wordpress par LiBe.