[JavaScript, Браузеры] UUID и браузеры. Почему фронтенд живет без страшных айдишников?

Автор Сообщение
news_bot ®

Стаж: 6 лет 7 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
20-Фев-2021 21:31

Решил я делать свой пет-проект по учету прочитанных книг на PWA. Покорять новые технологии и все такое. Расчет был на то, что с его выложу и установлю на телефон и вот у меня есть мобильное приложение, которое можно использовать оффлайн. Хочу я сгенерировать UUID, чтобы сохранить книгу, а не нахожу API. Предлагаю разобраться почему.
Что такое UUID
UUID — стандарт идентификации данных используемый, преимущественно, для распределенных систем. Его задача позволить генерировать ключи, которые не вызовут конфликтов при сохранении в то, или иное хранилище данных.
UUID представляет собой 16-байтное число в HEX'е формате:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
Где:
x - [0 - f] (Закодированные данные)
M - [0 - 5] (Версия UUID)
N - [8 - b] (Вариант UUID)

Здесь я не буду вдаваться в подробности, что из этого что означает. С этим вы подробно можете ознакомиться в википедии.
Способы генерации UUID

Все способы генерации UUID сводятся к тому, что мы берем уникальные данные клиента прогоняем через хеш-функцию и получаем наш 128-битный ключик.
1 и 2 версии использовали время с точностью до 0.1 микросекунды + MAC адрес, что гарантировало практически полное отсутствие возможности получить дубликат. Чтобы полностью добить эту вероятность первая версия добавляет рандомную соль, а вторая ничего не делает (вторую версию мы не любим, она вообще может сгенерировать только 64 уникальных id за семь минут).
3 и 5 хешируют пространство имен (Url, FQDN, OID) + само имя. Таким образом в каждый момент времени мы получаем абсолютно идентичные UUID для одних и тех же входных параметров.
Отличие 3 и 5 версии только в том, что 3 использует для хеширования MD-5, а 5 — SHA-1.
4 же версия просто использует рандом ¯_(ツ)_/¯.
Почему его нет в браузере
JS не имеет доступа к данным машины
Мы не можем получить MAC-адрес пользователя, мы не можем получить данные его IP, а так же вообще что-либо с его машины без разрешения пользователя.
Да, мы можем загружать файлы и делать красивые file-инпуты на фронте, но мы можем получить только конкретный файл, который нам предоставит пользователь. Но согласитесь, как бы не шибко удобно запрашивать на каждый UUID по файлу. Неудобно их запрашивать даже каждый раз при входе на сайт.
Сделано же это из благих целей: представьте, что читаете вы Хабр, а тут:

import * as console from 'console';
console.run('rm -rf /**/kursach*final-(\d+)?.docx')

