document.cookie = 'subject=math';
// Перезаписывает значение существующей cookie
document.cookie = 'subject=physics';
// Добавляет ещё одну cookie
document.cookie = 'tips=1';
document.cookie = 'subject=' + encodeURIComponent('Математика');ж
// %D0%9C%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0
document.cookie = 'subject=math; domain=ege.yandex.ru';
GET /
Host: ege.yandex.ru
GET /
Host: old.ege.yandex.ru
GET /
Host: yandex.ru
GET /
Host: oge.yandex.ru
По умолчанию текущий
Можно установить только на текущий или более специфичный (находясь на yandex.ru нельзя установить cookie для facebook.com)
document.cookie = 'subject=math; path=/tests';
GET /tests/list
GET /tests/page/2
GET /tests-list
GET /
uniq key = name + path + domain
document.cookie = 'subject=math; path=/';
document.cookie = 'subject=math; domain=ege.yandex.ru';
document.cookie = 'subject=math; path=/; domain=ege.yandex.ru';
subject=math; expires=Tue, 19 Apr 2019 00:00:00 GMT
Чтобы удалить, устанавливаем дату устаревания в прошлом
subject=math; expires=Tue, 19 Apr 1970 00:00:00 GMT
document.cookie = 'subject=math; path=/';
console.log(document.cookie);
// subject=math
Если подходят несколько – доступны все в порядке от наиболее специфичной к наименее
document.cookie = 'subject=math; path=/';
document.cookie = 'subject=physics; path=/oge';
console.log(document.cookie);
// subject=physics; subject=math
Cookies.set('subject', 'math', { expires: 7, path: '' });
Cookies.get('subject');
Cookies.remove('subject');
GET / HTTP/1.1
Host: ege.yandex.ru
Cookie: subject=math; tips=1
const express = require('express')
const app = express();
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use((req, res) => {
console.log(req.cookies);
// { subject: 'math', tips: 1 }
});
var express = require('express')
var app = express();
app.use((req, res) => {
res.cookie('subject', 'math', { path: '/tests' });
});
HTTP/1.1 200 OK
Set-Cookie: subject=math; path=/tests
res.cookie('subject', 'math', {
path: '/tests',
httpOnly: true
});
HTTP/1.1 200 OK
Set-Cookie: subject=math; path=/tests; HttpOnly
Не доступны в js-скриптах на клиенте
res.cookie('subject', 'math', {
path: '/tests',
secure: true
});
HTTP/1.1 200 OK
Set-Cookie: subject=math; path=/tests; secure
Не доступны по HTTP
GET / HTTP/1.1
Host: awesome-notes.com
HTTP/1.1 200 OK
<img src="http://awesome-pics.com/awesome-cat.jpg">
GET /awesome-cat.jpg HTTP/1.1
Host: awesome-pics.com
GET / HTTP/1.1
Host: evil-hacker.com
HTTP/1.1 200 OK
<form method="POST" action="https://bank.com/transferMoney">
<input name="amount" value="1000">
</form>
<script>document.forms[0].submit();</script>
POST /transferMoney HTTP/1.1
Host: bank.com
amount=1000
res.cookie('subject', 'math', {
path: '/tests',
sameSite: 'lax' // strict
});
HTTP/1.1 200 OK
Set-Cookie: subject=math; path=/tests; SameSite=Lax
Cookie не передаются в качестве third-party
<a href="https://yandex.ru/">Открыть Яндекс</a>
<link rel="prerender" href="https://yandex.ru/"/>
<form method="GET" action="https://yandex.ru/">
<form method="POST" action="https://yandex.ru/">
<iframe src="https://yandex.ru/">
$.get("https://yandex.ru/")
<img src="https://yandex.ru/logo.png">
Устаревание
Доступ с сервера
4Kb
Передаются с каждым запросом
Инициализация состояния клиента
Авторизация
Для статики используйте
cookieless домены (CDN)
В cookie храните id, по которому можно на сервере получить полные данные
Обфусцируйте 01100101
Using the Same-Site Cookie Attribute to Prevent CSRF Attacks
Не передаёт данные на сервер
Ограничение в 10Mb
localStorage.setItem('volume', 8);
localStorage.getItem('volume'); // "8"
localStorage.removeItem('volume')
localStorage.clear();
localStorage.volume = 8;
localStorage.setItem('repeat', 1);
// QUOTA_EXCEEDED_ERROR
Хранит строки, а не объекты
localStorage.setItem('options', { volume: 8 });
localStorage.getItem('options'); // "[object Object]"
localStorage.setItem('options', JSON.stringify({ volume: 8 }));
window.addEventListener('storage', event => {
console.log(event);
});
{
key: 'volume',
oldValue: '8',
newValue: '6'
}
try {
localStorage.setItem('options', '8');
} catch (error) {
console.error(error);
// SecurityError: The operation is insecure
}
Хранение настроек
Хранение промежуточных данных
Строго ограничено источником (origin)
Синхронный интерфейс
Асинхронный интерфейс к SQLite базе
const db = openDatabase('my-app', '1.0', null, 1024 * 1024);
db.transaction(tr => {
tr.executeSql(`
create table if not exists notes(
name TEXT
)
`);
tr.executeSql(`
insert into notes(name)
values("films")
`);
}, console.error);
Строго ограничено источником (origin)
Нет ограничений на размер*
Асинхронное
Не реляционная, а object storage
Не SQL, а API
// Указываем название и версию
const requestDb = indexedDB.open('my-app', 1);
requestDb.onerror = event => {
console.log(event.target.errorCode);
};
requestDb.onsuccess = event => {
// Получаем доступ к базе
const db = event.target.result;
};
// Будет вызван в первый раз и когда сменилась версия
requestDb.onupgradeneeded = event => {
const db = event.target.result;
const oldVersion = event.oldVersion;
// Инструкции к миграции на вторую версию
if (oldVersion < 2) {
// Создаём хранилище для заметок
db.createObjectStore('notes', {
keyPath: 'id',
autoIncrement: true
});
}
}
// Указываем на какие Store распространяется транзакция
const transaction = db.transaction(['notes'], 'readwrite');
// В рамках транзакции получаем ссылку на Store
const store = transaction.objectStore('notes');
// Добавляем заметку
const request = store.add({
id: 'films'
name: 'films'
});
request.onerror = err => console.error;
transaction.abort();
Транзакция остаётся живой, если есть активные запросы к концу event loop цикла
// Указываем на какие Store распространяется транзакция
const transaction = db.transaction(['notes'], 'readonly');
// В рамках транзакции получаем ссылку на Store
const store = transaction.objectStore('notes')
// Получаем используя значения ключа (id)
const request = store.get('films')
request.onsuccess = note => console.log
Может быть несколько паралелльных readonly транзакций, но только одна readwrite
const transaction = db.transaction(['notes'], 'readonly');
const store = transaction.objectStore('notes')
const requestCursor = store.openCursor();
requestCursor.onsuccess = event => {
const cursor = event.target.result;
// Выводим данные текущего объекта
console.log(cursor.key, cursor.value);
// Переходим к следующему
cursor.continue();
};
const db = event.target.result;
const store = db.createObjectStore('notes', {
keyPath: 'id',
autoIncrement: true
});
store.createIndex(
'name', // Название
'name', // Поле или массив ['name', 'keywords']
{ unique: false } // Параметры
);
const transaction = db.transaction(['notes'], 'readonly');
const store = transaction.objectStore('notes')
const requestCursor = store
.index('name')
.openCursor(IDBKeyRange.only(['films', true]));
// ...
const db = new Dexie('MyDatabase');
db
.version(1)
.stores({
notes: 'name'
});
db
.open()
.catch(error => console.error);
db
.notes
.where('name')
.equals(['films'])
.each(note => {
console.log(note.name);
});