Chrome Extension - Canceling $interval on navigation - angularjs

I have a AngularJS app implemented in a Chrome Extension. It's using Content Scripts to inject itself into the user's pages as they navigate. In it there's an $interval that calls back to our servers every N seconds as a polling mechanism to check for new data to return to the client.
myInterval = $interval(function() {
// calls back to server
}, 30000);
$scope.$on("$destroy", function() {
if (angular.isDefined(myInterval)) {
$interval.cancel(myInterval);
myInterval = undefined;
}
};
I noticed the traffic on our servers was increasing so I investigated and found that the query triggered by this interval is slamming our servers at a way higher rate than our users could possibly be triggering under expected conditions. For example, at a rate of 200+ requests per minute from the same user.
It seems the interval is staying alive even after navigating to a new page, closing the tab, etc. I've come to understand that listening for $destroy and canceling the interval takes care of routing changes within the app, but how do I make sure the interval is cancelled when a user undergoes normal browsing activity (since a new interval is about to be created when the next page is loaded)?

To listen for what you refer to ad "normal browsing activities", you can register a listener for window's onbeforeunload event.
$window.onbeforeunload = function (evt) {
$interval.cancel(myInterval);
};

Related

How can i get a custom event to fire in Google Analytics?

easy enough right : )
-react app
-im using import TagManager from "react-gtm-module";
in root.js
useEffect(() => {
TagManager.initialize({ gtmId: gtmKey });
//..........
},[]);
i show gtm.js firing in the network tab on browser tools, this should mean i'm running
now in my component, trying to access data layer
const GTMSearchEvent = (v) => {
console.log(v);
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
"event": "search",
"searchQuery": v,
});
};
my param "v" is logging so the function is running, just not really able to see any event triggering
i cant seem to find anything in GTM or GA showing this event.
how can i finish implementing? just want to pass the search queries to my analytics/tag mgr
Setting up the dataLayer pushes is just half of the battle. You need to config your GTM container to "listen" for those events and variables.
Setup new variable to track search query (note that you need to name it exactly how you pushed it to the data layer.
Setup new trigger to track the data layer event
After that, you can do whatever you want/need to do. Here's a simple example if you want to capture it as an event (note, the names are arbitrary, pick something that makes sense to you, just note the variable name that I used for the label, you can use this anywhere within GTM.

Any way to pause firestore listener without removing it?

Is there any way to pause firestore listener without removing it?
I have multiple firebase listeners, some are dependent on other, that changes or start other listeners on data change. Lets say my first listener starts a second listener its onSnapshot. First listener started on useEffect. For certain condition I may not want to change the second listener, so I need to discard data change update from first listener.
If condition met (button click), I discard data changes on first listener for a few moments. Currently I'm doing this using a boolean with useRef. My react app is working fine, with dependant listeners like this. I could remove the listener but I do not want to remove and recreate the listener.
I was wondering if there is a pausing mechanism or method available for any listener. I think it will save a tiny read cost if there was such a method because I'm not using that data sent onSnapshot.
Code example:
useEffect(() => {
let firstListener, secondListener;
//console.log("useEffect...");
function ListenerFunc(p) {
secondListener = await firestore
.collection("test")
.doc(p)
.onSnapshot((doc) => {
//console.log("Current data: ", doc.data());
//Need to discard unwanted change here.
//Changing it on button click for a 2 seconds then it changes back to : pauser.current = false.
if (pauser.current) {
console.log("paused for a moment.");
//pauser.current = false;
return;
}
else {
//update.
}
})
}
firstListener = firestore
.collection("test")
.doc("tab")
.onSnapshot((doc) => {
//console.log("Current data: ", doc.data());
var p = doc.data().p; //get variable p
ListenerFunc(p);
});
// cleanup.
}
Unfortunately this is not possible. If you need to stop listening for changes, even temporarily, you have to detach your listener and attach a new one when you want to start listening again, there is no pause mechanism for listeners.
You could open a Feature Request in Google's Issue Tracker if you'd like so that the product team can consider this, but given that this has already been proposed in this GitHub Feature Request for the IOS SDK and it was rejected I don't see this changing anytime soon.

Interval not causing re-render in react component

I am using the useEffect react hook to set an interval for a countdown variable. I have noticed that when the tab in my browser isn't active, the internal stops working. If the tab is active the code works exactly as expected.
Is there a better way to do this? Below is an example of the code.
export default function CountdownTimer({ minutes_left, action }) {
const [timeLeft, setTimeLeft] = useState();
function updateTimer() {
const remaining = ...work out time left
setTimeLeft(remaining);
}
useEffect(() => {
const interval = setInterval(() => {
updateTimer();
if (timeLeft.asMilliseconds() <= 0) {
action();
clearInterval(interval);
}
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<p>{timeLeft}</p>
);
}
I have implemented solutions in Dan Abramov's post here about this problem. However Dan's solution's also don't render when the tab is inactive.
Note that this is how browsers work: inactive tabs are deprioritised and timers get heavily throttled. Remember that setTimeout and setInterval are most expressly not "run code once X ms have passed", they are "run code after at least X ms have passed" without any guarantee that things won't actually happen much, much later due to scheduling (e.g. whether the tab is active, or even visible as far as the OS can inform the browser) or even just because a bit of JS code took longer than your indicated interval to complete and so your timeout literally can't fire until it's done (because JS is single-threaded).
For Firefox, See https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API#Policies_in_place_to_aid_background_page_performance on what kind of throttling is performed on timers, and for Chrome, see https://developers.google.com/web/updates/2017/03/background_tabs
(For any other browser, a web search should find you similar documentation.)
However, think about what it means for a tab to become a background process: if your users can't see your tab, why would you need to update anything at all? Your users won't be able to see those updates. Instead, consider suspending purely visual tasks when a tab becomes invisible, and then resuming them when the tab becomes visible again, and using a clock-time check to determine whether "things need to happen".
In fact, this is important to do even on a foreground tab if you use setInterval or setTimeout: 10 consecutive setTimeout(..., 100) do not imply that it'll be exactly 1 second later once they're done, it means it's at least 1s later when we're done but we could easily reach that "one second later" mark by the time the 7th or 8th timeout fires because JS is single threaded, and if some task took a while, your timeout won't fire until that task is done. So you're going to at the very least need to update your code for that already anyway.
Probably because useEffect call use clearInterval function after the first render. Here's how the cleanup for useEffect works with the returned function - Example Using Hooks.

How to refresh data without screen flashing angularJS

I am creating a chat based on simple get and post using some php and AngularJS. Since a chat requires data to be refreshed constantly , I was wondering what is the best way to refresh data? How I did it was
$scope.LoadData = function () {
$http.get('php/getchatmessages.php')
.then(
function (response) {
$scope.data.messages = [];
$scope.data.messages = response.data;
$scope.evid = $scope.data.messages[0].EventID;
},
function (response) {
alert(response.data);
}
);
$interval(function(){$scope.LoadData()},5000);
};
This not only cause screen flickers, but the speed of the refresh speeds up over time, taking a lot of processor power and slowly crashes the browser.
What is the best way to refresh data?
I would recommend looking into socket.io which enables real time communication via the WebSocket API. They also have a chat demo. I believe WebSocket's are the best way to refresh data in your case.
If WebSockets are not an option for you, then your method will work if you fix your current issues:
The speed of the refresh speeds up over time
This is because you are calling $interval(function(){$scope.LoadData()},5000); inside $scope.LoadData. Move it outside the function so it doesn't start a new interval every call.
Taking a lot of processor power and slowly crashes the browser
If you are simply replacing the exiting message array with the new from the response then remove $scope.data.messages = []; and you will stop the screen from "flickering".
As for the data issue, you should think about retrieving only new messages instead of getting all of them at once. This way you can simply append the new data to your messages array. This is dramatically improve performance by minimising response sizes.
The flicker speeds up because you use $interval in each invocation, which is PERIODIC and adds another invocation afterwards. Either use $timeout which only adds one invocation, or call $interval outside of LoadData.

Preventing page navigation inside a Backbone-driven SPA

The justification
In my BB app, I allow rapid input from users which gets queued & sent off periodically in the background to the server. The problem I currently have is if a user leaves the page they effectively discard any pending changes sitting in the queue.
So basically what I want to do is inform the user before they leave to give them the opportunity to wait for the changes to be saved rather than just exiting & discarding.
The nitty gritty
So for the general cases where the user refreshes or attempts to navigate to an external URL we can handle the onbeforeunload event. Where it becomes slightly tricky is when we are in the context of an SPA whereby switching between pages does not cause a page refresh.
My immediate thought was to use a global click event handler for all anchors and validate whether or not I want to allow the click, which would work for in-site link navigation. However, where this falls over is navigating via the browsers Back/Forward buttons.
I also had a look at Backbone.routefilter, which at first glance appeared to do exactly what I needed. However, using the simple case as described in the docs, the route was still being executed.
The question
How do we intercept navigation for all scenarios within a Backbone SPA?
Direct link navigation
Use a global event handler to capture all click events
$(document).on('click', 'a[href^="/"]', function (e) {
var href = $(e.currentTarget).attr('href');
e.preventDefault();
if (doSomeValidation()) {
router.navigate(href, { trigger: true });
}
});
Page refreshing / external URL navigation
Handle the onbeforeunload event on the window
$(window).on('beforeunload', function (e) {
if (!doSomeValidation()) {
return 'Leaving now will may result in data loss';
}
});
Browser back/forward button navigation
Behind the scenes Backbone.Router uses the Backbone.history which ultimately leverages the HTML5 pushstate API. Depending on what options you pass to Backbone.history.start, and what your browser is capable of, the API will hook into either the onhashchange event or the onpopstate event.
Delving into the source for Backbone.history.start it becomes apparent that regardless of whether you are using push state or not, the same event handler is used i.e. checkUrl.
if (this._hasPushState) {
addEventListener('popstate', this.checkUrl, false);
} else if (this._wantsHashChange && this._hasHashChange && !this.iframe) {
addEventListener('hashchange', this.checkUrl, false);
} else if (this._wantsHashChange) {
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}
Therefore, we can override this method & perform our validation in there
var originalCheckUrl = Backbone.history.checkUrl;
Backbone.history.checkUrl = function (e) {
if (doSomeValidation()) {
return originalCheckUrl.call(this, e);
} else {
// re-push the current page into the history (at this stage it's been popped)
window.history.pushState({}, document.title, Backbone.history.fragment);
// cancel the original event
return false;
}
};

Resources