И больше никаких проблем с высшим образованием.
Потому что до недавних пор он был просто не нужен
Браузер для того, чтобы сидеть в интернете.
Через браузер мы заходим на сайт. Если мы зашли на сайт — нам отдали страничку. А раз нам ее отдали — значит мы связаны с сетевым узлом который может сгенерировать UUID и сами мы можем этого не делать. По факту, нам как фронту вообще на ID информации все равно, мы отдали, а дальше это уже проблема принимающей стороны.
Вы можете возразить, что есть PWA, и что оно есть аж с 2007 года. Но так уж вышло, что PWA никому не нужен, примерно, с того же самого времени. (Хотя нынче Play Market позволяет загружать PWA как приложения, но...). Сами посудите, много вы PWA приложений установили? Я даже Хабр не поставил.
Другой вопрос — почему до сих тор нет никакого API типа getUUID(), чтобы переложить эту проблему на браузер? Скорее всего ответ кроется все в той же ссылке на то, что в 99 случаев из 100 сайту это просто-напросто незачем.
Но осадочек остался.
Какие трудности вас ждут
Точность времени
Я бы не стал называть это большой проблемой.
Мы можем получить время с точностью только до миллисекунды, в то время как первая версия UUID делала это с точностью до 100 наносекунд.
Ну чисто теоретически мы можем получить и с точностью до 1 микросекунды, но это будет время от открытия вкладки (это если мы сейчас про performance.now()), что уже не так заманчиво.
Идентификация браузера
Браузеры вообще не уникальны и сейчас я вам это докажу.
Для идентификации клиента HTML Living Standard нам предлагает использовать The Navigator object.
А теперь внимание сравним то, что нам предлагают сравнивать
Браузер
appCodeName
appName
platform
​product
productSub
vendor
vendorSub
Chrome
Mozilla
Netscape
Win32
Gecko
20030107
Google Inc.
-
Mozilla 75
Mozilla
Netscape
Win32
Gecko
20100101
-
-
Mozilla 45
Mozilla
Netscape
Win32
Gecko
20100101
-
-
Internet Explorer
Mozilla
Netscape
Win32
Gecko
-
-
-
Microsoft Edge
Mozilla
Netscape
Win32
Gecko
20030107
Google Inc.
-
Как вам такое? Почувствовали все разнообразие клиентов? Вот и я нет.
Но надо признать, что местами отличаются userAgent и appVersion:
Браузер
appVersion
userAgent
Chrome
5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
Mozilla 75
5.0 (Windows)
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0
Mozilla 45
5.0 (Windows)
Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0
Internet Explorer
5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; Tablet PC 2.0; Zoom 3.6.0; rv:11.0) like Gecko
Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; Tablet PC 2.0; Zoom 3.6.0; rv:11.0) like Gecko
Microsoft Edge
5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74
Тут Edge впереди планеты всей, так как он отображает IP, и мы можем использовать его. Но это только в Edge. А так, как видите, многого с навигатором не навоюешь.
Как это реализовал я

Для себя я решил отталкиваться от своих нужд и особенностей архитектуры своего приложения.
  • Книги очень сложно добавлять несколько раз в миллисекунду. Даже просто тыкать на кнопочку сложно.
  • Книги может добавлять только авторизованный пользователь.

Я первые 6 байт из 16 отвел под миллисекунды timestamp'а, что дает нам возможность генерировать ключи аж до 10889-08-02Т10:31:50.655. Хватит с запасом.
Последние 6 байт я беру из SHA-1 хеша логина — можно идентифицировать 281,474,976,710,656 уникальных пользователей (если взять расчет на то, что не будет коллизий). Тоже с запасом (у меня их всего 30).
1 байт у нас отводится на версию (M) и вариант (N).
Оставшиеся 3 байта я солю рандомом.
Что в итоге:
Если вдруг мое приложение станет супер-пупер популярным и 100,000 и они будут за минуту каждый делать по 100 книг, то за миллисекунду будет генерироваться:
$$
100,000 * 100 / 60,000 = 166
$$
Вероятность того, что совпадут два:
$$
166 1/256^3 1/256^5 = 166 1/255^8 = 166 / 18 10^{18}
$$
Это очень мало и этого мне хватает
Реализацию можно посмотреть тут.
Предвещая вопрос "А почему же не рандом?"
Да, есть такой легендарный код
function uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

В моем случае на бэкенде UUID используется как первичный ключ.
Когда первые байты ключа идут по порядку больше вероятность, что новая запись встанет в конец таблицы. Даже если на клиенте будет запущена синхронизация. Ведь вряд ли юзер выполнит синхронизацию данных внесенных полгода назад и СУБД будет сдвигать половину таблицы.
В случае же с рандомом — данные будут вставляться в табличку куда ни попадя.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_javascript, #_brauzery (Браузеры), #_frontend, #_javascript, #_brauzery (браузеры), #_tipy_dannyh (типы данных), #_uuid, #_javascript, #_brauzery (
Браузеры
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 28-Сен 18:23
Часовой пояс: UTC + 5