Два аспекти «децентралізованих» односторінкових додатків

У статті ми спробуємо описати два абсолютно не пов'язаних з собою аспекту децентралізованих односторінкових додатків. Це з'єднання двох користувачів і збереження паролів в односторінковому додатку за допомогою браузера.

Інформація про використовуваної технології WebRTC — webrtc.org. У браузері весь сенс спілкування зав'язаний на цій технології, а точніше на WebRTC API яке доступне для Front-end розробника.

Однією з цілей було створити децентралізований чат, чат без використання сервера. Проте, сервер виявився просто необхідний для реалізації наступних функцій:

  • Генерація унікальних id для ідентифікації підключених пристроїв (девайсів)
  • Обмін SDP (session description protocol) для ініціалізації з'єднання Клієнт — Клієнт; для сигналізації клієнтам використовувався websocket протокол
Створення з'єднання
Розглянемо детально схему з'єднання для двох користувачів. Для цього виділимо основні кроки у звичайній схемі з'єднання Клієнта А і Клієнта Б:

  1. А Клієнт є ініціатором з'єднання і генерує пропозицію (Offer), т. е sdp-пропозиція, яка містить всі доступні кодеки, на яких може спілкуватися клієнт і іншу інформацію. Клієнт А відправляє своє sdp-пропозиція Клієнту Б.

  2. Клієнт Б приймає sdp-пропозицію і генерує відповідь (Answer), т. е sdp-відповідь. На даному етапі Клієнт Б генерує в себе всі доступні кодеки і вибирає ті, які доступні обом клієнтам, додає свою інформацію і відправляє його Клієнту А.

  3. Клієнт А одержує sdp-відповідь від Клієнта Б. Далі Клієнт А вже безпосередньо зв'язується з Клієнтом Б на основі отриманої відповіді та інформації в ньому. Після цього між ними утворився RTCDataChannel — канал обміну даними.
Виділені слова в трьох випадках як раз показують навіщо потрібен сервер на початковому етапі з'єднання. Для обміну даними через сервер.

У нашому чаті обидва клієнта одночасно можуть виступати ініціаторами з'єднання, але в результаті ми все одно хочемо отримати лише одне з'єднання RTCDataChannel між двома клієнтами. Сигнальний звісно сервер може вказати якому клієнту виступати ініціатором — а якому чекати пропозицію. Але для мінімізації помилки при з'єднанні ми запускаємо механізм, в якому кожен з клієнтів є ініціюванні та приймаючому одночасно. В результаті залишаємо то з'єднання клієнт-клієнт, який утворюється швидше. Розглянемо більш детально цей аспект.

Нижче представлена схема з'єднання двох ініціюють/приймаючих клієнтів.


  1. Одночасно обидва клієнти отримують сповіщення по вебсокет-з'єднанню про те, що вони в одному чаті. Клієнт А і Клієнт Б ініціюють з'єднання — кожен генерує своє sdp-пропозицію і відсилає його на сервер.

  2. Сервер у свою чергу надсилає отримані пропозиції адресатам (пропозицію від Клієнта А відправляє Клієнту Б, пропозицію від Клієнта Б відправляє Клієнтові А).

  3. Кожен клієнт після отримання sdp-пропозиції генерує sdp-відповідь на нього. Обидва клієнти посилають свої sdp-відповіді на сервер.

  4. Сервер у свою чергу надсилає отримані sdp-відповіді їх адресатам (відповідь від Клієнта А відправляє Клієнту Б, відповідь від Клієнта Б відправляє Клієнтові А).

  5. Кожен клієнт отримує sdp-відповідь і приймає його. Важлива умова що у кожного клієнта є логіка, що з кожним іншим клієнтом не може бути більше одного з'єднання. Відповідно, якщо у Клієнта Б є відкритий RTCDataChannel з Клієнтом, то він не створює новий RTCDataChannel з ним, можна сказати, все інше просто ігнорується. Таким чином то сполука, яка створиться швидше і виграє.
Таким чином при ініціюванні з'єднання двома клієнтами, між ними буде всього один RTCDataChannel, а не два.

Реєстрація користувача та Browser History
При роботи з додатком кожен користувач проходить наступні етапи:

  1. Реєстрація — для кожного нового користувача.
  2. Авторизація — для кожного раніше зареєстрованого користувача.
  3. Безпосередньо робота з чатом.
Для цього в додатку передбачено три сторінки, кожна з яких призначена для відповідного етапу (структура сайту):

  • /register — сторінка реєстрації
  • /login — сторінка логіна
  • /chat — сторінка чату
