Web Api

Cookies, Storages, IndexedDB, History Api, PaymentRequest Api

Тимур Зубаиров

Cookies

Создание


        document.cookie = 'subject=Song';

        document.cookie = 'subject=Beautiful-song';

        document.cookie = 'volume=5';

        document.cookie = 'subject=' + encodeURIComponent('Песня');


        console.log(document.cookie)

        "volume=5; subject=%D0%9F%D0%B5%D1%81%D0%BD%D1%8F;"
            
            

Опции


            document.cookie =  'subject=Song; path=/songs'
            document.cookie =  'subject=Song; domain=music.yandex.ru'

            document.cookie =  'subject=Song; max-age=3600'
            // Количество секунд с текущего момента
            document.cookie =  'subject=Song; expires=Tue, 19 Apr 2021 00:00:00 GMT'
            // Дата окончания жизни

            
            

uniq key = name + path + domain

                
                    document.cookie = 'subject=Song1; path=/';
                    document.cookie = 'subject=Song2; path=/users';
                    document.cookie = 'subject=Song3; path=/users; domain=maps.yandex.ru';

                    console.log(document.cookie);
                    
                    "subject=Song1; subject=Song2; subject=Song3"
                    // Последовательно от менее специфичному к более специфичному
                    
                
            

Чтение

                  
                      document.cookie = 'subject=Song; path=/';

                      console.log(document.cookie);

                      "subject=Song"
                  
              

Чтобы удалить, устанавливаем дату устаревания в прошлом

            subject=Song; expires=Tue, 19 Apr 1970 00:00:00 GMT

subject=Song; max-age=0
            
        

js-cookie

                
                    Cookies.set('subject', 'Song', { expires: 7, path: '' });

                    Cookies.get('subject');

                    Cookies.remove('subject');
                
            

Отправка на сервер

GET / HTTP/1.1
        Host: music.yandex.ru
        Cookie: subject=Song; playlist=1
            

        const express = require('express')
        const app = express();
        const cookieParser = require('cookie-parser');

        app.use(cookieParser());

        app.use((req, res) => {
            console.log(req.cookies);
            // { subject: 'Song' }
        });
            

Отправка на клиент

                
                    var express = require('express')
                    var app = express();

                    app.use((req, res) => {
                        res.cookie('subject', 'Song', { path: '/users' });
                    });
                
            


        HTTP/1.1 200 OK
        Set-Cookie: subject=Song; path=/users
            

HttpOnly


                res.cookie('subject', 'Song', {
                    path: '/users',
                    httpOnly: true
                });
            


            HTTP/1.1 200 OK
            Set-Cookie: subject=Song; path=/users; HttpOnly
            

Не доступны в js-скриптах на клиенте

Secure


                res.cookie('subject', 'Song', {
                    path: '/users',
                    secure: true
                });
            

            HTTP/1.1 200 OK
            Set-Cookie: subject=Song; path=/users; secure
            

Не доступны по HTTP

Устаревание

Доступ с сервера

4Kb

Передаются с каждым запросом в заголовке, который не сжимается

Для статики используйте
cookieless домены (CDN)*

Актуально для HTTP 1

У HTTP 2 / HTTP 3 есть сжатие заголовков

В cookie храните id, по которому можно на сервере получить полные данные

Using the Same-Site Cookie Attribute to Prevent CSRF Attacks

HTTP cookie

WebStorage

SessionStorage – хранит данные до окончания сессии (до закрытия вкладки/окна)

LocalStorage – хранит данные перманентно, пока скрипт или пользователь не удалит их

Не передаёт данные на сервер

Ограничение в 10Mb

Интерфейс

                
                    localStorage.setItem('volume', 8);

                    localStorage.getItem('volume'); // "8"

                    localStorage.removeItem('volume')

                    localStorage.clear();
                
            

Хранит строки, а не объекты


        localStorage.setItem('options', { volume: 8 });

        localStorage.getItem('options'); // "[object Object]"

        localStorage.setItem('options', JSON.stringify({ volume: 8 }));
            

Событие


        window.addEventListener('storage', event => {
            console.log(event);
        });
            

                {
                    key: 'volume',
                    oldValue: '8',
                    newValue: '6'
                }
            

Хранение настроек

Хранение промежуточных данных

Строго ограничено источником (origin)

Синхронный интерфейс

Web Storage API

Html5 Local Storage How-To

Client-side Data Storage

IndexedDB

Data on support for the indexeddb feature across the major browsers from caniuse.com

Строго ограничено источником (origin)

Нет ограничений на размер*

Асинхронное

Не реляционное, а object storage

Создание


                // Указываем название и версию
                const requestDb = indexedDB.open('my-app', 1);
                // Метод open возвращает объект IDBOpenDBRequest
                // с тремя обработчиками:
                // onerror, onsuccess, onupgradeneeded
                requestDb.onerror = event => {
                    console.log(event.target.errorCode);
                };

                requestDb.onsuccess = event => {
                    // Экземпляр открытой базы напрямую не отдает
                    // Получаем доступ к базе
                    const db = event.target.result;
                };
            

