Skip to content

8. Exemplo [nuxt-05]: persistência do store com um cookie de sessão

Objetivo: gostaríamos que o store [Vuex] não fosse reinicializado a cada solicitação ao servidor. Para isso, vamos utilizar um cookie de sessão:

  • o armazenamento será inicializado pelo servidor e colocado por ele em um cookie de sessão;
  • o navegador do cliente receberá esse cookie de sessão e o enviará automaticamente a cada nova solicitação ao servidor;
  • o servidor poderá então recuperar esse cookie de sessão e trabalhar com o armazenamento que ele contém, um armazenamento atualizado pelo cliente;

8.1. Présentation

O projeto [nuxt-05] é obtido inicialmente por meio da cópia do projeto [nuxt-04]:

Image

Veremos que apenas o arquivo [store / index.js] será alterado.

Para utilizar cookies com o [nuxt], utilizaremos o módulo [cookie-universal-nuxt], que instalamos junto com o [yarn] em um terminal VSCode:

Image

  • no [4], digitamos o comando [yarn add cookie-universal-nuxt];

Assim, um novo módulo é adicionado ao arquivo [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 arquivo de configuração [nuxt.config.js]

Para que o [nuxt] possa utilizar os cookies do [cookie-universal-nuxt], é necessário declarar esse módulo no arquivo 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] do [nuxt];

O arquivo [nuxt.config.js] fica, no final, 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',
    // 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',
  // roteador
  router: {
    // raiz da aplicação URL
    base: '/nuxt-05/'
  },
  // servidor
  server: {
    // porta de serviço, 3000 por padrão
    port: 81,
    // endereços de rede monitorados, por padrão localhost: 127.0.0.1
    // 0.0.0.0 = todos os endereços de rede da máquina
    host: 'localhost'
  },
  // ambiente
  env: {
    maxAge: 60 * 5
  }
}
  • linha 79: foi adicionada a chave [env] ao arquivo. Essa chave é uma palavra reservada. Os elementos declarados nesse objeto estão disponíveis a partir do objeto [context.env] nos elementos do aplicativo;
  • linha 80: o atributo [maxAge] corresponderá ao tempo de vida máximo do cookie de sessão, medido a partir da última vez em que o cookie foi inicializado. Esse 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 todos os lugares;
  • [this.$cookies] dentro de uma visualização;

Obtém-se um cookie específico com a expressão [...$cookies.get(‘nom_du_cookie’)]. Define-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 store será o seguinte:

  • quando o servidor inicializar o armazenamento na função [nuxtServerInit], o estado do armazenamento será armazenado em um cookie chamado ‘session’;
  • o cookie ‘session’ passará então a fazer parte da resposta HTTP do servidor. Sabe-se que um navegador reenvia ao servidor os cookies que este lhe enviou. Ele faz isso a 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;
  • dentro do navegador, o aplicativo cliente recupera o store enviado pelo servidor e, em seguida, executa suas tarefas. Faremos com que, sempre que ele modificar o store, o novo estado deste seja armazenado no cookie “session” registrado pelo navegador;
  • se o usuário forçar uma chamada ao servidor, o navegador cliente reenviará automaticamente todos os cookies que o servidor lhe enviou anteriormente, incluindo o cookie chamado “session”;
  • quando, após essa chamada, o servidor reinicializar o armazenamento novamente, ele recuperará o cookie chamado “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 store

O store está implementado no arquivo [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 state
  replace(state, newState) {
    for (const attr in newState) {
      state[attr] = newState[attr]
    }
  }
}

export const actions = {
  async nuxtServerInit(store, context) {
    // quem está executando esse código?
    console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
    // aguardando o término de uma promessa
    await new Promise(function(resolve, reject) {
      // normalmente, temos aqui uma função assíncrona
      // simulamos isso com uma espera de um segundo
      setTimeout(() => {
        // inicialização da sessão
        initStore(store, context)
        // sucesso
        resolve()
      }, 1000)
    })
  }
}

function initStore(store, context) {
  // existe um cookie de sessão na solicitação atual
  const cookies = context.app.$cookies
  const session = cookies.get('session')
  if (!session) {
    // não há sessão existente
    console.log("nuxtServerInit, initialisation d'une nouvelle session")
    // inicializando o armazenamento
    store.commit('increment', 77)
  } else {
    console.log("nuxtServerInit, reprise d'une session existante")
    // atualizando o armazenamento com o cookie de sessão
    store.commit('replace', session.store)
  }
  // colocando o store no cookie de sessão
  cookies.set('session', { store: store.state }, { path: context.base, maxAge: context.env.maxAge })
  // registro
  console.log('initStore terminé, store=', store.state)
}

