8. Exemplo [nuxt-05]: persistência do store com um cookie de sessão
Objetivo: pretendemos que o store [Vuex] não seja reinicializado a cada pedido ao servidor. Para tal, vamos utilizar um cookie de sessão:
- o armazenamento será inicializado pelo servidor e este colocá-lo-á num cookie de sessão;
- o navegador do cliente receberá esse cookie de sessão e enviá-lo-á automaticamente em cada nova solicitação ao servidor;
- o servidor poderá então recuperar esse cookie de sessão e trabalhar com o armazenamento que este contém, um armazenamento atualizado pelo cliente;
8.1. Présentation
O projeto [nuxt-05] é obtido inicialmente através da cópia do projeto [nuxt-04]:

Veremos que apenas o ficheiro [store / index.js] irá sofrer alterações.
Para utilizar cookies com o [nuxt], vamos utilizar o módulo [cookie-universal-nuxt], que instalamos juntamente com o [yarn] num terminal VSCode:

- no [4], introduzimos o comando [yarn add cookie-universal-nuxt];
Desta forma, é adicionado um novo módulo ao ficheiro [package.json] do projeto [dvp]:
...
},
"dependencies": {
"@nuxtjs/axios": "^5.3.6",
"bootstrap": "^4.1.3",
"bootstrap-vue": "^2.0.0",
"cookie-universal-nuxt": "^2.0.19",
"nuxt": "^2.0.0"
},
8.2. O ficheiro de configuração [nuxt.config.js]
Para que o [nuxt] possa utilizar os cookies do [cookie-universal-nuxt], é necessário declarar este módulo no ficheiro de configuração [nuxt.config.js]:
...
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
...
- na linha 12, o módulo [cookie-universal-nuxt] é adicionado à tabela de módulos [6] de [nuxt];
O ficheiro [nuxt.config.js] fica, por fim, com o seguinte conteúdo:
export default {
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: 'Introduction à [nuxt.js]',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
hid: 'description',
name: 'description',
content: 'ssr routing loading asyncdata middleware plugins store'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [],
/*
** Plugins to load before mounting the App
*/
plugins: [],
/*
** Nuxt.js dev-modules
*/
buildModules: [
// Doc: https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module'
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Documentação: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {}
},
// diretório do código-fonte
srcDir: 'nuxt-05',
// router
router: {
// raiz da aplicação URL
base: '/nuxt-05/'
},
// servidor
server: {
// porta de serviço, 3000 por predefinição
port: 81,
// endereços de rede em escuta, por predefinição localhost: 127.0.0.1
// 0.0.0.0 = todos os endereços de rede do computador
host: 'localhost'
},
// ambiente
env: {
maxAge: 60 * 5
}
}
- linha 79: foi adicionada a chave [env] ao ficheiro. Esta chave é uma palavra reservada. Os elementos declarados neste objeto estão disponíveis a partir do objeto [context.env] nos elementos da aplicação;
- linha 80: o atributo [maxAge] corresponderá ao tempo de vida máximo do cookie de sessão, duração que é medida a partir da última vez que o cookie foi inicializado. Este tempo é expresso em segundos. Aqui, definimos um tempo de vida de 5 minutos;
8.3. O princípio da persistência do armazenamento
Os cookies trocados entre o cliente e o servidor estão disponíveis em ambos os lados (cliente e servidor) em:
- [context.app.$cookies], onde o objeto [context] está disponível, ou seja, praticamente em todo o lado;
- [this.$cookies] no interior de uma vista;
Obtenha-se um cookie específico com a expressão [...$cookies.get(‘nom_du_cookie’)]. Defina-se o valor de um cookie com a expressão [...$cookies.set(‘nom_du_cookie’, valeur_du_cookie)].
O princípio do cookie de persistência do armazenamento será o seguinte:
- quando o servidor inicializar o armazenamento na função [nuxtServerInit], o estado do armazenamento será guardado num cookie denominado «session»;
- o cookie «session» fará então parte da resposta HTTP do servidor. Sabe-se que um navegador devolve ao servidor os cookies que este lhe enviou. Faz-o em cada nova solicitação que envia ao servidor. Sabe-se também que o servidor envia o armazenamento dentro da página que envia ao cliente;
- no navegador, a aplicação cliente recupera o «store» enviado pelo servidor e, em seguida, executa as suas tarefas. Asseguraremos que, sempre que a aplicação alterar o «store», o seu novo estado seja armazenado no cookie «session» registado pelo navegador;
- se o utilizador forçar uma chamada ao servidor, o navegador do cliente reenviará automaticamente todos os cookies que o servidor lhe enviou anteriormente, nomeadamente o cookie denominado «session»;
- quando, na sequência dessa chamada, o servidor reinicializar novamente o armazenamento, irá recuperar o cookie denominado «session» e inicializar o estado do armazenamento com o valor deste;
- haverá, portanto, continuidade do «store» entre o cliente e o servidor;
8.4. Inicialização do armazenamento
O armazenamento está implementado no ficheiro [store / index.js]:
/* eslint-disable no-console */
export const state = () => ({
// contador
counter: 0
})
export const mutations = {
// incremento do contador em um valor [inc]
increment(state, inc) {
state.counter += inc
},
// substituição do estado
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// quem executa este código?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// aguarda-se o fim de uma promessa
await new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamo-la com uma espera de um segundo
setTimeout(() => {
// inicializar sessão
initStore(store, context)
// sucesso
resolve()
}, 1000)
})
}
}
function initStore(store, context) {
// existe algum cookie de sessão na solicitação atual
const cookies = context.app.$cookies
const session = cookies.get('session')
if (!session) {
// não existe nenhuma sessão
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// inicializar o armazenamento
store.commit('increment', 77)
} else {
console.log("nuxtServerInit, reprise d'une session existante")
// atualização do armazenamento com o cookie de sessão
store.commit('replace', session.store)
}
// colocamos o armazenamento no cookie de sessão
cookies.set('session', { store: store.state }, { path: context.base, maxAge: context.env.maxAge })
// registo
console.log('initStore terminé, store=', store.state)
}
Comentários
- linhas 2-5: o store será constituído por um contador;
- linhas 9-11: este contador poderá ser incrementado;
- linhas 13-17: o estado do registo poderá ser inicializado a partir de um novo estado. Esta função serve para mostrar uma possível inicialização do registo quando este não se limita apenas ao contador, como neste caso;
- linhas 21-35: a função [nuxtServerInit] não sofreu alterações;
- linha 30: quando o tempo de espera de um segundo terminar, o registo é inicializado com a função das linhas 38-56;
- linhas 40-41: começamos por recuperar o cookie denominado «session»:
- na primeira execução da aplicação e na primeira solicitação enviada ao servidor, este cookie ainda não existirá. Será então criado (linha 53) e enviado ao navegador do cliente;
- durante a mesma execução da aplicação e nas solicitações n.º 2, 3, ... feitas ao servidor, este cookie já existirá, pois o navegador do cliente o reenviará com cada nova solicitação feita ao servidor;
- numa segunda execução da aplicação e na primeira solicitação feita ao servidor, este cookie também pode existir. Com efeito, no final da etapa 1, o cookie foi armazenado no navegador com um determinado período de validade. Se esse período de validade não tiver expirado, o cookie denominado «session» será enviado com a primeira solicitação feita ao servidor
Em resumo, para cada pedido feito ao servidor: se o cookie «session» já estiver armazenado no navegador do cliente, o servidor irá recebê-lo; caso contrário, não o receberá.
- linhas 42-47: se o servidor não receber o cookie de sessão, então o armazenamento é inicializado pela linha 46;
- depois, na linha 53, será criado um cookie denominado «session» e colocado na resposta HTTP do servidor. O valor do cookie é o objeto [{ store: store.state }]. É, portanto, o estado do armazenamento e não o próprio armazenamento que é colocado no cookie de sessão;
- o terceiro parâmetro da função [set] é um objeto de opções:
- [path] indica a que URL este cookie deverá ser reenviado. [context.base] é o URL de base da aplicação [nuxt-05]. Este está definido no ficheiro [nuxt.config.js]:
// router
router: {
// raiz dos URL da aplicação
base: '/nuxt-05/'
},
- [maxAge] corresponde ao tempo de vida, em segundos, do cookie no navegador. Passado esse tempo, o navegador deixa de o reenviar ao servidor. [context.env.maxAge] reenvia, mais uma vez, um valor registado no ficheiro [nuxt.config.js]:
[env] é uma palavra-chave reservada do ficheiro de configuração. Aqui, define-se o tempo de vida para 5 minutos. Este período é medido a partir da última vez em que o navegador recebeu o cookie de sessão. Passado esse período, o cookie não será reenviado ao servidor, que terá então de iniciar uma nova sessão;
- linhas 48-50: se o servidor receber o cookie de sessão, o estado do armazenamento é inicializado com o objeto [store] do cookie de sessão. Recorde-se que este objeto contém o estado guardado do armazenamento;
- em seguida, na linha 53, o cookie de sessão será incluído na resposta enviada ao navegador do cliente:
- a função [get] vai buscar o cookie de sessão na solicitação recebida pelo servidor;
- a função [set] insere o cookie de sessão na resposta que o servidor envia ao navegador do cliente;
- em seguida, na linha 53, o cookie de sessão será incluído na resposta enviada ao navegador do cliente:
8.5. Incremento do contador da loja
O incremento do contador na página [index.vue] ocorre da seguinte forma:
// gestão de eventos
methods: {
incrementCounter() {
console.log('incrementCounter')
// incremento do contador em 1
this.$store.commit('increment', 1)
// alteração do valor apresentado
this.value = this.$store.state.counter
// gravação do estado no cookie de sessão
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
Do lado do cliente, sempre que se altera o store, é necessário guardá-lo no cookie de sessão. Com efeito, o utilizador pode solicitar manualmente uma URL a qualquer momento e, nesse caso, é necessário poder enviar ao servidor um store atualizado. É por isso que, na linha 10, após o incremento do contador do «store», guardamos o seu estado no cookie de sessão:
- os cookies estão disponíveis na propriedade [this.$cookies];
- o estado do store [this.$store.state] é guardado no cookie associado à chave [store];
- o caminho do cookie é [context.base]. Numa vista, o contexto está disponível em [this.$nuxt.context];
- o tempo de vida do cookie é [context.env.maxAge], disponível aqui na propriedade [this.$nuxt.context.env.maxAge];
8.6. Execução do exemplo [nuxt-05]
Iniciamos a aplicação [nuxt-05]:

As capturas de ecrã que se seguem foram tiradas num navegador Chrome. Solicitamos o URL [http://localhost:81/nuxt-05/]. Não se esqueça do último / a seguir a /nuxt-05, caso contrário não obterá os resultados esperados:

- em [4], obtivemos o valor inicial do store (77);
Vamos analisar os registos do navegador (F12):

- em [5-6], os registos do servidor;
- em [7], vemos que o servidor inicia uma nova sessão. Isto significa que não recebeu nenhum cookie de sessão;
- em [8], inicialização do contador com o valor 77;
- em [9], a página [index] do servidor (9) e a do cliente (10) apresentam, de facto, o mesmo valor do contador;
Agora, vejamos os cookies recebidos pelo navegador:

- em [1], selecione o separador [Application] e, em seguida, a opção [Cookies] [2]. Entre todos os cookies do seu navegador, selecione o do domínio [http://localhost:81];
- em [4], o cookie denominado «session». Se não o tiver, atualize a página [F5]: talvez tenha excedido o seu tempo de validade, que é de 5 minutos;
- em [5], o valor do cookie. Embora não seja muito legível devido à codificação dos caracteres { :, é possível distinguir o valor 77 do contador;
- em [6], o URL do cookie: sempre que este URL for solicitado, o navegador enviará o cookie para o servidor;
- em [7], a hora de validade do cookie. Quando essa hora for ultrapassada, o cookie será eliminado do navegador;
Certifique-se de que possui este cookie. Se não o tiver, atualize a página (F5). Quando tiver a página com o respetivo cookie, atualize novamente a página (F5). Os registos passam então a ser os seguintes:

Desta vez, em [3], o servidor recuperou corretamente o cookie de sessão. Foi o navegador do cliente que lho enviou.
Agora, aumente o contador e, de vez em quando, atualize a página atual (F5), quer seja [index] ou [page1], deverá verificar que o contador não volta a 77, como no exemplo [nuxt-04], mas mantém o valor que tinha no navegador do cliente antes de a página ser recarregada:


Os registos do navegador são, então, os seguintes:

Nota: para os testes, poderá ser necessário eliminar o cookie de sessão [5] armazenado no navegador para recomeçar com uma nova sessão, inicializada pelo servidor, na próxima solicitação enviada a este.
Por fim, vamos mostrar a influência da função [incrementCounter] da página [index] no cookie de sessão armazenado no navegador do cliente:
// gestão de eventos
methods: {
incrementCounter() {
console.log('incrementCounter')
// incremento do contador em 1
this.$store.commit('increment', 1)
// alteração do valor apresentado
this.value = this.$store.state.counter
// armazenamento do valor no cookie de sessão
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
- linha 10: a alteração do contador é refletida no cookie de sessão;
Vamos verificar este ponto. Partimos da seguinte situação:

- em [4], o contador do cookie de sessão reflete corretamente o valor apresentado [1];
Agora, vamos incrementar o contador uma vez: [5]. O cookie de sessão, que era [4], evolui da seguinte forma:

- para [7]; o contador do cookie de sessão passou efetivamente para 84. Para o verificar, é necessário atualizar a visualização [8]. Para tal, selecione outra opção do [Storage] ([9]) e, em seguida, volte a selecionar a opção [8]. O novo valor do cookie de sessão deverá então aparecer;