title | description | cover | date | dateUpdated | tags | ||
---|---|---|---|---|---|---|---|
Work with cookies the modern way |
Cookie Store API is a way to avoid the pain. |
images/cover.png |
2021-05-01 |
Last Modified |
|
Have you ever worked with cookies? Did you find working with them obvious? I think it has a lot of nuances for newbies.
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?
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.
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
.
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.
No. Just no. Working with document.cookie
is a synchronous operation, so you can’t use it in service workers.
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.
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!
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!
await cookieStore.delete('ie6');
Or you can set the expiration date to the past date if you want, but what for?
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-worker.js
await self.registration.cookies.subscribe([
{
name: 'cookie-name',
url: '/path-to-track',
}
]);
self.addEventListener('cookiechange', (event) => {
// process the changes
});
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.