...

воскресенье, 23 мая 2021 г.

[Перевод] Современный подход к работе с куки

Вы когда-нибудь работали с куки? Казалось ли вам при этом, что их использование организовано просто и понятно? Полагаю, что в работе с куки есть множество нюансов, о которых стоит знать новичкам.


Свойство document.cookie


Взглянем на классический способ работы с куки. Соответствующая спецификация существует, благодаря Netscape, с 1994 года. Компания Netscape реализовала свойство document.cookie в Netscape Navigator в 1996 году. Вот определение куки из тех времён:

Куки — это небольшой фрагмент информации, хранящийся на клиентской машине в файле cookies.txt.

Главу про document.cookie даже можно найти во втором издании книги «Javascript. The Definitive Guide», которое вышло в январе 1997 года. Это было 24 года тому назад. И мы всё ещё пользуемся тем же старым способом работы с куки ради обратной совместимости.

Как же это выглядит?

Получение куки

const cookies = document.cookie;
// возвращает "_octo=GH1.1.123.456; tz=Europe%2FMinsk" on GitHub

Да, именно так всё и делается. В нашем распоряжении оказывается строка со всеми значениями, хранящимися в куки-файле, разделёнными точкой с запятой.

Как вытащить из этой строки отдельное значение? Если вам кажется, что для этого надо самостоятельно разбить строку на части — знайте, что так оно и есть:

function getCookieValue(name) {
    const cookies = document.cookie.split(';');
    const res = cookies.find(c => c.startsWith(name + '='));
    if (res) {
        return res.substring(res.indexOf('=') + 1);
    }
}

Как узнать о том, когда истекает срок действия какого-нибудь из куки? Да, в общем-то, никак.

А как узнать домен, с которого установлен какой-нибудь куки? Тоже никак.

Правда, если надо, можно распарсить HTTP-заголовок Cookie.

Установка куки

document.cookie = 'theme=dark';

Вышеприведённая команда позволяет создать куки с именем theme, значением которого является dark. Хорошо. Значит ли это, что document.cookie — это строка. Нет, не значит. Это — сеттер.
document.cookie = 'mozilla=netscape';

Эта команда не перезапишет куки с именем theme. Она создаст новый куки с именем mozilla. Теперь у нас имеются два куки.

По умолчанию срок действия куки, созданного так, как показано выше, истекает после закрытия браузера. Но при создании куки можно указать срок его действия:

document.cookie = 'browser=ie11; expires=Tue, 17 Aug 2021 00:00:00 GMT';

Да. Это — как раз то, что мне нужно — подбирать дату и время истечения срока действия куки в формате GMT каждый раз, когда нужно установить куки. Ладно, давайте воспользуемся для решения этой задачи JavaScript-кодом:
const date = new Date();
date.setTime(date.getTime() + (30 * 24 * 60 * 60 * 1000)); // здорово-то как
document.cookie = `login=mefody; expires=${date.toUTCString()}; path=/`;

Но, к счастью, у нас есть и другой способ установки момента истечения срока действия куки:
document.cookie = 'element=caesium; max-age=952001689';

Свойство max-age представляет собой срок «жизни» куки в секундах. Соответствующее значение имеет более высокий приоритет, чем то, которое задано с помощью свойства expires.

И не надо забывать о свойствах path и domain. По умолчанию куки устанавливаются для текущего расположения и текущего хоста. Если надо установить куки для всего домена — надо будет добавить к команде установки куки конструкцию такого вида:

; path=/; domain=example.com

Удаление куки

document.cookie = 'login=; expires=Thu, 01 Jan 1970 00:00:00 GMT';

Для удаления куки надо установить дату и время истечения срока действия куки на какой-нибудь момент из прошлого. Для того чтобы всё точно сработало бы — тут рекомендуется использовать начало эпохи Unix.

Работа с куки в сервис-воркерах


Это просто невозможно. Дело в том, что работа с document.cookie — это синхронная операция, в результате воспользоваться ей в сервис-воркере нельзя.

Cookie Store API


Существует черновик стандарта одного замечательно API, направленного на работу с куки, который способен значительно облегчить нам жизнь в будущем.

Во-первых — это асинхронный API, а значит — пользоваться им можно, не блокируя главный поток. Применять его можно и в сервис-воркерах.

Во-вторых — этот API устроен гораздо понятнее, чем существующий механизм работы с куки.

▍Получение куки

const cookies = await cookieStore.getAll();
const sessionCookies = await cookieStore.getAll({
    name: 'session_',
    matchType: 'starts-with',
});

Метод getAll возвращает массив, а не строку. Именно этого я и жду, когда пытаюсь получить некий список.
const ga = await cookieStore.get('_ga');
/**
{
    "domain": "mozilla.org",
    "expires": 1682945254000,
    "name": "_ga",
    "path": "/",
    "sameSite": "lax",
    "secure": false,
    "value": "GA1.2.891784426.1616320570"
}
*/

А вот — приятная неожиданность. Можно узнать и дату истечения срока действия куки, и сведения о домене и пути, и при этом не пользоваться никакими хаками.

▍Установка куки

await cookieStore.set('name', 'value');

Или так:
await cookieStore.set({
    name: 'name',
    value: 'value',
    expires: Date.now() + 86400,
    domain: self.location.host,
    path: '/',
    secure: self.location.protocol === 'https:',
    httpOnly: false,
});

Мне очень нравится этот синтаксис!

▍Удаление куки

await cookieStore.delete('ie6');

Или можно, как раньше, установить дату истечения срока действия куки на некий момент в прошлом, но не вижу причины поступать именно так.

▍События куки

cookieStore.addEventListener('change', (event) => {
    for (const cookie in event.changed) {
        console.log(`Cookie ${cookie.name} changed to ${cookie.value}`);
    }
});

Как видите, теперь у нас есть возможность подписываться на изменения куки, не занимаясь опросом document.cookie, блокирующим главный поток. Фантастика!

▍Сервис-воркеры

// service-worker.js

await self.registration.cookies.subscribe([
    {
        name: 'cookie-name',
        url: '/path-to-track',
    }
]);

self.addEventListener('cookiechange', (event) => {
    // обработка изменений
});

Можно ли пользоваться этим API прямо сейчас?


Хотя этим API уже можно пользоваться, но тут надо проявлять осторожность. Cookie Store API работоспособно в Chrome 87+ (Edge 87+, Opera 73+). В других браузерах можно воспользоваться полифиллом, который, правда, не возвращает полной информации о куки, как это сделано в настоящем API. Прогрессивные улучшения — это вещь.

И учитывайте, что спецификация этого API всё ещё находится в статусе «Draft Community Group Report». Но если в вашем проекте важен хороший «опыт разработчика» — попробуйте современный способ работы с куки.

Пробовали ли вы работать с куки по-новому?


Adblock test (Why?)

Комментариев нет:

Отправить комментарий