8. Exemplo [nuxt-05]: Persistência do store com um cookie de sessão
Objetivo: Queremos que o armazenamento [Vuex] não seja reiniciado a cada solicitação ao servidor. Para isso, utilizaremos um cookie de sessão:
- o armazenamento será inicializado pelo servidor e armazenado por este num cookie de sessão;
- o navegador do cliente receberá este cookie de sessão e enviá-lo-á automaticamente com cada nova solicitação ao servidor;
- o servidor pode então recuperar este cookie de sessão e trabalhar com o armazenamento que ele contém, um armazenamento atualizado pelo cliente;
8.1. Visão geral
O projeto [nuxt-05] é inicialmente criado através da clonagem do projeto [nuxt-04]:

Veremos que apenas o ficheiro [store/index.js] será alterado.
Para utilizar cookies com o [nuxt], utilizaremos o módulo [cookie-universal-nuxt], que instalamos com o [yarn] num terminal do VSCode:

- Em [4], digite o comando [yarn add cookie-universal-nuxt];
Um novo módulo é assim adicionado 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] utilize cookies do [cookie-universal-nuxt], deve 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 à matriz de módulos [nuxt] [6];
O ficheiro [nuxt.config.js] fica, por fim, da seguinte forma:
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',
// Doc: 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) {}
},
// source code directory
srcDir: 'nuxt-05',
// router
router: {
// application URL root
base: '/nuxt-05/'
},
// server
server: {
// service port, default 3000
port: 81,
// network addresses listened to, default localhost: 127.0.0.1
// 0.0.0.0 = all the machine's network addresses
host: 'localhost'
},
// environment
env: {
maxAge: 60 * 5
}
}
- linha 79: adicionámos a chave [env] ao ficheiro. Esta chave é uma palavra reservada. Os elementos declarados neste objeto estão disponíveis através do objeto [context.env] nos elementos da aplicação;
- linha 80: o atributo [maxAge] será o tempo de vida máximo do cookie de sessão, medido a partir da última vez que o cookie foi inicializado. Esta duração é expressa 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] sempre que o objeto [context] estiver disponível, ou seja, em quase todo o lado;
- [this.$cookies] dentro de uma vista;
Um cookie específico é recuperado utilizando a expressão [...$cookies.get('cookie_name')]. O valor de um cookie é definido utilizando a expressão [...$cookies.set('cookie_name', cookie_value)].
O princípio subjacente ao cookie de persistência da loja é o seguinte:
- quando o servidor inicializa o store na função [nuxtServerInit], o estado do store será armazenado num cookie chamado «session»;
- o cookie «session» passará então a fazer parte da resposta HTTP do servidor. Sabemos que um navegador reenvia ao servidor os cookies que o servidor lhe enviou. Faz isso com cada novo pedido que faz ao servidor. Sabemos também que o servidor envia o armazenamento dentro da página que envia ao cliente;
- dentro do navegador, a aplicação cliente recupera o store enviado pelo servidor e, em seguida, executa as suas tarefas. Asseguraremos que, sempre que modificar o store, o seu novo estado seja armazenado no cookie «session» guardado pelo navegador;
- Se o utilizador forçar uma solicitação ao servidor, o navegador do cliente reenviará automaticamente todos os cookies que o servidor lhe enviou anteriormente, incluindo o cookie denominado «session»;
- quando, na sequência desta solicitação, o servidor reiniciar o armazenamento novamente, ele recuperará o cookie «session» e inicializará o estado do armazenamento com o seu valor;
- haverá, portanto, continuidade do armazenamento 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 = () => ({
// meter
counter: 0
})
export const mutations = {
// increment counter by one [inc] value
increment(state, inc) {
state.counter += inc
},
// state replacement
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// who executes this code?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// waiting for a promise to be fulfilled
await new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a one-second wait
setTimeout(() => {
// init session
initStore(store, context)
// success
resolve()
}, 1000)
})
}
}
function initStore(store, context) {
// is there a session cookie in the current request?
const cookies = context.app.$cookies
const session = cookies.get('session')
if (!session) {
// no existing session
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// initialize the blind
store.commit('increment', 77)
} else {
console.log("nuxtServerInit, reprise d'une session existante")
// update the store with the session cookie
store.commit('replace', session.store)
}
// put the store in the session cookie
cookies.set('session', { store: store.state }, { path: context.base, maxAge: context.env.maxAge })
// log
console.log('initStore terminé, store=', store.state)
}
Comentários
- linhas 2-5: a loja consistirá num balcão;
- linhas 9–11: este contador pode ser incrementado;
- linhas 13-17: o estado do store pode ser inicializado a partir de um novo estado. Esta função foi incluída para demonstrar uma possível inicialização do store quando este não se limita apenas a um contador, como neste exemplo;
- linhas 21–35: a função [nuxtServerInit] não sofreu alterações;
- linha 30: quando o tempo limite de um segundo tiver decorrido, a loja é inicializada utilizando a função nas linhas 38–56;
- linhas 40–41: começamos por recuperar o cookie denominado «session»:
- durante a primeira execução da aplicação e durante o primeiro pedido feito ao servidor, este cookie ainda não existirá. Será então criado (linha 53) e enviado para o navegador do cliente;
- durante a mesma execução da aplicação e durante as solicitações n.º 2, n.º 3, ... feitas ao servidor, este cookie existirá porque o navegador do cliente o reenviará com cada nova solicitação feita ao servidor;
- Durante uma segunda execução da aplicação e na primeira solicitação feita ao servidor, este cookie também pode existir. De facto, no final do passo 1, o cookie foi armazenado no navegador com um tempo de vida específico. Se este tempo de vida não tiver expirado, o cookie chamado «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 store é inicializado pela linha 46;
- depois, na linha 53, será criado um cookie chamado «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] especifica a URL para a qual este cookie deve ser enviado. [context.base] é a URL base da aplicação [nuxt-05]. Esta é definida no ficheiro [nuxt.config.js]:
// router
router: {
// application URL root
base: '/nuxt-05/'
},
- [maxAge] é o tempo de vida do cookie em segundos no navegador. Após esse tempo, o navegador deixa de o enviar de volta ao servidor. [context.env.maxAge] devolve um valor especificado no ficheiro [nuxt.config.js]:
[env] é uma palavra-chave reservada no ficheiro de configuração. Aqui, definimos o tempo de vida para 5 minutos. Esta duração é medida a partir da última vez que o navegador recebeu o cookie de sessão. Após este período, o cookie não será reenviado para o 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 da loja é inicializado com o objeto [store] proveniente do cookie de sessão. Recorde-se que este objeto contém o estado guardado da loja;
- depois, na linha 53, o cookie de sessão é colocado na resposta enviada ao navegador do cliente:
- a função [get] recupera o cookie de sessão da solicitação recebida pelo servidor;
- a função [set] coloca o cookie de sessão na resposta que o servidor envia ao navegador do cliente;
- depois, na linha 53, o cookie de sessão é colocado na resposta enviada ao navegador do cliente:
8.5. Incrementar o contador da loja
O incremento do contador na página [index.vue] funciona da seguinte forma:
// event management
methods: {
incrementCounter() {
console.log('incrementCounter')
// counter increment of 1
this.$store.commit('increment', 1)
// change display value
this.value = this.$store.state.counter
// store in session cookie
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
No lado do cliente, sempre que o store é modificado, deve ser guardado no cookie de sessão. Isto porque o utilizador pode solicitar manualmente um URL a qualquer momento, e temos de ser capazes de enviar um store atualizado para o servidor. É por isso que, na linha 10, após incrementar o 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];
- A duração do cookie é [context.env.maxAge], que está disponível aqui na propriedade [this.$nuxt.context.env.maxAge];
8.6. Executar o exemplo [nuxt-05]
Lançamos a aplicação [nuxt-05]:

As capturas de ecrã seguintes foram tiradas no navegador Chrome. Solicitamos o URL [http://localhost:81/nuxt-05/]. Não se esqueça do / final após /nuxt-05, caso contrário não obterá os resultados esperados:

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

- em [5-6], os registos do servidor;
- em [7], vemos que o servidor está a iniciar uma nova sessão. Isto significa que não recebeu um 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 página [index] do cliente (10) apresentam ambas o mesmo valor do contador;
Agora, vamos ver os cookies recebidos pelo navegador:

- Em [1], selecione o separador [Aplicação] e, em seguida, a opção [Cookies] [2]. De todos os cookies no seu navegador, selecione aquele do domínio [http://localhost:81];
- em [4], o cookie chamado «session». Se não o vir, atualize a página [F5]: talvez o seu tempo de vida de 5 minutos tenha expirado;
- 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 expiração do cookie. Assim que este tempo tiver passado, o cookie será eliminado do navegador;
Certifique-se de que tem este cookie. Se não o tiver, atualize a página (F5). Assim que tiver a página com o cookie, atualize a página novamente (F5). Os registos ficarão então assim:

Desta vez, em [3], o servidor recuperou com sucesso o cookie de sessão. Este foi-lhe enviado pelo navegador do cliente.
Agora, aumente o contador e, de vez em quando, atualize a página atual (F5) — seja ela [index] ou [page1] — e deverá notar 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 atualizada:


Os registos do navegador ficam então assim:

Nota: Para efeitos de teste, poderá ser necessário eliminar [5] o cookie de sessão armazenado no navegador para iniciar uma nova sessão, inicializada pelo servidor, durante o próximo pedido de acesso ao mesmo.
Por fim, vamos demonstrar o efeito da função [incrementCounter] na página [index] sobre o cookie de sessão armazenado no navegador do cliente:
// event management
methods: {
incrementCounter() {
console.log('incrementCounter')
// counter increment of 1
this.$store.commit('increment', 1)
// change display value
this.value = this.$store.state.counter
// store in session cookie
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
- Linha 10: A alteração no contador é refletida no cookie de sessão;
Vamos verificar isto. Começamos com a seguinte situação:

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

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