Comentários

  • linhas 2-5: o store será composto por um contador;
  • linhas 9-11: esse contador poderá ser incrementado;
  • linhas 13-17: o estado do store poderá ser inicializado a partir de um novo estado. Essa função serve para mostrar uma possível inicialização do store 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 se esgota, inicializa-se o register utilizando a função das linhas 38-56;
  • linhas 40-41: começamos recuperando o cookie chamado “session”:
    1. na primeira execução do aplicativo e na primeira solicitação feita ao servidor, esse cookie ainda não existirá. Ele será então criado (linha 53) e enviado ao navegador do cliente;
    2. na mesma execução do aplicativo e nas solicitações nº 2, 3, ... feitas ao servidor, esse cookie já existirá, pois o navegador do cliente o reenviará a cada nova solicitação feita ao servidor;
    3. durante uma segunda execução do aplicativo e na primeira solicitação feita ao servidor, esse cookie também pode existir. De fato, ao final da etapa 1, o cookie foi armazenado no navegador com um determinado tempo de vida. Se esse 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 solicitação feita ao servidor: se o cookie “session” já estiver armazenado no navegador do cliente, o servidor o receberá; caso contrário, não o receberá.

  • linhas 42-47: se o servidor não receber o cookie de sessão, o store será inicializado pela linha 46;
    • em seguida, na linha 53, um cookie chamado “session” será criado e incluído na resposta HTTP do servidor. O valor do cookie é o objeto [{ store: store.state }]. Portanto, é o estado do store — e não o próprio store — que é colocado no cookie de sessão;
    • o terceiro parâmetro da função [set] é um objeto de opções:
      • [path] indica a qual URL esse cookie deverá ser reenviado. [context.base] é o URL básico do aplicativo [nuxt-05]. Este está definido no arquivo [nuxt.config.js]:

  // roteador
  router: {
    // raiz dos URL do aplicativo
    base: '/nuxt-05/'
},
      • [maxAge] é o tempo de vida, em segundos, do cookie no navegador. Após esse período, o navegador não o reenvia mais ao servidor. [context.env.maxAge], por sua vez, retorna um valor registrado no arquivo [nuxt.config.js]:
1
2
3
4
// ambiente
  env: {
    maxAge: 60 * 5
}

[env] é uma palavra-chave reservada do arquivo de configuração. Aqui, define-se a validade para 5 minutos. Esse tempo é medido a partir da última vez em que o navegador recebeu o cookie de sessão. Após esse período, o cookie não será reenviado ao servidor, que deverá, então, iniciar uma nova sessão;

  • linhas 48-50: se o servidor receber o cookie de sessão, o estado do store será inicializado com o objeto [store] do cookie de sessão. Vale lembrar que esse objeto contém o estado salvo do store;
    • em seguida, na linha 53, o cookie de sessão será incluído na resposta enviada ao navegador do cliente:
      • a função [get] busca 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;

8.5. Incremento do contador da loja

O incremento do contador na página [index.vue] ocorre da seguinte maneira:


  // gerenciamento de eventos
  methods: {
    incrementCounter() {
      console.log('incrementCounter')
       // incremento do contador em 1
      this.$store.commit('increment', 1)
       // alteração do valor exibido
      this.value = this.$store.state.counter
       // salvamento do valor armazenado 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 o store for alterado, é necessário salvá-lo no cookie de sessão. De fato, o usuário pode solicitar manualmente uma URL a qualquer momento, e, nesse caso, deve ser possível enviar ao servidor um store atualizado. É por isso que, na linha 10, após o incremento do contador do store, salvamos o estado deste no cookie de sessão:

  • os cookies estão disponíveis na propriedade [this.$cookies];
  • o estado do store [this.$store.state] é salvo no cookie associado à chave [store];
  • o caminho do cookie é [context.base]. Em uma visualização, 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 o aplicativo [nuxt-05]:

Image

As capturas de tela a seguir foram feitas no navegador Chrome. Solicitamos o URL [http://localhost:81/nuxt-05/]. Não se esqueça do último / após /nuxt-05, caso contrário, você não obterá os resultados esperados:

Image

  • em [4], obtivemos o valor inicial do store (77);

Vamos examinar os logs do navegador (F12):

Image

  • em [5-6], os logs do servidor;
  • em [7], vemos que o servidor inicia uma nova sessão. Isso significa que ele 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) exibem, de fato, o mesmo valor do contador;

Agora, vamos examinar os cookies recebidos pelo navegador:

Image

  • em [1], selecione a aba [Application] e, em seguida, a opção [Cookies] [2]. Entre todos os cookies do seu navegador, selecione aquele do domínio [http://localhost:81];
  • em [4], o cookie chamado “session”. Se você não o tiver, atualize a página [F5]: talvez você tenha excedido 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 esse URL for solicitado, o navegador enviará o cookie ao servidor;
  • em [7], a hora de validade do cookie. Quando essa hora for ultrapassada, o cookie será excluído do navegador;

Certifique-se de ter esse cookie. Se você não o tiver, atualize a página (F5). Quando a página estiver com o cookie, atualize-a novamente (F5). Os registros ficarão então da seguinte forma:

Image

Desta vez, em [3], o servidor recuperou corretamente o cookie de sessão. Foi o navegador do cliente que o enviou a ele.

Agora, aumente o contador e, de vez em quando, atualize a página atual (F5), seja [index] ou [page1], você deve observar que o contador não volta para 77, como no exemplo [nuxt-04], mas mantém o valor que tinha no navegador do cliente antes da atualização da página:

Image

Image

Os logs do navegador ficam, então, assim:

Image

Observação: para os testes, talvez seja necessário excluir o cookie de sessão [5] armazenado no navegador para iniciar uma nova sessão, inicializada pelo servidor, na próxima solicitação enviada a ele.

Por fim, vamos mostrar a influência da função [incrementCounter] da página [index] sobre o cookie de sessão armazenado no navegador do cliente:


// gerenciamento de eventos
  methods: {
    incrementCounter() {
      console.log('incrementCounter')
      // incremento do contador em 1
      this.$store.commit('increment', 1)
      // alteração do valor exibido
      this.value = this.$store.state.counter
      // salvamento do valor armazenado 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 esse ponto. Partimos da seguinte situação:

Image

  • 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, que era [4], sofre a seguinte alteração:

Image

  • para [7]; o contador do cookie de sessão passou, de fato, para 84. Para verificar isso, é preciso atualizar a visualização [8]. Para isso, selecione outra opção do [Storage] ([9]) e, em seguida, selecione novamente a opção [8]. O novo valor do cookie de sessão deverá então aparecer;