Обновление

                
                    // Будет вызван в первый раз, и когда сменилась версия
                    requestDb.onupgradeneeded = event => {
                        const db = event.target.result;
                        const oldVersion = event.oldVersion;
                        // Инструкции к миграции на вторую версию
                        if (oldVersion < 2) {
                            // Создаём хранилище для заметок
                            // IndexedDB оперирует не таблицами,
                            // а хранилищами объектов: ObjectStore
                            db.createObjectStore('notes', {
                                keyPath: 'id', // Имя ключевого поля
                                autoIncrement: true
                            });
                        }
                    }
                
            

Можем добавлять/удалять/обновлять данные вне обработчика onupgradeneeded


Можем создавать/измененять хранилище объектов только при обновлении версии БД внутри обработчика onupgradeneeded

Любые операции с данными в IndexedDB происходят в рамках транзакции

Это обеспечивает целостность базы данных (в случае сбоя операции транзакция откатывается)

Добавление объекта

                
                    // Открываем транзакцию
                    // Указываем, к каким Store будет иметь доступ транзакция
                    const transaction = db.transaction('notes', 'readwrite');
                    // В рамках транзакции получаем ссылку на объект Store
                    const store = transaction.objectStore('notes');
                    // Выполняем запрос в хранилище
                    // Добавляем заметку
                    const request = store.add({
                        id: 1,
                        name: 'films'
                    });

                    request.onsuccess = (event) => console.error(event.target.result);

                    request.onerror = err => console.error(err);
                
            

Может быть несколько паралелльных readonly транзакций, но только одна readwrite

readwrite транзакция “блокирует” хранилище для записи

Получение объекта

                    
                        // Открываем транзакцию
                        // Указываем, к каким Store будет иметь доступ транзакция
                        const transaction = db.transaction(['notes'], 'readonly');
                        // В рамках транзакции получаем ссылку на объект Store
                        const store = transaction.objectStore('notes')
                        // Выполняем запрос в хранилище
                        // Получаем данные, используя значения ключа (id)
                        const request = store.get(1)

                        request.onsuccess = note => console.log(note)
                    
                

Метод get удобно использовать, если знаем ключ, по которому хотим получить данные

А если хотим пройти через все записи в ObjectStore?

Можно воспользоваться курсором

Курсор – особый объект, который пересекает ObjectStore с заданным запросом и возвращает по одному ключу/значению за раз, таким образом экономя память

Чтобы получить все объекты, используем курсор

                
                    const requestCursor = db
                        .transaction(['notes'], 'readonly')
                        .objectStore('notes')
                        .openCursor();
                    requestCursor.onsuccess = event => {
                        const cursor = event.target.result;
                        if (cursor) {
                            console.log(cursor.key, cursor.value);
                            cursor.continue();
                        } else {
                            console.log("No more notes");
                        }
                    };
                    
                

Основное отличие курсора в том, что request.onsuccess срабатывает несколько раз: по одному разу для каждого результата

Что использовать, если хотим искать по условию?

Например, нужно найти Заметки с именем Films?

Использовать индексы

Индекс – это “надстройка“ к хранилищу, отслеживающая заданное поле объекта.

Для каждого значения этого поля хранится список ключей для объектов, имеющих это значение.

Индексы

                
                    const db = event.target.result;

                    const store = db.createObjectStore('notes', {
                        keyPath: 'id',
                        autoIncrement: true
                    });

                    store.createIndex(
                        'nameIdx',      // Название
                        'name',         // Поле или массив ['name', 'keywords'],
                                        // по которому будем искать
                        { unique: false }  // Параметры
                    );
                
            

Поиск по условию

                
                    const transaction = db.transaction(['notes'], 'readonly');
                    const store = transaction.objectStore('notes')

                    const requestCursor = store
                        .index('nameIdx')
                        .openCursor(IDBKeyRange.only('films'));

                    // ...
                
            

dexie.org


        const db = new Dexie('MyDatabase');

        db
            .version(1)
            .stores({
                notes: 'name'
            });

        db
            .open()
            .catch(error => console.error(error));
            

dexie.org


        db
            .notes
            .where('name')
            .equals(['films'])
            .each(note => {
                console.log(note.name);
            });
            

Using IndexedDB

History API

Позволяет манипулировать историей браузера в пределах сессии

History API опирается на один DOM интерфейс — объект History. Он доступен через window.history

Основные методы и свойства объекта History

window.history.length

window.history.state

window.history.go(n)

window.history.back()

window.history.forward()

window.history.pushState(data, title [, url])

window.history.replaceState(data, title [, url])

window.history.length – cвойство length хранит количество записей в текущей сессии истории

                    
                        window.history.length
                        // 10
                    
            

window.history.state – cвойство state хранит текущий объект истории

                    
                        window.history.state
                        // { history.id: 0 }
                    
            