Але сторінки генеруються на клієнті, а для переходу між ними використовується HistoryAPI. Важливим моментом для зручності використання програми, є збереження авторизаційних даних користувача в браузері (пароля), використовуючи при цьому поведінка браузера за замовчуванням.

В нашому випадку, введені дані користувача повинні пропонуватися до збереження форми логіна при зміні url з "/login" на "/chat", а при зміні "/login" на "/register" не повинні пропонуватися. На практиці це виявилося неможливо реалізувати. Збереження пропонувалося і при переході з "/login" на "/register" і "/register" на "/login". Таким чином, задача полягала у скасуванні збереження даних в браузері для певних випадків.

Для вирішення даного питання були використані різні методи, які представлені нижче.

Автозаповнення для форми
Щоб вимкнути автозаповнення на сторінці реєстрації, встановлюємо для всієї форми атрибут 'autocomplete' зі значенням 'off'.

Розмітка для даного варіанту:

<form autocomplete="off" data-role="loginForm">
<label>Ім'я користувача:</label>
<input type="text" name="userName">
<label>Пароль користувача:</label>
<input type="password" name="userPassword">
<button type="submit">Увійти</button>
</form>

Посилання на даний варіант вирішення stackoverflow.com/a/468295.

Автозаповнення для конкретних полів
Пробували вимкнути автозаповнення на сторінці реєстрації встановивши атрибут 'autocomplete' зі значенням 'off' для полів вводу, тобто імені і пароля користувача.

Розмітка для даного варіанту:

<form data-role="loginForm">
<label>Ім'я користувача:</label>
<input type="text" name="userName" autocomplete="off">
<label>Пароль користувача:</label>
<input type="password" name="userPassword" autocomplete="off">
<button type="submit">Увійти</button>
</form>

У попередньому посиланні обговорюється і цей варіант.

Приховування полів
Даний варіант передбачає додавання в розмітку прихованого елемента div, який включає в себе поля ім'я та пароль користувача, які завжди будуть залишатися порожніми. Розмітка для даного варіанту:

<form data-role="loginForm">
<div style={{display:'none'}}>
<input type="text" />
<input type="password" />
</div>
<label>Ім'я користувача:</label>
<input type="text" name="userName" autocomplete="off">
<label>Пароль користувача:</label>
<input type="password" name="userPassword" autocomplete="off">
<button type="submit">Увійти</button>
</form>

Посилання на даний варіант вирішення stackoverflow.com/a/25111774. Аналогічне рішення запропоновано і в даному джерелі.

Обнулення полів при submit
В даному варіанті вдаємося до допомоги js. При кліці користувача на кнопку «Увійти» за події submit виконуємо обнулення полів імені та пароля.

Програмний код:

handleSubmit: function(event) {
this.loginForm = document.querySelector('[data-role="loginForm"]');
event.preventDefault();
this.loginForm.elements.userName.value = ";
this.loginForm.elements.userPassword.value = ";
}

Обнулення даних виконувалось як відразу після кліка, так і через певний інтервал часу ( SetTimeout() ).

Програмний перехід через HistoryAPI
Останній з варіантів полягає у створенні нової кнопки у формі, яка б виконувала роль кнопки submit.

Розмітка даного варіанту:

<form data-role="loginForm">
<label>Ім'я користувача:</label>
<input type="text" name="userName" autocomplete="off">
<label>Пароль користувача:</label>
<input type="password" name="userPassword" autocomplete="off">
<button type="button" data-action="submit" onclick="handleClick">Увійти</button>
</form>

Програмний код:

handleClick(event) {
if (event.target.dataset.action === 'submit'){
history.pushState({foo: 'bar'}, 'Title', '/chat')
}
}

Всі вищенаведені способи не дозволяли при одному переході зберігати пароль, а при іншому не зберігати. Можна було зберігати завжди, або не зберігати ніколи.

Звідси звичайно про наболіле, що давно пора для односторінкових додатків зробити JS API для браузера про збереження пароля:

window.navigator.promtPasswordSave()

Щось на зразок цього було б чудово.

В даний час в якості рішення розглянутого питання в додатку була змінена структура сайта наступна:

  • /account — сторінка реєстрації та логіна
  • /chat — сторінка чату
Де сторінка /account має два внутрішніх стану login і register, в залежності від цього стану і ми генеруємо ту чи іншу розмітку. Тільки так ми змогли вирішити проблему з контрольованим збереженням пароля в браузері.

SPACHAT (Single Page Application Chat) — веб додаток для обміну повідомленнями між клієнтами за допомогою технології WebRTC, яке ми реалізовуємо. Реалізоване додаток або просто чат можна подивитися на сайті spachat.net.

Безпосередньо сам код доступні за посиланням github.com/volodalexey/spachat.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.