Two aspects of “decentralized” single-page applications
Information about the used WebRTC technology - webrtc.org . In the browser, the whole point of communication is tied to this technology, and more specifically to the WebRTC API which is available for the Front-end developer.
One of the goals was to create a decentralized chat, chat without using a server. However, the server was simply necessary to implement the following features:
- Unique id generation for identifying connected devices (devices)
- Exchange SDP (session description protocol) to initiate a Client-Client connection; websocket protocol was used to signal clients
Create connection
Consider in detail the connection diagram for two users. To do this, we highlight the main steps in the usual connection scheme of Client A and Client B:
- Client A initiates the connection and generates an Offer, i.e. an sdp offer that contains all available codecs on which the client and other information can communicate. Client A sends his sdp offer to Client B.
- Client B receives an sdp sentence and generates a response (Answer), i.e. an sdp response. At this stage, Client B generates all available codecs and selects those that are available to both clients, adds his information and sends it to Client A.
- Client A receives an sdp response from Client B. Next, Client A is already directly connected to Client B based on the received response and the information in it. After that, between them formed RTCDataChannel - a data exchange channel.
The highlighted words in three cases just show why a server is needed at the initial stage of the connection. To exchange data through the server.
In our chat, both clients can simultaneously initiate a connection, but as a result, we still want to get only one RTCDataChannel connection between two clients. The signal server, of course, can tell which client to initiate - and which one to wait for the offer. But to minimize errors during the connection, we launch a mechanism in which each of the clients is initiating and receiving at the same time. As a result, we leave the client-client connection that forms faster. Let us consider this aspect in more detail.
Below is a diagram of the connection of two initiating / receiving clients.

- At the same time, both clients receive a notification via a web socket connection that they are in the same chat. Client A and Client B initiate the connection - each generates its own sdp-offer and sends it to the server.
- The server, in turn, sends the received offers to the recipients (the proposal from Client A sends to Client B, the proposal from Client B sends to Client A).
- Each client, after receiving an sdp offer, generates an sdp response to it. Both clients send their sdp responses to the server.
- The server, in turn, sends the received sdp responses to their recipients (the response from Client A is sent to Client B, the response from Client B is sent to Client A).
- Each client receives an sdp response and accepts it. An important condition is that each client has a logic, that with each other client there can be no more than one connection. Accordingly, if Client B has an open RTCDataChannel with Client A, then he does not create a new RTCDataChannel with him, we can say that everything else is simply ignored. Thus, the connection that will be created faster and win.
Thus, when two clients initiate a connection, there will be only one RTCDataChannel between them, and not two.
User Registration and Browser History
When working with the application, each user goes through the following steps:
- Registration - for each new user.
- Authorization - for each previously registered user.
- Directly work with chat.
For this, three pages were provided in the application, each of which is intended for the corresponding stage (site structure):
- / register - registration page
- / login - login page
- / chat - chat page
But pages are generated on the client, and HistoryAPI is used to switch between them . An important point for the convenience of using the application is to save the user's authorization data in the browser (password), using the default browser behavior.
In our case, the entered user data should be offered for saving for the login form when changing the url from "/ login" to "/ chat", and when changing "/ login" to "/ register" should not be offered. In practice, this turned out to be impossible to implement. Saving was suggested when switching from "/ login" to "/ register" and from "/ register" to "/ login". Thus, the task was to cancel saving data in the browser for certain cases.
To solve this issue, various methods were used, which are presented below.
AutoFill for form
To disable autofill on the registration page, set the attribute 'autocomplete' with the value 'off' for the entire form.
The markup for this option:
Link to this solution option stackoverflow.com/a/468295 .
Autocomplete for specific fields
We tried to disable autocomplete on the registration page by setting the attribute 'autocomplete' with the value 'off' for the input fields, that is, the user name and password.
The markup for this option:
The previous link also discusses this option.
Hiding fields
This option involves adding a hidden div element to the markup, which includes user name and password fields that will always remain empty. The markup for this option:
Link to this solution option stackoverflow.com/a/25111774 . A similar solution is proposed in this source.
Reset fields at submit
In this option, we resort to the help of js. When the user clicks on the "Login" button on the submit event, we reset the name and password fields.
Program Code:
handleSubmit: function(event) {
this.loginForm = document.querySelector('[data-role="loginForm"]');
event.preventDefault();
this.loginForm.elements.userName.value = '';
this.loginForm.elements.userPassword.value = '';
}Zeroing of the data was carried out both immediately after the click, and after a certain time interval (SetTimeout ()).
Program transition through HistoryAPI
The last option is to create a new button in the form that would act as the submit button.
The markup of this option:
Program Code:
handleClick(event) {
if (event.target.dataset.action === 'submit'){
history.pushState({foo: 'bar'}, 'Title', '/chat')
}
}All of the above methods did not allow you to save the password with one transition, but not with the other. You could either save always or never save.
Hence, of course, about the painful thing, it's high time for single-page applications to make the JS API for the browser about saving the password:
window.navigator.promtPasswordSave()Something like this would be great.
Currently, as a solution to this issue in the application, the site structure has been changed to the following:
- / account - registration and login page
- / chat - chat page
Where the / account page has two internal login and register states, depending on this state we generate this or that markup. Only in this way we were able to solve the problem with controlled password storage in the browser.
SPACHAT (Single Page Application Chat) is a web application for exchanging messages between clients using WebRTC technology, which we implement. The implemented application or just a chat can be viewed on the site spachat.net .
The code itself is available at github.com/volodalexey/spachat .