Dominando HTML5 offline com AppCache

Offline?

Application Cache API

Arquivo manifesto

CACHE MANIFEST

/index.html
/imagens/logo.png
/javascript/script.js
/css/estilo.css

Linkar pro manifesto

<html manifest="demo.appcache">

Pronto! Fácil!

Rá!

Não é fácil!

Mime-type

text/cache-manifest

Meu site bonitão!

Meu site bonitão!

Rá!

Fora do manifesto? Não funciona!

Seção network

CACHE MANIFEST

/index.html
/imagens/logo.png
/javascript/script.js
/css/estilo.css

NETWORK:
http://www.google-analytics.com/ga.js

Seção network

CACHE MANIFEST

/index.html
/imagens/logo.png
/javascript/script.js
/css/estilo.css

NETWORK:
*

E quando estou offline?

Usuários:

Usuários:

Seção fallback

CACHE MANIFEST

/index.html
/imagens/logo.png
/javascript/script.js
/css/estilo.css

NETWORK:
*

FALLBACK:
/img/avatares/ /img/avatar-generico.png

Usuários:

Detalhes do manifesto

Página com manifesto

CACHE MANIFEST

/index.html
/imagens/logo.png
/javascript/script.js
/css/estilo.css
<!-- index.html -->
<html manifest="demo.appcache">

Página com manifesto

CACHE MANIFEST

/imagens/logo.png
/javascript/script.js
/css/estilo.css
<!-- index.html -->
<html manifest="demo.appcache">

Rá!

Query strings

CACHE MANIFEST

/imagens/logo.png
/javascript/script.js
/css/estilo.css
<img src="/imagens/logo.png?malandragem">

Rá!

404 ou 500? Cache todo é descartado!

Rá!

Cache-Control: no-store

API JavaScript window.applicationCache

Feature test

if ('applicationCache' in window) {

    // AppCache disponível!

} else {

    // sinto muito!

}

Eventos JS

applicationCache.onchecking = function(){ /* ... */ }

Eventos JS

applicationCache.onchecking = function(){ /* ... */ }

applicationCache.ondownloading = function(){ /* ... */ }

Eventos JS

applicationCache.onchecking = function(){ /* ... */ }

applicationCache.ondownloading = function(){ /* ... */ }

applicationCache.onprogress = function(){ /* ... */ }

Progresso

applicationCache.onprogress = function(e){
    log('Baixamos já ' + e.loaded + ' de ' + e.total);
}

Eventos JS

applicationCache.onchecking = function(){ /* ... */ }

applicationCache.ondownloading = function(){ /* ... */ }

applicationCache.onprogress = function(){ /* ... */ }

applicationCache.oncached = function(){ /* ... */ }

Eventos JS

applicationCache.onchecking = function(){ /* ... */ }

applicationCache.ondownloading = function(){ /* ... */ }

applicationCache.onprogress = function(){ /* ... */ }

applicationCache.oncached = function(){ /* ... */ }

applicationCache.onerror = function(){ /* ... */ }

Status

applicationCache.status 
   // 2 == CHECKING
   // 3 == DOWNLOADING
   // ...

E depois?

Servido sempre do cache!

Rá!

Atualiza só quando o manifesto muda

CACHE MANIFEST

/imagens/logo.png
/css/estilo.css
/javascript/script.js
CACHE MANIFEST

/imagens/logo.png
/css/estilo.css
/javascript/script.js
/javascript/jquery.js

Queria mudar o próprio arquivo

CACHE MANIFEST

/imagens/logo.png
/css/estilo.css
/javascript/script.js
/javascript/jquery.js
CACHE MANIFEST

/imagens/logo.png
/css/estilo.css
/javascript/script.js?v2
/javascript/jquery.js
CACHE MANIFEST

/imagens/logo.png
/css/estilo.css
/javascript/script-v2.js
/javascript/jquery.js
CACHE MANIFEST
# Versão 2

/index.html
/imagens/logo.png
/css/estilo.css
/javascript/script.js
/javascript/jquery.js

Eventos JS - Update

applicationCache.onchecking = function(){ /* ... */ }

Eventos JS - Update

applicationCache.onchecking = function(){ /* ... */ }

applicationCache.onnoupdate = function(){ /* ... */ }

Eventos JS - Update

applicationCache.onchecking = function(){ /* ... */ }

