Chapter 10. Advanced Storage

In this chapter, we will talk about Dart's ability to store data locally on a client, break the storage limit, and prevent security issues in our web applications. We will also take a look at the good old cookies, show you how to use Web Storage, and elaborate on the more powerful and useful IndexedDB to store large amount of data in the user's web browser. The following topics will be covered in this chapter:

  • Cookies
  • Web Storage
  • Web SQL
  • IndexedDB

Cookies

The concept of cookies was introduced for the first time in 1994 in the Mosaic Netscape web browser. A year later, this concept was introduced in Internet Explorer. From that time, the idea of tracking contents of shopping cart baskets across browser sessions remains relevant until today. So, what problem do cookies solve?

An HTTP protocol is stateless and doesn't allow the server to remember the request later. Because it was not designed to be stateful, each request is distinct and individual. This simplifies contracts and minimizes the amount of data transferred between the client and the server. In order to have stateful communication between web browsers, you need to provide an area in their subdirectories where the state information can be stored and accessed. The area and the information stored in this area is called a cookie. Cookies contain the following data:

  • A name-value pair with the actual data
  • An expiry date after which the cookie is no longer valid
  • The domain and path of the server it should be sent to

Cookies are handled transparently by the web browser. They are added to the header of each HTTP request if its URL matches the domain and the path is specified in the cookie. Cookies are accessible via the document.cookie property. Bear in mind that when you create a cookie, you specify the name, value, expiry date, and optionally the path and domain of cookies in a special text format. You can find a cookie by its name and fetch the value only, as all the other information that is specified while creating a cookie is used by the web browser and is not available to you. We created the cookies.dart file to keep small library wrappers of the original API of the cookie to avoid boilerplate and code duplication, which allow you to easily set, get, and remove cookies from any application. The cookies use the date format derived from the RFC 1123 International format, that is, Wdy, DD Mon YYYY HH:MM:SS GMT. Let's look at them in more detail:

  • Wdy: This is a three-letter abbreviation that represents the day of the week, and is followed by a comma
  • DD: These are two numbers that represent the day of the month
  • Mon: These are three letters that represent the name of the month
  • YYYY: These are four numbers that represent the year
  • HH: These are two numbers that represent the hour
  • MM: These are two numbers that represent the minutes
  • SS: These are two numbers that represent the seconds

You can see the date in this example: Thu, 09 Oct 2014 17:16:29 GMT.

Let's go through the following code and see how cookies can be reached by Dart. We import the dart:html package especially to make document.cookies available in our code. The intl.dart package is imported because of DateFormat usage in the toUTCString function to calculate the expiry date in the UTC format of the cookie based on the value in the days attribute and transforms it into a string. If the value of days is less than one, then the toUTCString function returns an empty string. To create cookies with the setCookie function, we need to specify the name of the cookie, value, and the number of days to expire. We can provide the optional path and domain information as well. In practice, you cannot use non-ASCII characters in cookies at all. To use Unicode, control codes, or other arbitrary byte sequences, you must use the most popular UTF-8-inside-URL-encoding that is produced by different encoding methods of the Uri class. To return cookies via getCookie, we only need the name of the cookie. At the end, you will find the removeCookie function.

library cookie;

import 'dart:html';
import 'package:intl/intl.dart';
// Number milliseconds in one day
var theDay = 24*60*60*1000;

DateFormat get cookieFormat => 
  new DateFormat("EEE, dd MMM yyyy HH:mm:ss");

String toUTCString(int days) {
  if (days > 0) { 
    var date = new DateTime.now();
    date = new DateTime.fromMillisecondsSinceEpoch(
        date.millisecondsSinceEpoch + days*24*60*60*1000);
    return " expires=${cookieFormat.format(date)} GMT";
  } else {

    return " ";
  }
}

void setCookie(String name, String value, int days, 
          {String path:'/', String domain:null}) {
   StringBuffer sb = new StringBuffer(
      "${Uri.encodeQueryComponent(name)}=" +
      "${Uri.encodeQueryComponent(value)};");
  sb.write(toUTCString(days));
  sb.write("path=${Uri.encodeFull(path)}; ");
  if (domain != null) {
    sb.write("domain=${Uri.encodeFull(domain)}; ");
  }
  document.cookie = sb.toString();
}

String getCookie(String name) {
  var cName = name + "=";
    document.cookie.split("; ").forEach((String cookie) {
    if (cookie.startsWith(cName)) {
      return cookie.substring(cName.length);
    }
  });
  return null;
}

void removeCookie(String name) {
  setCookie(name, '', -1);
}

The domain of the cookie tells the browser the domain where the cookie should be sent, and in its absence, the domain of the cookie becomes the domain of the page. It allows cookies to cross subdomains, but it does not allow the cookies to cross domains. The path of the domain is the directory present in the root of the domain where the cookie is active.

