8. Приклад [nuxt-05]: збереження стану за допомогою сесійного файлу cookie
Мета: ми хочемо, щоб сховище [Vuex] не скидалося при кожному запиті до сервера. Для цього ми використаємо сесійний файл cookie:
- сховище буде ініціалізовано сервером і розміщено ним у сесійному файлі cookie;
- браузер клієнта отримає цей сесійний файл cookie і автоматично надсилатиме його з кожним новим запитом до сервера;
- тоді сервер зможе отримати цей сесійний файл cookie та працювати зі сховищем, яке він містить, — сховищем, оновленим клієнтом;
8.1. Présentation
Проєкт [nuxt-05] спочатку створюється шляхом копіювання проєкту [nuxt-04]:

Ми побачимо, що зміниться лише файл [store / index.js].
Щоб використовувати файли cookie з [nuxt], ми скористаємося модулем [cookie-universal-nuxt], який встановлюємо разом із [yarn] у терміналі VSCode:

- у [4] вводимо команду [yarn add cookie-universal-nuxt];
Таким чином до файлу [package.json] проекту [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. Файл конфігурації [nuxt.config.js]
Щоб [nuxt] міг використовувати файли cookie з [cookie-universal-nuxt], цей модуль потрібно вказати у файлі конфігурації [nuxt.config.js]:
...
],
/*
** Nuxt.js modules
*/
modules: [
// Документація: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Документація: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
...
- у рядку 12 модуль [cookie-universal-nuxt] додається до масиву модулів [6] модуля [nuxt];
У підсумку файл [nuxt.config.js] має такий вигляд:
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: [
// Документація: https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module'
],
/*
** Nuxt.js modules
*/
modules: [
// Документація: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Документація: 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) {}
},
// каталог з вихідним кодом
srcDir: 'nuxt-05',
// маршрутизатор
router: {
// кореневий каталог додатка URL
base: '/nuxt-05/'
},
// сервер
server: {
// порт служби, за замовчуванням 3000
port: 81,
// мережеві адреси, на яких відбувається прослуховування, за замовчуванням localhost: 127.0.0.1
// 0.0.0.0 = усі мережеві адреси комп’ютера
host: 'localhost'
},
// середовище
env: {
maxAge: 60 * 5
}
}
- рядок 79: до файлу додано ключ [env]. Цей ключ є зарезервованим словом. Елементи, оголошені в цьому об’єкті, доступні через об’єкт [context.env] у елементах додатка;
- рядок 80: атрибут [maxAge] визначає максимальний термін дії сесійного файлу cookie, який відраховується від моменту останньої ініціалізації файлу cookie. Цей термін вимірюється в секундах. Тут встановлено термін дії 5 хвилин;
8.3. Принцип збереження даних у сховищі
Файли cookie, що обмінюються між клієнтом і сервером, доступні з обох боків (клієнта та сервера) у:
- [context.app.$cookies] там, де доступний об’єкт [context], тобто практично скрізь;
- [this.$cookies] всередині подання;
Отримати конкретний файл cookie можна за допомогою виразу [...$cookies.get(‘nom_du_cookie’)]. Встановити значення файлу cookie можна за допомогою виразу [...$cookies.set(‘nom_du_cookie’, valeur_du_cookie)].
Принцип роботи постійного файлу cookie сховища буде таким:
- коли сервер ініціалізує сховище у функції [nuxtServerInit], стан сховища буде збережено у файлі cookie з назвою «session»;
- файл cookie «session» стане частиною відповіді HTTP від сервера. Відомо, що браузер повертає серверу файли cookie, які той йому надіслав. Він робить це під час кожного нового запиту до сервера. Також відомо, що сервер надсилає сховище разом із сторінкою, яку він відправляє клієнту;
- у браузері клієнтська програма отримує store, надісланий сервером, і потім виконує свою роботу. Ми подбаємо про те, щоб кожного разу, коли вона змінює store, його новий стан зберігався у файлі cookie «session», записаному браузером;
- якщо користувач ініціює виклик сервера, клієнтський браузер автоматично надішле всі файли cookie, які сервер раніше надіслав йому, зокрема файл cookie з назвою «session»;
- коли після цього виклику сервер знову скине стан сховища, він отримає файл cookie з назвою «session» та ініціалізує стан сховища за допомогою його значення;
- отже, між клієнтом і сервером буде забезпечено безперервність роботи сховища;
8.4. Ініціалізація сховища
Сховище реалізовано у файлі [store / index.js]:
/* eslint-disable no-console */
export const state = () => ({
// лічильник
counter: 0
})
export const mutations = {
// збільшення лічильника на одне значення [inc]
increment(state, inc) {
state.counter += inc
},
// заміна стану
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// хто виконує цей код?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// очікуємо завершення обіцянки
await new Promise(function(resolve, reject) {
// зазвичай тут є асинхронна функція
// її імітують за допомогою очікування протягом однієї секунди
setTimeout(() => {
// ініціалізація сеансу
initStore(store, context)
// успіх
resolve()
}, 1000)
})
}
}
function initStore(store, context) {
// чи є сесійний файл cookie у поточному запиті
const cookies = context.app.$cookies
const session = cookies.get('session')
if (!session) {
// сесія відсутня
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// ініціалізуємо сховище
store.commit('increment', 77)
} else {
console.log("nuxtServerInit, reprise d'une session existante")
// оновлюємо сховище за допомогою сесійного файлу cookie
store.commit('replace', session.store)
}
// зберігання даних записується в сесійний файл cookie
cookies.set('session', { store: store.state }, { path: context.base, maxAge: context.env.maxAge })
// журнал
console.log('initStore terminé, store=', store.state)
}
Коментарі
- рядки 2–5: масив складатиметься з лічильника;
- рядки 9–11: цей лічильник можна буде інкрементувати;
- рядки 13–17: стан регістру можна ініціалізувати з нового стану. Ця функція призначена для демонстрації можливої ініціалізації регістру, коли він не обмежується лише лічильником, як у цьому випадку;
- рядки 21–35: функція [nuxtServerInit] не змінилася;
- рядок 30: коли час очікування в одну секунду минув, регістр ініціалізується за допомогою функції з рядків 38–56;
- рядки 40–41: спочатку отримуємо файл cookie з назвою «session»:
- під час першого запуску додатка та під час першого запиту до сервера цей файл cookie ще не існуватиме. Тоді його буде створено (рядок 53) і надіслано до браузера клієнта;
- під час того самого запуску додатка та під час запитів № 2, 3, ... до сервера цей файл cookie вже існуватиме, оскільки браузер клієнта надсилатиме його разом із кожним новим запитом до сервера;
- під час другого запуску додатка та під час першого запиту до сервера цей файл cookie також може існувати. Адже після завершення етапу 1 файл cookie було збережено в браузері з певним терміном дії. Якщо цей термін дії не минув, файл cookie з назвою «session» буде надіслано разом із першим запитом до сервера
Підсумовуючи, для кожного запиту до сервера: якщо файл cookie «session» вже збережено в браузері клієнта, то сервер отримає його, інакше — ні.
- рядки 42–47: якщо сервер не отримує сесійний файл cookie, то за допомогою рядка 46 ініціалізується масив store;
- потім у рядку 53 створюється файл cookie з назвою «session» і розміщується у відповіді сервера HTTP. Значенням файлу cookie є об’єкт [{ store: store.state }]. Отже, у файл cookie сеансу записується стан сховища, а не саме сховище;
- третій параметр функції [set] — це об’єкт опцій:
- [path] вказує, до якого URL цей файл cookie має бути відправлений. [context.base] — це базовий URL додатка [nuxt-05]. Він визначений у файлі [nuxt.config.js]:
// маршрутизатор
router: {
// кореневий каталог URL додатка
base: '/nuxt-05/'
},
- [maxAge] — це час існування файлу cookie в браузері, вимірюваний у секундах. Після закінчення цього часу браузер більше не надсилає його на сервер. [context.env.maxAge] також повертає значення, записане у файлі [nuxt.config.js]:
[env] — це зарезервоване ключове слово конфігураційного файлу. Тут встановлюється термін дії, що становить 5 хвилин. Цей термін відраховується від моменту останнього отримання браузером сесійного файлу cookie. Після закінчення цього терміну файл cookie більше не надсилатиметься на сервер, який тоді повинен буде розпочати нову сесію;
- рядки 48–50: якщо сервер отримує сесійний файл cookie, то стан сховища ініціалізується за допомогою об’єкта [store] із сесійного файлу cookie. Нагадаємо, що цей об’єкт містить збережений стан сховища;
- далі, у рядку 53, сесійний файл cookie буде розміщено у відповіді, що надсилається клієнтському браузеру:
- функція [get] витягує сесійний файл cookie з запиту, отриманого сервером;
- функція [set] вставляє сесійний файл cookie у відповідь, яку сервер надсилає клієнтському браузеру;
- далі, у рядку 53, сесійний файл cookie буде розміщено у відповіді, що надсилається клієнтському браузеру:
8.5. Збільшення лічильника магазину
Збільшення лічильника на сторінці [index.vue] відбувається наступним чином:
// управління подіями
methods: {
incrementCounter() {
console.log('incrementCounter')
// збільшення лічильника на 1
this.$store.commit('increment', 1)
// зміна значення, що відображається
this.value = this.$store.state.counter
// збереження даних у сесійному файлі cookie
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
На стороні клієнта щоразу, коли змінюється store, його потрібно зберегти у сесійному файлі cookie. Адже користувач може в будь-який момент вручну надіслати запит URL, і тоді потрібно мати можливість надіслати серверу оновлений store. Саме тому в рядку 10, після інкременту лічильника store, його стан зберігається у сесійному файлі cookie:
- файли cookie доступні у властивості [this.$cookies];
- стан масиву [this.$store.state] зберігається у файлі cookie, пов’язаному з ключем [store];
- шлях до файлу cookie — [context.base]. У вікні контекст доступний у [this.$nuxt.context];
- термін дії файлу cookie — [context.env.maxAge], доступний тут у властивості [this.$nuxt.context.env.maxAge];
8.6. Виконання прикладу [nuxt-05]
Запускаємо додаток [nuxt-05]:

Наведені нижче знімки екрана зроблені у браузері Chrome. Ми запитуємо URL [http://localhost:81/nuxt-05/]. Не забудьте про останній символ «/» після «/nuxt-05», інакше ви не отримаєте очікуваних результатів:

- у [4] ми отримали початкове значення store (77);
Розглянемо логи браузера (F12):

- у [5-6] — журнали сервера;
- у [7] бачимо, що сервер запускає нову сесію. Це означає, що він не отримав сесійного файлу cookie;
- у [8] — ініціалізація лічильника зі значенням 77;
- у [9] сторінка [index] на сервері (9) та на клієнті (10) дійсно відображають однакове значення лічильника;
Тепер розглянемо файли cookie, отримані браузером:

- у [1] виберіть вкладку [Application], а потім опцію [Cookies] [2]. Серед усіх файлів cookie у вашому браузері виберіть той, що належить домену [http://localhost:81];
- у [4] — файл cookie з назвою «session». Якщо його немає, перезавантажте сторінку [F5]: можливо, термін його дії, який становить 5 хвилин, вже минув;
- на [5] — значення файлу cookie. Хоча воно не дуже читабельне через кодування символів { :, можна розрізнити значення лічильника 77;
- у [6] — ідентифікатор файлу cookie URL: щоразу, коли буде надходити запит на цей URL, браузер надсилатиме файл cookie на сервер;
- у [7] — час закінчення терміну дії файлу cookie. Коли цей час мине, файл cookie буде видалено з браузера;
Переконайтеся, що у вас є цей файл cookie. Якщо його немає, перезавантажте сторінку (F5). Коли сторінка завантажиться разом із файлом cookie, перезавантажте її ще раз (F5). Тоді записи у логах стануть такими:

Цього разу, у записі [3], сервер успішно отримав сесійний файл cookie. Його надіслав серверу клієнтський браузер.
Тепер збільшуйте значення лічильника, а потім час від часу перезавантажуйте поточну сторінку (F5), будь то [index] чи [page1], ви повинні помітити, що лічильник не повертається до 77, як у прикладі [nuxt-04], а зберігає значення, яке він мав у браузері клієнта до оновлення сторінки:


Журнали браузера виглядають наступним чином:

Примітка: для тестування може знадобитися видалити сесійний файл cookie [5], збережений у браузері, щоб розпочати нову сесію, ініційовану сервером, під час наступного запиту до нього.
Нарешті, продемонструємо вплив функції [incrementCounter] зі сторінки [index] на сесійний файл cookie, збережений у браузері клієнта:
// обробка подій
methods: {
incrementCounter() {
console.log('incrementCounter')
// збільшення лічильника на 1
this.$store.commit('increment', 1)
// зміна відображуваного значення
this.value = this.$store.state.counter
// збереження значення в сесійному файлі cookie
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
- рядок 10: зміна лічильника відображається у сесійному файлі cookie;
Перевіримо це. Виходимо з такої ситуації:

- у [4] лічильник сесійного файлу cookie правильно відображає показане значення [1];
Тепер збільшимо лічильник на одиницю: [5]. Сесійний файл cookie з значенням [4] змінюється наступним чином:

- до [7], лічильник сесійного файлу cookie дійсно перейшов на 84. Щоб це побачити, потрібно оновити вікно [8]. Для цього виберіть інший варіант з [Storage] або [9], а потім знову виберіть варіант [8]. Після цього має з’явитися нове значення сесійного файлу cookie;