applicationCache.onnoupdate = function(){ /* ... */ }

// ou

applicationCache.ondownloading = function(){ /* ... */ }
applicationCache.onprogress = function(){ /* ... */ }

Eventos JS - Update

applicationCache.onchecking = function(){ /* ... */ }

applicationCache.onnoupdate = function(){ /* ... */ }

// ou

applicationCache.ondownloading = function(){ /* ... */ }
applicationCache.onprogress = function(){ /* ... */ }

applicationCache.onupdateready = function(){ /* ... */ }

Não tá atualizando!

Rá!

Lembre do HTTP cache normal

Não cacheie o manifesto!

Atualizou? Não vi!

Rá!

Reload

applicationCache.onupdateready = function() {
    if (confirm('Tem atualização. Recarregar?')) {
        location.reload();
    }
}

Swap Cache

applicationCache.onupdateready = function() {
    applicationCache.swapCache();
}

Swap Cache

applicationCache.onupdateready = function() {

    ajax('/teste.html', ...); // versão velha

    applicationCache.swapCache();

    ajax('/teste.html', ...); // versão nova!
}

Tem atualização?

Update

applicationCache.update();

Update

setTimeout(function(){
    applicationCache.update();  
}, 1000 * 60 * 60);

Como apagar?

Eventos JS - Remove

applicationCache.onchecking = function(){ /* ... */ }

applicationCache.onobsolete = function(){ /* ... */ }

O usuário não controla nada!

Rá!

Controle na mão do usuário

instalar.html

<html manifest="apostila.appcache"></html>
CACHE MANIFEST

/apostila/

Disparar instalação

botaoInstalar.onclick = function(){
    document.body.innerHTML 
        += '<iframe src="instalar.html"></iframe>';
}

E o update?

Abort

applicationCache.onchecking = function() {
    applicationCache.abort();
}

Rá!

Impedir update automático

Request do manifesto

Não quero atualizar Tudo bem, não mudou Agora quero atualizar Então toma o manifesto

Um pouco de PHP

<?php 
  if ($_COOKIE['offline'] == 'noupdate') {
    // falo que manifesto não mudou
  } else {
    // mando o manifesto novo
  }
?>

Enviando o manifesto

<?php 
  if ($_COOKIE['offline'] == 'noupdate') {
    // falo que manifesto não mudou
  } else {
    include('apostila.appcache');
  }
?>

304 Not Modified

304 no servidor

<?php 
  if ($_COOKIE['offline'] == 'noupdate') {
    header('HTTP/1.1 304 Not Modified');
  } else {
    include('apostila.appcache');
  }
?>

Quero atualizar!

botaoAtualizar.onclick = function() {
    document.cookie = '';
    
    // recarrega o iframe de instalação
}

Tem atualização?

versao.txt

10345675

versao.txt

10345675
CACHE MANIFEST

/apostila/
/versao.txt
# ....

Qual versão tenho?

ajax('/versao.txt', function(versao){
    alert('Versão instalada: ' + versao)
})

Qual a última versão?

ajax('/versao.txt?ultima', function(ultima){
    alert('Versão no servidor: ' + ultima)
})

Aviso de update

ajax('/versao.txt', function(versao){
    ajax('/versao.txt?ultima', function(ultima){
        if (versao !== ultima) {
        
            // avisa o usuario que tem nova versao
            // e dá opção de instalação
            
        }
    })
})

Não quero mais!

404 no servidor

<?php 
  if ($_COOKIE['offline'] == 'noupdate') {
    header('HTTP/1.1 304 Not Modified');
  } elseif ($_COOKIE['offline'] == 'remove') {
    header('HTTP/1.1 404 Not Found');
  } else {
    include('apostila.appcache');
  }
?>

Cookie no browser

// quero atualizar!
botaoRemover.onclick = function() {
    document.cookie = 'offline=remove';
    // recarrega o iframe de instalação
}
<?php 
  header("Vary: Cookie");
  header("Expires: Thu, 01 Jan 1970 00:00:00 GMT");

  if ($_COOKIE['offline'] == 'noupdate') {
    header('HTTP/1.1 304 Not Modified');
  } elseif ($_COOKIE['offline'] == 'remove') {
    header('HTTP/1.1 404 Not Found');
  } else {
    header('Content-Type: text/cache-manifest');
    include('apostila.appcache');
  }
?>

Aí sim!

Dicas
finais