window.history.go(n) – метод, позволяющий гулять по истории. В качестве аргумента передается смещение относительно текущей позиции

window.history.back() – метод, позволяющий гулять по истории. Идентичен вызову window.history.go(-1)

                    
                        window.history.back()
                    
            

window.history.forward() – метод, позволяющий гулять по истории. Идентичен вызову window.history.go(1)

window.history.pushState(state, title [, url]) – метод, добавляющий новое состояние в историю браузера

state – любой валидный тип в JavaScript, который можно сериализовать

title – все современные браузеры игнорируют этот параметр

url – относительный/абсолютный URL новой записи в истории браузера

window.history.replaceState(state, title [, url]) – метод, обновляющий текущее состояние истории браузера

Cобытие popstate

Не вызывает событие

                
                    window.history.pushState()
                    window.history.replaceState()
                
            

Вызывает событие

                
                    window.history.back()
                    window.history.forward()
                    window.history.go(-2)
                    Совершение действий в браузере
                    (нажатие стрелок для движения по истории)
                
            
                
                    window.onpopstate = event => {
                        console.info("onpopstate-event", event);
                    };

                    window.history.pushState({ page: 1 }, "title 1", "?page=1");
                    window.history.pushState({ page: 2 }, "title 2", "?page=2");
                
            
                
                    window.history.back();
                
            

Payment Request API

Преимущества Payment Request API

Payment Request API позволяет хранить данные в браузере

Страницы оформления заказа стандартизованы

Создание экземпляра PaymentRequest

                
                    const paymentRequest = new PaymentRequest(
                        supportedPaymentMethods, // способы оплаты
                        orderDetails, // детали заказа
                        paymentOptions  // [данные об оплате]
                    );
                
            

Поддерживаемые способы оплаты

                
                    const paymentRequest = new PaymentRequest(
                        paymentMethods, orderDetails, paymentOptions
                    );
                
                
                    const paymentMethods = [
                        {
                            supportedMethods: 'basic-card',
                            data: {
                                supportedNetworks: [ // "бренды" поддерживаемых карт
                                    'visa', 'mastercard', 'unionpay'
                                ],
                                supportedTypes: [ // типы поддерживаемых карт
                                    'debit', 'credit'
                                ]
                            }
                        },
                        { supportedMethods: "https://apple.com/apple-pay" }
                    ];
                
            

Поддерживаемые способы оплаты

- Стандартизированные способы оплаты

- Способы оплаты на основе URL

Стандартизированные способы оплаты

Имеют реестр способов оплаты

- basic-card

- basic-credit-transfer

- tokenized-card

- interledger

- sepamail

Способы оплаты на основе URL

Cвязаны с определенным платежным приложением

Не имеют реестра способов оплаты

Информация о заказе

                
                    const paymentRequest = new PaymentRequest(
                        paymentMethods, orderDetails, paymentOptions
                    );
                
                
                    const orderDetails = {
                        displayItems: [{
                            label: '1 x Футболка ДММ',
                            amount: { currency: 'RUB', value: '650.00' }
                        }],
                        total: {
                            label: 'ДММ мерч',
                            amount: { currency: 'RUB', value: '650.00' }
                        }
                    };
                
            

Поддерживаемые методы оплаты

                
                    const paymentRequest = new PaymentRequest(
                        paymentMethods, orderDetails, paymentOptions
                    );
                
                
                    // Пользователю будет предложено указать
                    // адрес электронной почты, имя, номер телефона, адрес доставки
                    // и тип доставки, например:
                    const paymentOptions = {
                        requestPayerEmail: true,
                        requestPayerPhone: true,
                        requestShipping: true
                    };
                
            
                
                    paymentRequest.show()
                
            
                
                    paymentRequest.show()
                
            
                
                    paymentRequest.show()
                
            
                
                    paymentRequest
                        .show().then(paymentResponse => console.info(paymentResponse));
                    // {
                    //    methodName: 'basic-card',
                    //    details: {
                    //        billingAddress: {
                    //            city: 'Москва',
                    //            country: 'RU',
                    //            organization: 'Яндекс',
                    //            phone: '+79991233212',
                    //            ...
                    //        },
                    //        cardNumber: '1234123412341234',
                    //        cardSecurityCode: '123',
                    //        cardholderName: 'ILYENKO KRISTINA',
                    //        expiryMonth: '06',
                    //        expiryYear: '2020'
                    //    }
                    //    ...
                    // }
                
            
                
                    paymentRequest
                        .show()
                        .then(paymentResponse => {
                            const paymentInfo = {
                                details:    paymentResponse.details,
                                methodName: paymentResponse.methodName
                            };

                            const paymentData = {
                                body: JSON.stringify(paymentInfo),
                                credentials: 'include',
                                headers: { 'Content-Type': 'application/json' },
                                method: 'POST'
                            };

                            return fetch('/paymentGatewayAddress', paymentData)
                                .then(...)
                                .catch(...);
                        });
                
            

Конец

Еще API