Comment gérer les pages obsolètes d'une PWA ?

WRInaute accro
Bonjour

C'est pour une PWA.

Je me met à l'étude de chargements de préférence up to date, avec l'api fetch.

Comment savoir si une page a été modifiée sur le serveur ?

C'est-à-dire, si cache valide, à partir du cache, sinon fetch + put dans le cache.

J'ai imaginé l'enreg d'un hash code sha256 des pages.

Mais le fichier contenant le hash code devrait être écrit sur le serveur à chaque modif. d'une page.

La lecture de ce hash code ( fetch + response ), serait plus rapide que celle du fichier.

Et, le hash code du cache serait lu à partir du contenu cache.

Ensuite, comparaison, et si différent, fetch + put.

Celà vous semble-t-il viable ?

Merci beaucoup.
 
WRInaute accro
Rebonjour

Je suis en train de coder.

Qu'est-ce qui se passe :

quand dans un self.addEventListener('fetch', function(evt) :

on appelle plusieurs fetch, en modes et GET et PUT ?

Plus général : Comment se fait-il qu'il soit possible d'appeler un fetch à l'intérieur du Listener fetch , sans que celà ne déclenche un autre event fetch ?

Merci beaucoup de vos réponses.

Je suis un newbie en Javascript.

Amicalement.
 
WRInaute passionné
Dans l'esprit d'une PWA, je ne dirais pas qu'il faut voir si la "page" a été modifiée ou pas, parce qu'on n'est pas sensé charger une page, mais des données (le contenu dynamique, en JSON par exemple), et ensuite la page est remplie en JS.

En dehors du 1er chargement du template (une page HTML vide, en gros, les fichiers JS/CSS...), le reste ce sont des flux JSON, on interroge le serveur pour savoir si tel truc ou tel truc est à jour (par exemple une liste de produits : on compare si le dernier stocké en base locale correspond au dernier du serveur, ou bien s'il y en a des nouveaux à charger).

Ton histoire de hashcode peut fonctionner bien sûr, mais alors on se retape le chargement de la page au complet au lieu de ne charger que les données qui ont changé, ça n'est pas optimal.
 
WRInaute accro
Bonjour rick38

Effectivement, le hash-code ne devrait mesurer que les data ( json ou text ).

J'ai du mal à faire des fetch PUT pour uploader les hash-codes sur le serveur.

A chaque fichier "fichier.json", son hash-code "hashcode_fichier.json".

Dans mon esprit, à chaque lecture de fichier , les hash-codes sont systématiquement lus avec fetch, et comparés à celui du fichier en cache.

Si les deux sont identiques, return response du cache, sinon fetch fichier et put du hash-code.

Mais je n'arrive pas à faire des PUT avec des fetch.

Voici le code :

JavaScript:
// Make an HTTP PUT Request
async function put(url, someData) {
        console.log('Handling PUT fetch for : ' + url + ' , hash_code : '. someData);
        // Awaiting fetch which contains method,
        // headers and content-type and body
        await fetch(url, {
                method: 'PUT',
                headers: {
                        'Content-type': 'application/json; charset=UTF-8' // Indicates the content
                },
                body: JSON.stringify(someData) // We send data in JSON format
        }).then(function(reponse) {
                return reponse.json();
        }, function(error) {
                console.log('Error while putting file : ' + url + '  , with hash code : ' + someData + '  , with error : ' + error);
                return false;
        })
}


Merci beaucoup de ton aide.
 
WRInaute accro
Rebonjour rick38

Voilà une fonction à utiliser pour translater du html en json :

JavaScript:
// Test with an element.
var initElement = document.getElementsByTagName("html")[0];
var json = mapDOM(initElement, true);
console.log(json);

// Test with a string.
initElement = "<div><span>text</span>Text2</div>";
json = mapDOM(initElement, true);
console.log(json);

function mapDOM(element, json) {
    var treeObject = {};
            // If string convert to document Node
    if (typeof element === "string") {
        if (window.DOMParser) {
              parser = new DOMParser();
              docNode = parser.parseFromString(element,"text/xml");
        } else { // Microsoft strikes again
              docNode = new ActiveXObject("Microsoft.XMLDOM");
              docNode.async = false;
              docNode.loadXML(element); 
        }
        element = docNode.firstChild;
    }
            //Recursively loop through DOM elements and assign properties to object
    function treeHTML(element, object) {
        object["type"] = element.nodeName;
        var nodeList = element.childNodes;
        if (nodeList != null) {
            if (nodeList.length) {
                object["content"] = [];
                for (var i = 0; i < nodeList.length; i++) {
                    if (nodeList[i].nodeType == 3) {
                        object["content"].push(nodeList[i].nodeValue);
                    } else {
                        object["content"].push({});
                        treeHTML(nodeList[i], object["content"][object["content"].length -1]);
                    }
                }
            }
        }
        if (element.attributes != null) {
            if (element.attributes.length) {
                object["attributes"] = {};
                for (var i = 0; i < element.attributes.length; i++) {
                    object["attributes"][element.attributes[i].nodeName] = element.attributes[i].nodeValue;
                }
            }
        }
    }
    treeHTML(element, treeObject);
            return (json) ? JSON.stringify(treeObject) : treeObject;
}
 
WRInaute accro
Ah

Voilà une fonction sympathique :

JavaScript:
// Small library to improve on fetch() usage
const api = function(method, url, data, headers = {}){
  return fetch(url, {
    method: method.toUpperCase(),
    body: JSON.stringify(data),  // send it as stringified json
    credentials: api.credentials,  // to keep the session on the request
    headers: Object.assign({}, api.headers, headers)  // extend the headers
  }).then(res => res.ok ? res.json() : Promise.reject(res));
};
// Defaults that can be globally overwritten
api.credentials = 'include';
api.headers = {
  'csrf-token': window.csrf || '',    // only if globally set, otherwise ignored
  'Accept': 'application/json',       // receive json
  'Content-Type': 'application/json'  // send json
};
// Convenient methods
['get', 'post', 'put', 'delete'].forEach(method => {
  api[method] = api.bind(null, method);
});


Pour appeler en mode put je fais :

api['put'](url, data) ?

ou bien :

api.put(url, data ) ?

Je m'emberlificote dans les objets en javascript.

Merci beaucoup.
 
WRInaute accro
Bonjour rick38

Tout fonctionne peu près, si ce n'est l'envoi par PUT des hashcodes.

La fonction échoue lors des put.

Voilà le code de la fonction :

Merci beaucoup.


JavaScript:
// Small library to improve on fetch() usage
const api_put = function(method, url, data, headers = {}){
        return fetch(url, {
                method: method.toUpperCase(),
                body: data,                                                // send it as stringified json
                credentials: api_put.credentials,                        // to keep the session on the request
                headers: Object.assign({}, api_put.headers, headers)    // extend the headers
        }).then(res => res.ok ? res.text() : Promise.reject(res));
};
// Defaults that can be globally overwritten
api_put.credentials = 'include';
api_put.headers = {
        //        'csrf-token': window.csrf || '',     // only if globally set, otherwise ignored
        'Accept': 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8',    // receive text/html
        'Content-Type': 'text/html; charset=utf-8'    // send text/html
};
// Convenient methods
['post', 'put', 'delete'].forEach(method => {
        api_put[method] = api_put.bind(null, method);
});
 
WRInaute accro
Cà marche.

Pour que le serveur web ( Nginx ) accepte les PUT, il suffit de mettre celà dans la config. :

Code:
    location /pwa {
            root     /var/www/html;
            dav_methods  PUT;

#          dav_methods PUT DELETE MKCOL COPY MOVE;

            dav_access user:rw group:rw all:r;
    }

Cà marche, mais je dois peaufiner la loqique de ma fonction FETCH qui est censée prendre en charge les FetchEvent.

Merci beaucoup.
 
WRInaute accro
Voilà la fonction FETCH(tmp_file) :

JavaScript:
function hashCode(str) {
        var hash = 0;
        if(typeof str == 'undefined') {
                return hash;
        }
        var len = str.length;
        var tmp_char = null;
        for (var i = 0; i < len; i++) {
                tmp_char = str.charCodeAt(i);
                hash = ((hash<<5)-hash)+tmp_char;
                hash = hash & hash; // Convert to 32bit integer
        }
        return hash;
}

// Small library to improve on fetch() usage
const api_fetch = function(method, url, data, headers = {}){
        return fetch(url, {
                method: method.toUpperCase(),
                body: data,                                                // send it as stringified json
                credentials: api_fetch.credentials,                        // to keep the session on the request
                headers: Object.assign({}, api_fetch.headers, headers)    // extend the headers
        }).then(res => res.ok ? res : Promise.reject(res));
};
// Defaults that can be globally overwritten
api_fetch.credentials = 'include';
api_fetch.headers = {
        'cache': 'no-store',
        'Accept': 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8',    // receive text/html
        'Content-Type': 'text/html; charset=utf-8'    // send text/html
};
// method Fetch
api_fetch['fetch'] = api_fetch.bind(null, 'fetch');

// Small library to improve on fetch() usage
const api_put = function(method, url, data, headers = {}){
        return fetch(url, {
                method: method.toUpperCase(),
                body: data,                                                // send it as stringified json
                credentials: api_put.credentials,                        // to keep the session on the request
                headers: Object.assign({}, api_put.headers, headers)    // extend the headers
        }).then(res => res.ok ? res.text() : Promise.reject(res));
};
// Defaults that can be globally overwritten
api_put.credentials = 'include';
api_put.headers = {
        'Accept': 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8',    // receive text/html
        'Content-Type': 'text/html; charset=utf-8'    // send text/html
};
// Convenient methods
['post', 'put', 'delete'].forEach(method => {
        api_put[method] = api_put.bind(null, method);
});

const CACHE = 'pwa-conf-v1';
var staticAssets = [
        './',
        './index.html',
        './app.js',
        './styles.css'
];

function from_filename(str, sep) {
        return new Array(str.substring(0, str.lastIndexOf(sep)), str.substring(str.lastIndexOf(sep) + 1));
}

function FETCH(tmp_file) {
        var chemin                = null;
        var avant_file_ndd        = null;
        var file_ndd            = null;
        var file                 = tmp_file;
        var hash_code_file        = null;
        var HashCodeResponse    = null;
        var Response            = null;
        var text                = null;
        var hash_code_cache        = null;
        var hash_code            = null;
        console.log('Handling FETCH function for : ' + file);
        str = from_filename(file, '/');
        chemin = str[0];
        avant_file_ndd = from_filename(from_filename(file, '/')[1], '.')[0];
        file_ndd = from_filename(file, '.')[1];
        hash_code_file = chemin + '/hash_code_' + avant_file_ndd + '.txt';
        file = new URL(file, location.href);
        hash_code_file = new URL(hash_code_file, location.href);
        console.log('request for : ' + file + ' and : ' + hash_code_file);
        return caches.open(CACHE).then(function (cache) {
                /*****************************/
                /**    LE FICHIER DE HASH CODE    **/
                /** CONTIENT LE HASH CODE    **/
                /**    DU FICHIER CHARGE.        **/
                /*****************************/
                /***********************/
                /**    VALEUR IMPOSSIBLE **/
                /***********************/
                HashCodeResponse = -10;
                api_fetch['get'](hash_code_file).then(function(response) {
                        if(!response) {
                                return false;
                        }
                        return  response.text();
                }, function(error) {
                        console.log('Error while fetching1 hash_code_file : ' + hash_code_file + '  , with error : ' + error.message);
                }).then(function(text) {
                        HashCodeResponse = text;
                        console.log('Succeded while fetching hash_code_file : ' + hash_code_file + ' with text : ' + text);
                }, function(error) {
                        console.log('Error while fetching2 hash_code_file : ' + hash_code_file + '  , with error : ' + error.message);
                })
                console.log('HashCodeResponse = ' + HashCodeResponse);
                /*****************************************************/
                /**    ON CHARGE LE HASH CODE DU FICHIER EN CACHE        **/
                /*****************************************************/
                /**        CE HASH CODE DOIT AVOIR ETE UPDATE LORS        **/
                /**            DU DERNIER CHARGEMENT DU FICHIER.        **/
                /*****************************************************/
                cache.match(file).then(function(response) {
                        if(!response) {
                                return false;
                        }
                        Response = response.clone();
                        return response.text();
                }).then(function(text) {
                        hash_code_cache = hashCode(text);
                        console.log('Succeded while reading file from the cache : ' + file + ' with text : ' + text);
                        if(HashCodeResponse == hash_code_cache) {
                                /**********************/
                                /**    REPONSE DU CACHE **/
                                /**********************/
                                return Response;
                        }
                }, function(error) {
                        console.log('Error while reading file from cache : ' + file + '  , with error : ' + error.message);
                })
                /*****************************/
                /**       HASH CODE INVALIDE    **/
                /**       ON LIT LE FICHIER    **/
                /**        ET ON UPDATE        **/
                /**        LE HASH CODE        **/
                /*****************************/
                api_fetch['get'](file).then(function(response) {
                        if(!response) {
                                return false;
                        }
                        Response = response.clone();
                        return response.text();
                }).then(function(text) {
                        console.log('Succeded while fetching file : ' + file + ' with response : ' + text);
                        hash_code = hashCode(text);
                        console.log('hash code = ' + hash_code);
                        /*************************/
                        /**      ON MET EN CACHE    **/
                        /**        LE FICHIER        **/
                        /*************************/
                        cache.put(file, Response);
                        /*************************/
                        /**        ON UPDATE        **/
                        /**        LE HASH CODE    **/
                        /*************************/
                        api_put['put'](hash_code_file, hash_code).then(function(text) {
                                console.log('Succeded while putting file : ' + hash_code_file + ' with hash code : ' + hash_code + ' with response : ' + text);
                        }, function(error) {
                                console.log('Error while putting file : ' + hash_code_file + ' with hash code : ' + hash_code + '  , with error : ' + error.message);
                        })
                        /************************/
                        /**    REPONSE DU FICHIER **/
                        /************************/
                        return Response;
                }, function(error) {
                        console.log('Error while fetching file : ' + file + '  , with error : ' + error.message);
                        return false;
                })
        }, function(error) {
                console.log('Error while opening cache : ' + CACHE + '  , with error : ' + error.message);
                return false;
        })
}
 
WRInaute accro
Bonjour

J'ai une question :

J'ai une fonction FETCH(tmp_file) qui rend une Promise de type Response.

Cette fonction utilise des fetch().

Est-il possible, de déclarer le FetchEvent de cette manière :

JavaScript:
self.addEventListener('fetch', function(event) {
        return FETCH(event.request.url);
});

sachant que fetch et FETCH retournent une Promise ?

A part çà, les FetchEvent sont déclenchés par l'interface utilisateur ?

C'est-à-dire : Les clics sur les href ?

Merci beaucoup.
 
WRInaute accro
Pardon

Problème résolu.

Le FETCH rend une Promise, il faut la résoudre.

JavaScript:
self.addEventListener('fetch', function(event) {
       return FETCH(event.request.url).then(function(response) {
            return response;
      }, { function(error ){
           // traitement d'erreur.
     })
});

Là, çà marche, mais j'ai encore des erreurs, je vais charger moins de fichiers, vérifier FETCH() et investiguer.

Et puis FETCH(event) au lieu de FETCH(event.request.url), correct ?

Merci beaucoup.
 
WRInaute accro
J'ai une question :

Dans le service worker, je déclare les FetchEvent avec une fonction FETCH(file) qui rend la promise de type response, ou false.

Il y a également, la fonction LECT(file), utilisant FETCH(file), et qui rend directement le contenu de file, lu soit dans le cache, soit le fichier file.

Ces deux fonctions, doivent retourner toujours une version à jour de file.

Voici le code :

JavaScript:
self.addEventListener('fetch', function(evt) {
        // In a real app, you'd use a more sophisticated URL check.
        //        if (event.request.url.match(/.*/)) {
        //        event.respondWith(FETCH(event.request.url));
        var file = evt.request.url;
        evt.waitUntil(
                FETCH(file).then(function(response) {
                        if(response === false) {
                                console.log('Failed while FETCHING file, with response : ' + response);
                        }
                        return response;
                }, function(error) {
                        console.log('Error while FETCHING file, with error : ' + error.message);
                        return false;
                })
        );
        return false;
});

function LECT(file) {
        FETCH(file).then(function(response) {
                if(response === false) {
                        console.log('Failed while reading file, with response : ' + response);
                }
                response.text().then(function(text) {
                        if(text) {
                                console.log('Ok while reading file, with response : ' + text);
                                return text;
                        } else {
                                console.log('Bad while reading file, with response : ' + text);
                                return false;
                        }
                }, function(error) {
                        console.log('Error while  file, with error : ' + error.message);
                        return false;
                })
        }, function(error) {
                console.log('Error while  file, with error : ' + error.message);
                return false;
        })
}

Ma question : Dois-je utiliser waitUntil ?

Mon code est-il correct ?

Je débute avec L'API fetch.

Merci beaucoup.
 
Discussions similaires
Haut