React : Handle multiple browser(s), browser tabs to restrict duplicate operations - javascript

I have a button Submit when clicked performs some operation calling an API. Post click, the button is disabled or basically the initial state of the button and the operation is changed.
I have two or multiple browser tabs which shows same screen of Submit. If in any one of the tab, the Submit operation is performed, the other tabs should show the updated version. The other tabs should show the disabled version and should not show the initial state.
How do I achieve this? I am using React, JS

#1 Data duplication MUST be restricted at the server side.
I would recommend some cache like node-cache. Node-cache will having scalability issues, so better to go with redis. (The logic should be smt. like: If the form has submited with the user_id, or with form_id, you can create a cache for that, and if you proceed it, store it in the db, other case throws an error. On the other browser you must validate before the submit if the unique_id neither in the cache nor in the db. If exists, you can throws an error in the nr.2 browser.
#2 If you want to disable the button, you have to use websockets

If you're looking for a client-only solution, here is a great article about sharing state between browser tabs. The limitation is that it won't work on different browsers/machines.

The best way to handle this from a UI/UX perspective is to use validation. If User A clicks submit, then User B clicks submit from a different browser or tab, an error should be displayed to User B indicating that "This action has already taken place".
That being said, what you are trying to achieve is possible.
One way is by using a WebSocket. A WebSocket is a persistent connection between the client and server, that allows bi-directional communication.
The page with the submit button in your React app would be a "subscriber" to some websocket channel. When the submit button is clicked for the first time(it doesn't matter from where), a message can be "published" from a WebSocket server to ALL subscribers, regardless of the browser or tab being used.
Basically, you would add an onMessage handler in your React app where you can disable the submit button when a specific message is received.
I don't know what tech you are using on the server side, but for a WebSocket server, there are many options out there. For the React app, there is react-websocket which is straight-forward to use.

you can do it in client-side
const Form = () => {
const [buttonDisabled, setButtonDisable] = useState(false);
// first tab fire
const onSubmit = () => {
.
.
.
localStorage.setItem("formSubmited", "true");
};
useEffect(() => {
const disableButton = (e) => {
if (e.storageArea.formSubmited) {
setButtonDisable(true);
}
};
// second tab fire
window.addEventListener("storage", disableButton);
return () => {
window.removeEventListener("storage", disableButton);
};
}, []);
.
.
.
};

Related

Firebase custom claim how to set?

I'm struggling with firebase custom claims.
I have tested a lot of approaches nothing works. Obviously, I miss something important in the concept itself.
So I'm back to the root. This script from the google example should apply customs rule on a newly created user
exports.processSignUp = functions.auth.user().onCreate(event => {
const user = event.data; // The Firebase user.
const customClaims = {
param: true,
accessLevel: 9
};
// Set custom user claims on this newly created user.
return admin.auth().setCustomUserClaims(user.uid, customClaims)
});
Then on a client, I check the result with
firebase.auth().currentUser.getIdTokenResult()
.then((idTokenResult) => {
// Confirm the user is an Admin.
console.log(idTokenResult.claims)
if (!!idTokenResult.claims.param) {
// Show admin UI.
console.log("param")
} else {
// Show regular user UI.
console.log("no param")
}
})
.catch((error) => {
console.log(error);
});
Everything just a raw copy-paste still doesn't work. I've tested both from the local machine(there could be troubles with cors?) and deployed
This is a race situation. If the Function end first then, you will get the updated data.
The getIdTokenResult method does force refresh but if the custom claim is not ready then, it is pointless.
You need to set another data control structure to trigger the force refresh on the client. By example a real-time listener to the rtd;
root.child(`permissions/${uid}`).on..
And the logic inside the listener would be: if the value for that node exists and is a number greater than some threshold, then trigger the user auth refresh
During that time the ui can reflect a loading state if there is no datasnapshot or the not admin view if the datasnapshot exists but is a lower permission level.
In Functions you have to set the node after the claim is set:
..setCustomUserClaims(..).then(
ref.setValue(9)
);
I have a more detailed example on pastebin
The claims on the client are populated when the client gets an ID token from the server. The ID token is valid for an hour, after which the SDK automatically refreshes it.
By the time the Cloud Functions auth.user().onCreate gets called, the client has already gotten the ID token for the new user. This means that it can take up to an hour before the client sees the updated claims.
If you want the client to get the custom claims before that, you can force it to refresh the token. But in this video our security experts recommend (that you consider) using a different storage mechanism for claims that you want to be applied straight away.

How to notify other users that the a certain modal is open

Given the situation:
A user (customer agent) should know if the modal is currently open by other customer support to avoid conflicts on processing the data. So example I opened the modal, it should notify the other users that "hey this is being processed right now, go do other stuff"
Since the project is using meteor.js I considered to take advantage of its real-time feature. I was thinking of storing (MongoDB) a state on every modal if it is open or not (because every modal is a data), of course we can set the "close state" if the user closes the modal (in case he did not proceed processing the data), however what if the user accidentally closed the browser tab / browser window / power outage? then the data is set as "open" in the database forever.
I also considered the use of sockets. (i.e. socketIO) so it will publish to the other users the data of the current modal that is open every time a user opens a modal, so user opened a modal -> socket notify other users on what modal is open. but what if the user opened a modal -> socket notify other users -> then another user logged in to the system (which means he did not get the socket notification and might end up opening the same modal.
So any recomendations / ideas / tips you wanna share?
I'm using Meteor.js, React js, MongoDB as my DB.
You could achieve what your looking for with socket.io an example of how you would handle notifying other users is by simply creating a Map() within the server. This holds information about currently open modals and the users accessing them. I have outlined some code below that could give you and idea of how to do it:
//client side
function modals(socket) {
this.sendModalOpen = (modalIdentifier) => {
socket.emit('openedModal', {
modal: modalIdentifier
});
};
this.closeModal = () => {
socket.emit('closedModal', {
modal: modalIdentifier
});
};
}
socket.on('recModalInfo', (data) => {
for (let x = 0; x < data.info.length; x++) {
console.log(data.info[x][0] + " has open " + data.info[x][1]);
}
});
//server side
let modal = new Map();
io.on('connection', (socket) => {
//Here we are sending any new connections a list of all current modals being viewed with Identifiers.
//You could send all of the items inside the map() using map.entries
let currentInfo = [];
modal.forEach((value, key) => {
currentInfo.push([key, value]);
});
socket.emit('recModalInfo', {
info: currentInfo
});
socket.on('openedModal', (data) => {
modal.set(socket.id, data.modalIdentifier);
});
socket.on('closedModal', (data) => {
modal.delete(socket.id);
});
});
I have included client and serverside, but as your obviously not using vanilla JavaScript here the clientside code is more of a representation of what needs to happen that you can adapt.
but what if the user opened a modal -> socket notify other users ->
then another user logged in to the system (which means he did not get
the socket notification and might end up opening the same modal.
When a user connects to the server and a socket is created the io.on('connection', (socket)=>{ }); Is always ran which allows us to then send out an emit detailing all of the current modals open and a identifier.
A user (customer agent) should know if the modal is currently open by
other customer support to avoid conflicts on processing the data.
As your client will receive information about the modals currently open you can either choose to make them not available on the DOM by not rendering the code used to access the modal. You could also process a check on the map if it's a 1-1 scenario and build further functionality if say a manager or a special individual wants to view the modal for some reason or just not at all.

Java script, PHP

I have a scenario where I need to execute a logout function in php, this function deletes the user from DB and informs another application through sockets. This function should be called when the user closes the browser or tab. I have tried various scenarios posted by others and nothing seems to work in chrome(Version 57.0.2987.110) and firefox.
Following is the examples I tried along with links,
My sample Code
<script type="text/javascript">
var str = 'delete';// this will be set to 'Apply' if the form is submitted.
function logout(){
location.href = 'Logout.php';
}
function pageHidden(evt){
if (str==='delete')
logout();
}
window.addEventListener("pagehide", pageHidden, false);
</script >
Examples I tried....
// 1st approach
//window.addEventListener("beforeunload", function (e) {
/// var confirmationMessage = "Do you want to leave?";
// (e || window.event).returnValue = confirmationMessage;
// return confirmationMessage;
// });
// 2nd approach
// window.onbeforeunload = myUnloadEvent;
// function myUnloadEvent() {
// console.log("Do your actions in here")
// }
// 3rd approach
$(window).on('beforeunload', function() {
return 'Your own message goes here...';
});
checked the following urls
1. window.onunload is not working properly in Chrome browser. Can any one help me?
2. https://webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/ - I followed this approach. Tried some other approaches as well.
3. I can't trigger the unload event in Chrome etc....
Any help is much appreciated, because if the user closes the browser an entry remains in the DB and this is not allowing any new user to login.
You shouldn't rely on JavaScript for sever-side code. It's actually entirely possible to achieve what you're looking for, purely with PHP. Just make sure to 'kill' the session before starting it:
session_set_cookie_params(0);
session_start();
session_set_cookie_params(0) will tell the browser that any exisiting session should only exist for another 0 seconds. Essentially, this means that a user will automatically 'log out' immediately. This way, you don't have to rely on client-side code, which is susceptible to all measure of interrupts such as power outages.
Hope this helps! :)
The correct way to logout is related to how they are logged in.
In PHP, the login state is typically managed by sessions. By default the timeout is 24 minutes of inactivity, but you can easily reduce it.
When a user logs out, you typically reset one or more session variables, and, while you’re at it, kill off the current session, and delete the session cookie.
However, you cannot rely on a user to log out, and many typically just wander off. This is why there is always a relatively short timeout on sessions.
If you want to automatically logout when the tab is closed, you will need JavaScript to intercept the process with window.onbeforeunload and then use Ajax to send the logout to the server.
As regards the database, you normally do not record the login state in the database. You may record the login time, and if you like, the logout time, but remember that may be never.

Simple form submission with Firebase; can I detect when offline?

I'm using Firebase perhaps slightly unconventionally -for simple form submission. Submission of my website's contact form simply results in:
ref.push({name:'dr foo', email:'1#2.com', message:'bar'}, myCallback);
The Firebase is hooked up to Zapier to send the site owner an email. All works well, but I'd like to be able to handle the user loosing their connection. When Firebase can't reach the server I'd like to display: "Please check your connection", or a similar message when the user hits the send button. The "Thanks, we'll be in touch"-type message should only be displayed on a successful write.
At first I tried including an if (error) branch in the callback, but of course disconnection is not something that Firebase considers an error as it "catches up" when it can.
I also tried the code in the docs which monitors .info/connected. While this wouldn't display a message on a form submission attempt, I was thinking I could instead display a warning if disconnected. The sample worked intermittently (Chrome 39, Firefox 30, Linux Mint), but the lag between disconnection and the event firing means it's probably not suitable for this case.
Is what I'm trying to do possible?
It indeed seems that the .info/connected values only changes once some other data transfer occurs (and fails).
The only way I can come up with is by using the transaction mechanism with applyLocally set to false. E.g.
function testOnlineStatus() {
var ref = new Firebase('https://your.firebaseio.com/');
ref.child('globalcounter').transaction(function(count) {
return (count || 0) + 1;
}, function(error, committed, snapshot) {
if (error) {
alert('Are you offline?');
}
}, false /* force roundtrip to server */);
}
setInterval(testOnlineStatus, 2000);
This one triggered for me after about 15 seconds.

Paypal Embedded Flow not using returnUrl or cancelUrl

I am using Paypals Adaptive Payments and Embedded flow feature to provide checkout via a minibrowser. Everything seems to be working correctly in the sandbox environment except that when the payment is completed successfully, the user is never redirected to my returnUrl set in the PAY API request. Same goes for my cancelUrl.
After the payment is complete, the user is shown an order overview in the minibrowser and a button labelled "close". If a user clicks this button, the minibrowser is closed.
If a user clicks cancel at any time, the minibrowser is closed.
There doesn't seem to be a way to have my page aware of the change besides setting up some polling or something which doesn't make sense, my returnUrl and cancelUrl should be used somewhere, right?
this is my code to get the redirect url (using adaptive payments gem):
pay_request = PaypalAdaptive::Request.new
data = {
'requestEnvelope' => {'errorLanguage' => 'en_US'},
'currencyCode' => 'USD',
'receiverList' =>
{ 'receiver' => [
{'email' => '...', 'amount'=> 10.00}
]},
'actionType' => 'PAY',
'returnUrl' => 'http://www.example.com/paid',
'cancelUrl' => 'http://www.example.com/cancelled',
'ipnNotificationUrl' => 'http://www.example.com/ipn'
}
pay_response = pay_request.pay(data)
redirect_to pay_response.approve_paypal_payment_url "mini"
And here is how I am setting up the paypal js:
var dg = new PAYPAL.apps.DGFlowMini({ trigger: "buyit", expType: "mini" });
It all seems pretty straight forward, not sure what I am missing.
Well - seems to be a bug on our side - just tried it myself and confirmed with our integration teams. :-(
Unfortunately the other short term fix I can think of other than what you've mentioned (checking for the existence of the popup window) is to call the PaymentDetails API from your server side to check the status of the Payment. I've opened the bug on our side but don't have an ETA.
Edit 10/18: Sorry I'm wrong. This is working - it's just that our developer guide is not providing all the required information. In case of the mini-browser flow, you would need to provide a 'callbackFunction' and also name your dgFlow variable as 'dgFlowMini'. (the latter is important - as apdg.js is expecting the 'dgFlowMini' variable to be defined) Here is the code that works:
var returnFromPayPal = function(){
alert("Returned from PayPal");
// Here you would need to pass on the payKey to your server side handle to call the PaymentDetails API to make sure Payment has been successful or not
// based on the payment status- redirect to your success or cancel/failed urls
}
var dgFlowMini = new PAYPAL.apps.DGFlowMini({trigger: 'em_authz_button', expType: 'mini', callbackFunction: 'returnFromPayPal'});
I have a working sample here: https://pp-ap-sample.appspot.com/adaptivesample?action=pay (make sure you select mini as the Experience Type)
We will get our docs updated and also cleanup apdg.js to remove the dependency on the JS variable name.
Looks like the PayPal experience for embedded flows has gotten worse. Now you'll receive an error message after invoking mini or lightbox that says "Payment can't be completed. This feature is currently unavailable."

Categories