Offline Broker on JavaScript

 3r38080. 3r3-31. In my project, I needed a functional that would allow not to lose the entered data, in case of internet connection interruption, and I came up with a very simple “Broker”, which allowed not to lose data when the connection was lost, and send them when the connection was restored again. Perhaps “Broker” is not a very good name for him, but do not judge strictly. I want to share, maybe someone will be useful. 3r33466.  3r38080.
3r33466.  3r38080. 3r33466.  3r38080. 3r330. About project 3r3331. 3r33466.  3r38080. My project is designed to account for expenses and income, or as a simple version of home accounting. It was created as a progressive web application, so that it would be convenient to use it on mobile devices, as well as to open up the possibilities of push notifications, access the camera for reading bar codes, and the like. There is a similar mobile application, called ZenMoney, but I wanted something of my own and in my own way. 3r33466.  3r38080. 3r33466.  3r38080. 3r330. The emergence of the idea
3r33466.  3r38080. I try to keep track of expenses and incomes clearly, but since it is often forgotten to bring in the necessary positions, especially with regard to cash, I have to do it almost as soon as the “transaction” occurred. Sometimes I entered data in public transport, such as the subway, where connection loss often occurs even in spite of the widespread Wi-Fi network. It was a shame that everything hangs up and nothing happens, and then the data is simply lost. 3r33466.  3r38080. 3r33466.  3r38080. The idea came from the use of a queue broker, such as RabbitMQ. Of course, I have a simpler and less functional solution, but there is something similar to its principles. I thought that after all, you can save everything, for example, in Cache or LocalStorage in the form of an object with a queue of "unsatisfied" requests, and when a connection appears, you can easily execute the connection. Of course, they are not executed in a queue, which is even better due to asynchronous processing of requests in the JS language, given that you have only one “subscriber”. I faced some difficulties, maybe even the implementation of this all will seem a little curve, but it is a working solution. Of course, it can be improved, but for the time being I will describe the existing “raw” but working version. 3r33466.  3r38080. 3r33466.  3r38080. 3r330. Getting to the development of
3r33466.  3r38080. The first thing I thought about was where to store data in the absence of a connection? The service-server imposed on me PWA, works well with the cache, but is it wise to use the cache ?! Difficult question, I will not go into it. In general, I decided that LocalStorage is better suited to me. Since LocalStorage stores values ​​of type key: value, the object had to be added as a Json string. In my project for external development, I added, in the class folder, a directory called QueueBroker
 3r38080. 3r33410. 3r33411. File structure 3r36969. 3r33434. 3r340. /** ---- ** /
 3r38080. App── app.js
 3r38080. Boots── bootstrap.js
 3r38080. Classes── classes
 3r38080. │ └── QueueBroker
 3r38080. │ ├── index.js
 3r38080. 3 └── Library
 3r38080. │ ├── Broker.js
 3r38080. │ └── Storage.js
 3r38080. Components── components
 3r38080. /** ---- ** /
 3r38080. 3r33466.  3r38080. 3r33476. 3r33476. 3r33466.  3r38080. My project is made in the Laravel + VueJs stack, so a certain dependency of the file structure is required. I do not know how in such cases it is correct to call your own directories for classes, therefore I did so. 3r33466.  3r38080. 3r33466.  3r38080. The index file is created to simply connect the modules from the nested Library. It may not be a very elegant solution, but I wanted to make it so that if I suddenly changed my mind about using LocalStorage, I would write another class for Storage with the same methods, pass it to the broker's designer, and without changing anything I would use another storage. 3r33466.  3r38080. 3r33410. 3r33411. index.js [/b] 3r33434. 3r33414. 3r3155. const broker = require ('./Library /Broker'); 3r38080. const Storage = require ('./Library /Storage'); 3r38080. 3r38080. module.exports.Broker = Broker; 3r38080. module.exports.Storage = Storage; 3r38080.
3r33466.  3r38080. 3r33476. 3r33476. 3r33466.  3r38080. This method allows you to connect only those libraries that I need in my scripts, for example, if I need both:
 3r38080. 3r33414. 3r3155. import {Storage, Broker} from '//classes/QueueBroker/index'; 3r38080.
3r33466.  3r38080. To make it easy for me to change the storage class, I made a semblance of the constructor for the Broker class, in which the Storage object could be passed as an argument, as long as it has the necessary functions. I know that on ES6 I could write class and constructor, but decided to do it in the old manner - prototype. Comments will write directly on the code:
 3r38080. 3r33466.  3r38080. 3r33410. 3r33411. Broker.js [/b] 3r33434. 3r33414. 3r3155. const axios = require ('axios'); //I like the axios
3r38080. /* 3r38080. This is the similarity of the designer. 3r38080. The prefix is ​​needed so that we can use different objects for storage in different parts of the front-end of the application
* /
function Broker (storage, prefix = 'storageKey') {
this.storage = storage; 3r38080. this.prefix = prefix; 3r38080. 3r38080. /* 3r38080. If our broker is still empty, we will fill an empty object into it. The main thing is that the storage with the add function can convert it to json
* /
if (this.storage.get ('broker') === null) {
this.broker = {}; 3r38080. this.storage.add ('broker', this.broker)
}
else {
//And here, on the contrary, Storage should be able to give us an object from Json that we write to the property of our prototype class
this.broker = this.storage.getObject ('broker'); 3r38080.}
}; 3r38080. 3r38080. //Just a counter so that we can determine how many messages are waiting to be sent to the server 3r3480. Broker.prototype.queueCount = function () {
return Object.keys (this.broker) .length; 3r38080.}; 3r38080. 3r38080. //The method of saving the "unsatisfied" request to our Storage, with the assignment of the key 3r-3480. Broker.prototype.saveToStorage = function (method, url, data) {
let key = this.prefix + '_' + (Object.keys (this.broker) .length + 1); 3r38080. 3r38080. this.broker[key]= {method, url, data}; 3r38080. 3r38080. //By the way, here it is also advisable to make different keys and not to write everything down in the broker, but for the sake of simplicity, I decided to leave it to
this.storage.add ('broker', this.broker); 3r38080.}; 3r38080. 3r38080. //This is the method that will send data when connection
is restored. Broker.prototype.run = function () {
for (let key in this.broker) {
this.sendToServer (this.broker[key], key)
}
}
3r38080. /* 3r38080. The method of sending to the server. We need an object with recorded data to be sent, which contains a method, url and data, as well as an item key in our repository, in order to delete it after it has been successfully sent
* /
Broker.prototype.sendToServer = function (object, brokerKey) {
3r38080. axios ({
method: object.method,
url: object.url,
dаta: object.data,
})
.then (response => {
if (response.data.status == 200) {
//Delete the object by key, after sending 3r33480 successfully. delete this.broker[brokerKey];
//Overwriting the object
this.storage.add ('broker', this.broker);
}
else {
//leave for debug ;-)
console.log (response.data)
}
3r38080.}) 3r38080. .catch (error => {
/*
Well and it is over after all successful tests we will make a beautiful catching of errors, but not within the framework of this article 3r38080. * /
}); 3r38080. 3r38080.}; 3r38080. 3r38080. //Do not forget to export
module.exports = Broker; 3r38080.
3r33466.  3r38080. 3r33476. 3r33476. 3r33466.  3r38080. 3r33466.  3r38080. Next, you need the Storage object itself, which will successfully save and retrieve everything from the
storage.  3r38080. 3r33410. 3r33411. Storage.js [/b] 3r33434. 3r33414. 3r3155. //Ability to enable debug mode for debugging
function storage (debug) {
if (debug === true)
{
this.debugMode = true; 3r38080.}
3r38080. this.storage = window.localStorage; 3r38080.}; 3r38080. 3r38080. //Special method for converting an object in Json and saving it in the
repository. Storage.prototype.addObjectToStorage = function (key, object) {
this.storage.setItem (key, JSON.stringify (object)); 3r38080.}; 3r38080. 3r38080. //To write the remaining parameters (numbers, booleans, and strings)
Storage.prototype.addStringToStorage = function (key, value) {
this.storage.setItem (key, value); 3r38080.}; 3r38080. 3r38080. //Get the item from storage
Storage.prototype.get = function (key) {
return this.storage.getItem (key); 3r38080.}; 3r38080. 3r38080. //Getting an object from our Json inside, which we wrote down by another method above
Storage.prototype.getObject = function (key) {
try
{
return JSON.parse (this.storage.getItem (key)); 3r38080.}
catch (e) 3r38080. {
this._debug (e); 3r38080. this._debug (key + '=' + this.storage.getItem (key)); 3r38080. return false; 3r38080.}
}; 3r38080. 3r38080. /* 3r38080. Adding, in order not to bother with the methods, we give it to him, and he himself chooses how to write it, serialize it in Json, or write it in pure form
* /
Storage.prototype.add = function (key, value) {
try
{
if (typeof value === 'object') {
this.addObjectToStorage (key, value); 3r38080.}
else if (typeof value === 'string' || typeof value === 'number') {
this.addStringToStorage (key, value); 3r38080.}
else {
//A little check for types
this._debug ('2 parameter does not belong to a known type').}
3r38080. return this.storage; 3r38080. 3r38080.}
catch (e) 3r38080. {
//Storage overflow protection is built-in, but we need to know if this happens
if (e === QUOTA_EXCEEDED_ERR) {
this._debug ('LocalStorage is exceeded the free space limit')
}
else
{
this._debug (e)
}
}
}; 3r38080. 3r38080. //Clear storage
Storage.prototype.clear = function () {
try
{
this.storage.clear (); 3r38080. return true; 3r38080.}
catch (e) 3r38080. {
this._debug (e)
return false; 3r38080.}
}; 3r38080. 3r38080. //Remove item from storage
Storage.prototype.delete = function (key) {
try
{
this.storage.removeItem (key); 3r38080. return true; 3r38080.}
catch (e) 3r38080. {
this._debug (e)
return false; 3r38080.}
}; 3r38080. 3r38080. //Small debager we use in the course of
Storage.prototype._debug = function (error) {
if (this.debugMode)
{
console.error (error); 3r38080.}
return null; 3r38080.}; 3r38080. 3r38080. //Do not forget to export
module.exports = Storage; 3r38080.
3r33466.  3r38080. 3r33476. 3r33476. 3r33466.  3r38080. 3r33466.  3r38080. When all the above will be done, it can be used at your discretion, I use it as follows:
 3r38080. 3r33410. 3r33411. Use when saving 3r3r6969. 3r33434. 3r33414. 3r3155. //this is inside the Vue (methods) object
3r38080. /* ---- * /
3r38080. //Here we announce our broker and Storage for him
sendBroker (method, url, data) {
let storage = new Storage (true); 3r38080. let broker = new Broker (storage, 'fundsControl'); 3r38080. broker.saveToStorage (method, url, data); 3r38080.}, 3r38080. 3r38080. //Here we execute our usual query
fundsSave () {
3r38080. let url = '/pa /funds'; 3r38080. let method = ''; 3r38080. 3r38080. if (this.fundsFormType === 'create') {
method = 'post'; 3r38080.}
else if (this.fundsFormType === 'update') {
method = 'put'; 3r38080.}
else if (this.fundsFormType === 'delete') {
method = 'delete'; 3r38080.}
3r38080. this. $ store.commit ('setPreloader', true); 3r38080. 3r38080.           axios ({
method: method,
url: url,
dаta: this.fundsFormData,
})
.then (response => {
if (response.data.status == 200) {
this.fundsFormShow = false;
this.getFunds ();
this. $ store.commit ('setPreloader', false);
}
else {
this. $ store.commit ('AlertError', 'Error retrieving data from the server');
}
3rrr8080.}) 3rrr8080. //And just here we catch our connection error
.catch (error => {
this. $ store.commit ('setAlert',
{3rr3480. type: 'warning', 3rr3480. status: true, 3r33480. message: 'Error connecting to server. However, your the data will not be ureryan and will be recorded after the connection is restored '
}
);
this.fundsFormShow = false;
this. $ store.commit (' setPreloader ', false); 3rrr8080. 3rrr8080 ////write our "unsatisfied" query
this.sendBroker (method, url, this.fundsFormData);
console.error (error); 3rr3480.}); 3r38080.}, 3r38080.
3r33466.  3r38080. 3r33476. 3r33476. 3r33466.  3r38080. 3r33410. 3r33411. Use when reconnecting [/b] 3r33434. 3r33414. 3r3155. //This is the code for the Vue component
/* - * /
3r38080. methods: {
3r38080. /* ---- * /
3r38080. /* 3r38080. Initiation of our broker, with the same parameters, so that we know that we work with the same keys with which we wrote to the broker
* /
brokerSendRun ()
{
let storage = new Storage (true); 3r38080. let broker = new Broker (storage, 'fundsControl'); 3r38080. 3r38080. //Check if there is something not sent
if (broker.queueCount ()> 0)
{
//Run the method that sends everything
broker.run (); 3r38080. 3r38080. //Display the general alert of the application, with notification 3r3480. this. $ store.commit ('setAlert', {type: 'info', status: true, message: 'There are messages not sent to the server due to connection errors, check that all data has been successfully saved now'}); 3r38080.}
3r38080.}
}
3r38080. /* --- * /
3r38080. /* 3r38080. Well, we call our method, for example, when the component is mounted, just after the disconnected connection, the page will most likely be reloaded, and if it loads, our messages will be sent to the server 3r38080. * /
mounted () {
this.brokerSendRun (); 3r38080.}
3r38080. /* --- * /

3r33466.  3r38080. 3r33476. 3r33476. 3r33466.  3r38080. 3r33466.  3r38080. 3r33464. P.S. 3rr3465. 3r33466.  3r38080. I find it difficult to talk about the code, so I tried to provide the code given in the examples with detailed comments as much as possible. If you have ideas for improving this solution or for improving this article, I will be glad to see them in the comments. I took examples from my own project on Vue, explaining this in order to make it clear why I have the same name for the methods and why I contact them in this . I do not do this article on Vue, so I don’t provide other component code, I leave it just for understanding. 3r33476. 3r38080. 3r38080. 3r33473. ! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o].src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e.parentNode.insertBefore (r, e)}; "[object Opera]" == e.opera? a.addEventListener? a.addEventListener ("DOMContentLoaded", d,! 1): e.attachEvent ("onload", d ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () (); 3r37474. 3r38080. 3r33476. 3r38080. 3r38080. 3r38080. 3r38080.
+ 0 -

Add comment