diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index dbeb1540..265bd1b9 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -204,7 +204,7 @@ We may have both default and named exports in a single module, but in practice p **Another thing to note is that named exports must (naturally) have a name, while `export default` may be anonymous.** -For instance, these are all perfecly valid default exports: +For instance, these are all perfectly valid default exports: ```js export default class { // no class name @@ -433,6 +433,6 @@ if (something) { } ``` -...But what if we really need to import something conditionally? Or at the right time? Like, load a module upon request, when it's really needed? +...But what if we really need to import something conditionally? Or at the right time? Like, load a module upon request, when it's really needed? We'll see dynamic imports in the next chapter. diff --git a/2-ui/3-event-details/10-onload-ondomcontentloaded/article.md b/2-ui/3-event-details/10-onload-ondomcontentloaded/article.md index 7c62da19..60d7bdb1 100644 --- a/2-ui/3-event-details/10-onload-ondomcontentloaded/article.md +++ b/2-ui/3-event-details/10-onload-ondomcontentloaded/article.md @@ -121,9 +121,34 @@ The example below correctly shows image sizes, because `window.onload` waits for ## window.onunload -When a visitor leaves the page, the `unload` event triggers on `window`. We can do something there that doesn't involve a delay, like closing related popup windows. But we can't cancel the transition to another page. +When a visitor leaves the page, the `unload` event triggers on `window`. We can do something there that doesn't involve a delay, like closing related popup windows. -For that we should use another event -- `onbeforeunload`. +This event is a good place to send out analytics. + +E.g. we have a script that gathers some data about mouse clicks, scrolls, viewed page areas -- the statistics that can help us to see what users want. + +Then `onunload` is the best time to send it out. Regular networking methods such as [fetch](info:fetch-basics) or [XMLHttpRequest](info:xmlhttprequest) don't work well, because we're in the process of leaving the page. + +So, there exist `navigator.sendBeacon(url, data)` method for such needs, described in the specification . + +It sends the data in background. The transition to another page is not delayed: the browser leaves the page and performs `sendBeacon` in background. + +Here's how to use it: +```js +let analyticsData = { /* object with gathered data */ }; + +window.addEventListener("unload", function() { + navigator.sendBeacon("/analytics", JSON.stringify(analyticsData)); +}; +``` + +- The request is sent as POST. +- We can send not only a string, but also forms and other formats, as described in the chapter , but usually it's a stringified object. +- The data is limited by 64kb. + +When the `sendBeacon` request is finished, the browser probably has already left the document, so there's no way to get server response (which is usually empty for analytics). + +If we want to cancel the transition to another page, we can't do it here. But we can use another event -- `onbeforeunload`. ## window.onbeforeunload [#window.onbeforeunload] diff --git a/2-ui/5-data-storage/01-cookie/article.md b/2-ui/5-data-storage/01-cookie/article.md index ae14d0c2..047e03cd 100644 --- a/2-ui/5-data-storage/01-cookie/article.md +++ b/2-ui/5-data-storage/01-cookie/article.md @@ -1,17 +1,16 @@ # Cookies, document.cookie -Cookies are small strings of data that are stored directly in the browser. They are not a part of Javascript, but rather a part of HTTP protocol, defined by [RFC 6265](https://tools.ietf.org/html/rfc6265) specification. +Cookies are small strings of data that are stored directly in the browser. They are a part of HTTP protocol, defined by [RFC 6265](https://tools.ietf.org/html/rfc6265) specification. -Most of the time, cookies are set by a web server. +Most of the time, cookies are set by a web server. Then they are automatically added to every request to the same domain. -One of the most widespread use of cookies is authentication: +One of the most widespread use cases is authentication: -1. Upon sign in, the server uses `Set-Cookie` HTTP-header in a response to set a cookie with "session identifier". -2. The browser stores the cookie. -3. Next time when the request is set to the same domain, the browser sends the over the net using `Cookie` HTTP-header. -4. So the server knows who made the request. +1. Upon sign in, the server uses `Set-Cookie` HTTP-header in the response to set a cookie with "session identifier". +2. Next time when the request is set to the same domain, the browser sends the over the net using `Cookie` HTTP-header. +3. So the server knows who made the request. -The browser provides a special accessor `document.cookie` for cookies. +We can also access cookies from the browser, using `document.cookie` property. There are many tricky things about cookies and their options. In this chapter we'll cover them in detail. @@ -32,11 +31,11 @@ alert( document.cookie ); // cookie1=value1; cookie2=value2;... ``` -The string consist of `name=value` pairs, delimited by `; `. Each one is a separate cookie. +The value of `document.cookie` consists of `name=value` pairs, delimited by `; `. Each one is a separate cookie. To find a particular cookie, we can split `document.cookie` by `; `, and then find the right name. We can use either a regular expression or array functions to do that. -To make things simple, at the end of the chapter you'll find a few functions to manipulate cookies. +We leave it as an excercise for the reader. Also, at the end of the chapter you'll find helper functions to manipulate cookies. ## Writing to document.cookie @@ -51,26 +50,26 @@ document.cookie = "user=John"; // update only cookie named 'user' alert(document.cookie); // show all cookies ``` -If you run it, then probably you'll see multiple cookies. That's because `document.cookie=` operation does not overwrite all cookies, but only `user`. +If you run it, then probably you'll see multiple cookies. That's because `document.cookie=` operation does not overwrite all cookies. It only sets the mentioned cookie `user`. Technically, name and value can have any characters, but to keep the formatting valid they should be escaped using a built-in `encodeURIComponent` function: ```js run // special values, need encoding -let name = "<>"; -let value = "=" +let name = "my name"; +let value = "John Smith" -// encodes the cookie as %3C%3E=%3D +// encodes the cookie as my%20name=John%20Smith document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value); -alert(document.cookie); // ...; %3C%3E=%3D +alert(document.cookie); // ...; my%20name=John%20Smith ``` ```warn header="Limitations" There are few limitations: - The `name=value` pair, after `encodeURIComponent`, should not exceed 4kb. So we can't store anything huge in a cookie. -- The total number of cookies per domain is limited to 20+, depending on a browser. +- The total number of cookies per domain is limited to around 20+, the exact limit depends on a browser. ``` Cookies have several options, many of them are important and should be set. @@ -87,7 +86,7 @@ document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT" The url path prefix, where the cookie is accessible. Must be absolute. By default, it's the current path. -If a cookie is set with `path=/mypath`, it's visible at `/mypath` and `/mypath/*`, but not at `/page` or `/mypathpage`. +If a cookie is set with `path=/admin`, it's visible at pages `/admin` and `/admin/something`, but not at `/home` or `/adminpage`. Usually, we set `path=/` to make the cookie accessible from all website pages. @@ -95,11 +94,11 @@ Usually, we set `path=/` to make the cookie accessible from all website pages. - **`domain=site.com`** -Domain where the cookie is accessible. +A domain where the cookie is accessible. In practice though, there are limitations. We can't set any domain. By default, a cookie is accessible only at the domain that set it. So, if the cookie was set by `site.com`, we won't get it `other.com`. -...But what's more tricky, we also won't get the cookie at a subdomain `forum.site.com`: +...But what's more tricky, we also won't get the cookie at a subdomain `forum.site.com`! ```js // at site.com @@ -125,6 +124,8 @@ alert(document.cookie); // with user For historical reasons, `domain=.site.com` (a dot at the start) also works this way, it might better to add the dot to support very old browsers. +So, `domain` option allows to make a cookie accessible at subdomains. + ## expires, max-age By default, if a cookie doesn't have one of these options, it disappears when the browser is closed. Such cookies are called "session cookies" @@ -144,13 +145,13 @@ date = date.toUTCString(); document.cookie = "user=John; expires=" + date; ``` -If we set `expires` to the past, the cookie will be deleted. +If we set `expires` to a date in the past, the cookie is deleted. - **`max-age=3600`** -An alternative to `expires`, specifies the cookie expiration in seconds. +An alternative to `expires`, specifies the cookie expiration in seconds from the current moment. -Can be either a number of seconds from the current moment, or zero/negative for immediate expiration (to remove the cookie): +If zero or negative, then the cookie is deleted: ```js // cookie will die +1 hour from now @@ -168,11 +169,12 @@ The cookie should be transferred only over HTTPS. **By default if we set a cookie at `http://site.com`, then it also appears at `https://site.com` and vise versa.** -That is, cookies only check the domain, they do not distinguish between the protocols. +That is, cookies are domain-based, they do not distinguish between the protocols. -With this option, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, then the flag can prevent this. +With this option, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, then the flag is the right thing. ```js +// assuming we're on https:// now // set the cookie secure (only accessible if over HTTPS) document.cookie = "user=John; secure"; ``` @@ -185,9 +187,9 @@ To understand when it's useful, let's introduce the following attack scenario. ### XSRF attack -Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` on every request, so that it recognizes you and performs all sensitive financial operations. +Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request, so that it recognizes you and performs all sensitive financial operations. -Now, while browsing the web in another window, you occasionally come to another site `evil.com`, that has a `
` with hacker's account and JavaScript code that submits it automatically. +Now, while browsing the web in another window, you occasionally come to another site `evil.com`, that automatically submits a form `` to `bank.com` with input fields that initiate a transaction to the hacker's account. The form is submitted from `evil.com` directly to the bank site, and your cookie is also sent, just because it's sent every time you visit `bank.com`. So the bank recognizes you and actually performs the payment. @@ -197,31 +199,33 @@ That's called a cross-site request forgery (or XSRF) attack. Real banks are protected from it of course. All forms generated by `bank.com` have a special field, so called "xsrf protection token", that an evil page can't neither generate, nor somehow extract from a remote page (it can submit a form there, but can't get the data back). +But that takes time to implement: we need to ensure that every form has the token field, and we must also check all requests. + ### Enter cookie samesite option -Now, cookie `samesite` option provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens". +The cookie `samesite` option provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens". It has two possible values: -- **`samesite=strict`, same as `samesite` without value** +- **`samesite=strict` (same as `samesite` without value)** A cookie with `samesite=strict` is never sent if the user comes from outside the site. -In other words, whether a user follows a link from the mail or submits a form from `evil.com`, or does any operation with the site that originates from another domain, the cookie is not sent. Then the XSRF attack will fail, as `bank.com` will not recognize the user without the cookie, and will not proceed with the payment. +In other words, whether a user follows a link from their mail or submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent. -The protection is quite reliable. Only operations that come from `bank.com` will send the samesite cookie. +If authentication cookies have `samesite` option, then XSRF attack has no chances to succeed, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment. + +The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite` cookie. Although, there's a small inconvenience. When a user follows a legitimate link to `bank.com`, like from their own notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case. -We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with `samesite=strict`. - -Then a person coming from outside of the site will see a welcome, but payments must be initiated from the bank website. +We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then a person coming from outside of the site will see a welcome, but payments must be initiated from the bank website. - **`samesite=lax`** -Another approach to keep user experience is to use `samesite=lax`, a more relaxed value. +A more relaxed approach that also protects from XSRF and doesn't break user experience. Lax mode, just like `strict`, forbids the browser to send cookies when coming from outside the site, but adds an exception. @@ -234,22 +238,24 @@ A `samesite=lax` cookie is sent if both of these conditions are true: That's usually true, but if the navigation is performed in an ` + + + + ... +
+ +``` + +- So, it was possible to make a GET/POST request to another site, even without networking methods. +- But as it's forbidden to access the content of an ` - -
- -
- -``` - -- So, it was possible to make a GET/POST request to another site. -- But as it's forbidden to access the content of an `