For a better understanding of the use of cookies in the real world, you can look at the shopping_cart_cookie project. This project is very big, so I will show you small code snippets and point you in the right direction. This project contains the following main classes:

  • Product: This class describes the items in a cart with the ID, description, and price. Users can specify the quantity of items they want to buy, so the amount can be calculated by multiplying the quantity with the price.
  • ShoppingModel: This class helps you to fetch products from the product.json file and returns the Future instance with a list of products.
  • ShoppingController: This class renders the grid of products and updates the amount per product and the total amount. We can send a reference in the body of the table. This reference on the element keeps the total amount and the instance of the ShoppingModel and StorageManager class via a constructor injection.

We will call getProducts from the model in the constructor especially to return data from the server and when the data is ready, we will call the _init method to initialize our application. The readQuantity method is called for the first time to check and return the quantity of the product saved in the cookie. Later in the code, we will call calculateAmount of the product based on the quantity and price, as shown in the following code:

ShoppingController(this.tBody, this.totalAmount, this.service,
  this.storage) {
  model.getProducts().then((List<Product> products) {
      _products = products;
      _init();
    });
}

_init() {
    update().then((value) {
      draw();
      drawTotal();
    });
  }

Future update() {
  return Future.forEach(_products, (Product product) {
    // Read quantity of product from cookie
    return readQuantity(product);
  });
}

Future readQuantity(Product product) {
  return storage.getItem(Product.toCookieName(product))
  .then((String quantity) {
    if (quantity != null && quantity.length > 0) {
      product.quantity = int.parse(quantity);
    } else {
      product.quantity = 0;
    }
  });
}
//…

The Product.toCookieName method creates a unique string identifier with a combination of Product.NAME and product.id. Now launch the application and open the index.html file on the Dartium web browser to see a list of products.

Cookies

We can check the existence of cookies in the web browser. In Dartium, open Developer tools from the Tools menu item and choose the Resources tab, as shown in the following screenshot:

Cookies

Select Cookies and choose localhost to ensure that no cookies are associated with our web server.

Cookies

If the user changes the quantity of any product via the text input field, then the number of products and the total number of selected products will be recalculated. At the same time, our code calls the saveQuantity method of the ShoppingController class, as follows:

saveQuantity(Product product) {
    if (product.quantity == 0) {
      storage.removeItem(Product.toCookieName(product));
    } else {
      storage.setItem(Product.toCookieName(product), 
        product.quantity.toString());
    }
  }

The product is removed from the cookie if the number in the Quantity field equals zero. In other cases, we will create or update the quantity value in cookies.

Cookies

Let's check the preceding information. Return to the Resources tab and navigate to the localhost tree item. Click on the Refresh icon at the bottom of the window to see the list of cookies associated with our server.

Cookies

Now, close the index.html page and open it again. Information about the selected products along with their specified quantity and number will be available here. Click on the Check Out button to invoke a cart.checkOut method to show a message about the paid items and remove the cookies from them. The code is as follows:

checkOut.onClick.listen((Event event){
  cart.checkOut();
});

The following screenshot shows the resulting message:

Cookies

Cookies are a universal mechanism that help to persist user-specific data in the web browser. They can be very useful while essaying the following roles:

  • Tracking a cookie helps compile long-term records of the browsing history.
  • The authentication cookie used by the web server helps you know whether the user was logged in or not.
  • Session cookies exist until the user navigates to a website and expires after a specified interval, or if the user logs in again via the server logic.
  • Persistent cookies live longer than session cookies and may be used to keep vital information about the user, such as the name, surname, and so on.
  • Secure cookies are only used via HTTPS with a secure attribute enabled to ensure that the cookie transmission between the client and the server is always secure.
  • Third-party cookies are the opposite of first-party cookies, and they don't belong to the same domain that is displayed in the web browser. Different content presented on web pages from third-party domains can contain scripts that help track users' browser history for effective advertising.

You can combine different roles to the achieve specific requirements of your business logic. The following are the benefits of having cookies:

  • Convenience: Cookies can remember every website you have been to and also the information in forms such as residential or business address, e-mail address, username, and so on
  • Personalization: Cookies can store preferences, which helps every user to personalize the website content
  • Effective advertising: Cookies can help run marketing campaigns to offer products or services relevant to a specific user
  • Ease of control: Cookies can be cleared or disabled via the client's web browser

The following are the disadvantages of cookies:

  • Privacy: Cookies keep a track of all the websites that you have visited
  • Security: Implementation of cookies on different web browsers is accompanied by the detection of various security holes
  • Limitation: Cookies have a limit of 4095 bytes per cookie
  • Data: Cookies can overhead each request with excessive extra data

So, the main purpose of cookies is to make the client-to-server communication stateful. If you need to only save data on a client or work offline, you can use other techniques and one of them is Web Storage.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.47.218