Work with cookies the modern way
Have you ever worked with cookies? Did you find working with them obvious? I think it has a lot of nuances for newbies.
document.cookie
Let’s take a look at the classic way to work with cookies. We’ve had cookies in the specification since 1994, thanks to Netscape. Netscape implemented document.cookie
in 1996 in their Netscape Navigator. Look at this definition of a cookie from those days.
A cookie is a small piece of information stored on the client machine in the
cookies.txt
file.
You can even find the chapter about document.cookie
in the “Javascript. The Definitive Guide. Second Edition, January 1997”. 24 years ago. And we are still using that old way to work with cookies because of backward compatibility.
So, what’s the way?
Get cookies
const cookies = document.cookie;
// returns "_octo=GH1.1.123.456; tz=Europe%2FMinsk" on GitHub
Yeah, that’s it. It returns a string with all cookies separated by ;
.
How to get a single cookie value? Right, split the string manually.
function getCookieValue(name) {
const cookies = document.cookie.split(';');
const res = cookies.find(c => c.startsWith(name + '='));
if (res) {
return res.substring(res.indexOf('=') + 1);
}
}
How to get some cookie expiration date? No way.
How to get some cookie domain? No way.
You can parse the HTTP Cookie
header if you want.
Set cookies
document.cookie = 'theme=dark';
It creates the cookie named theme
with the value equals dark
. Ok, does it mean that document.cookie
is a string? Nope. It’s a setter.
document.cookie = 'mozilla=netscape';
It doesn’t rewrite the old cookie named theme
, it creates the new one named mozilla
. Now you have two cookies.
By default, a created cookie expires when the browser is closed. You can set the expiration date.
document.cookie = 'browser=ie11; expires=Tue, 17 Aug 2021 00:00:00 GMT';
Yeah, right, that’s what I want, calculate the expiration date in GMT format every time I want to set a cookie. Ok, let’s write some more JavaScript.
const date = new Date();
date.setTime(date.getTime() + (30 * 24 * 60 * 60 * 1000)); // love it
document.cookie = `login=mefody; expires=${date.toUTCString()}; path=/`;
Fortunately, we have another option to set the cookie expiration.
document.cookie = 'element=caesium; max-age=952001689';
The max-age
part is a lifetime of a cookie in seconds and it has more priority than the expires
part.
Don’t forget about path
and domain
. By default, the cookie is set for the current location and the current host. If you need to set the cookie for the entire domain, add ; path=/; domain=example.com
.
Delete cookies
document.cookie = 'login=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
To delete the cookie you should set its expiration date to some past date. To be sure that it will be deleted, use the Unix epoch start.
Service Workers
No. Just no. Working with document.cookie
is a synchronous operation, so you can’t use it in service workers.
Cookie Store API
There is a draft of an awesome API that can help us to avoid a lot of pain in the future. It’s Cookie Store API.
Firstly, it’s an async API. It means you can use it without blocking the main thread. And service workers can use them too.
Secondly, it’s more clear for understanding.
Get cookies
const cookies = await cookieStore.getAll();
const sessionCookies = await cookieStore.getAll({
name: 'session_',
matchType: 'starts-with',
});
The method getAll
returns an array, not a string. That’s what I expect when I try to get a list of something.
const ga = await cookieStore.get('_ga');
/**
{
"domain": "mozilla.org",
"expires": 1682945254000,
"name": "_ga",
"path": "/",
"sameSite": "lax",
"secure": false,
"value": "GA1.2.891784426.1616320570"
}
*/
Whoa! I can get the expiration date, domain and path info without hacks!
Set cookies
await cookieStore.set('name', 'value');
or
await cookieStore.set({
name: 'name',
value: 'value',
expires: Date.now() + 86400,
domain: self.location.host,
path: '/',
secure: self.location.protocol === 'https:',
httpOnly: false,
});
Love this syntax!
Delete cookies
await cookieStore.delete('ie6');
Or you can set the expiration date to the past date if you want, but what for?
Cookie events
cookieStore.addEventListener('change', (event) => {
for (const cookie in event.changed) {
console.log(`Cookie ${cookie.name} changed to ${cookie.value}`);
}
});
Yeah, you will have a possibility to subscribe to cookies changes without thread blocking polling. Fantastic!
Service Workers
// service-worker.js
await self.registration.cookies.subscribe([
{
name: 'cookie-name',
url: '/path-to-track',
}
]);
self.addEventListener('cookiechange', (event) => {
// process the changes
});
Can I use it right now?
Be careful, but you can. The Cookie Store API works in Chrome 87+ (Edge 87+, Opera 73+). For other browsers, you can use the polyfill that doesn’t return the full cookie info as an original API. Progressive enhancement is the thing.
Keep in mind that this API specification is still in “Draft Community Group Report” status. But if your DX is an important thing for the project, try the modern way.
Sources
- Netscape cookies
- Cookie Store API Spec
- Cookie Store API Explainer
- Chrome Status: Cookie Store API
- MDN: Cookie Store API
Webmentions [?] (likes: 22, reposts: 7)
I would not be so confident about it. Here is the Mozilla's position: mozilla.github.io/standards-posi…