Playing with firebase RTDB lately, I've been using .once('value', v => ...) to build the GUI of my app. The exact code below:
<script>
function onAuthCompleted(usr) {
var salesTxRef = firebaseApp.database().ref('/salesTx').limitToLast(5);
salesTxRef.once("value", salesTx => {
salesTx.forEach(txRef => {
const tx = txRef.val();
const $item = $('<li></li>').html('$' + tx.total + ' <small>' + tx.currencyCode + '</small>');
$('.main ul').append($item);
});
});
}
</script>
The problem is that if I leave the page long enough opened, .once() gets called multiple times (once every 2-3 hours). Is this a bug on the javascript library? Known issue? Is there something that I'm incorrectly doing or a misunderstanding on my part?
As #Frank van Puffelen pointed out in the comment, the problem came from the method that called onAuthCompleted(usr):
firebaseApp.auth().onAuthStateChanged(function(user) {
if (user) {
if (typeof onAuthCompleted == 'function') {
onAuthCompleted(user);
}
} else {
console.log('User is not logged in. Cannot start session.');
}
}, function(error) {
console.log(error);
});
The onAuthStateChanged() being called hourly to refresh the session caused onAuthCompleted() to be called again thus registering the .once() method one more time (every ~hour). That was causing the strange perceived behavior.
I can confirm that .once() works as expected and it was my misunderstanding on how onAuthStateChange() works.
Thanks,
Related
I have the following code I'm trying to run and I'm getting different results every time I run it. Sometimes I just get "Powered Off". Sometimes I get that plus "Powered on". Sometimes I get all of the above plus "Scanning started". And sometimes it runs all the way thru and I get a bunch of "still searching..." and it eventually hits the BLE device and returns the advertisement.
I have no idea why this is working sometimes and not others.
var async = require('async');
var noble = require('../index');
var peripheralIdOrAddress = "8cf681a590f3";
noble.on('stateChange', function(state) {
if (state === 'poweredOn') {
noble.startScanning();
console.log("Powered On")
} else {
noble.stopScanning();
console.log("Powered Off")
}
});
noble.on('scanStart', ()=>console.log("Scanning started"))
noble.on('scanStop', ()=>console.log("Scanning stopped"))
noble.on('discover', function(peripheral) {
console.log("Discovered something!");
if (peripheral.id === peripheralIdOrAddress || peripheral.address === peripheralIdOrAddress)
{
noble.stopScanning();
console.log('peripheral with ID ' + peripheral.id + ' found');
var advertisement = peripheral.advertisement;
console.log(JSON.stringify(advertisement));
peripheral.on('disconnect', function() {
console.log("Peripheral disconnected");
process.exit(0);
});
peripheral.on('connect', ()=>console.log("Peripheral connected"));
peripheral.connect(function(error) {
peripheral.discoverServices("a0521000-2a63-479e-9d0a-09dfa7c8fd98",()=>console.log("Service discovered"));
});
}
else{console.log("Not the one. still searching...")}
});
Crazy addition to this question. I tried a lot of things with turning stuff off and unplugging things.
In the end I found out it was the power port. If I'm plugged into wall power, I get about 1 out of ever 10 runs that works. If I unplug wall power and go off battery, it works every time.
So bizarre.
I've got a near full-functioning realtime database presence system as described here but I am encountering a few issues. It seems that people are remaining online even after having disconnected a while ago. I'm not sure why but I've opened up my security rules to unauthenticated requests temporarily to no avail. It could be due the bug described here.
If the issue is the ladder, what would be the proper JavaScript implementation to avoid this issue? Is it a good solution to recreate the onDisconnect listener every 60 minutes? For reference, the code I'm using is shown below:
export function selfPresence(byUpdate) {
onValue(ref(rtdb, '.info/connected'), (snap) => {
isDBConnected = snap.val();
if (snap.val() === true) {
presencecon = push(ref(rtdb, `users/${user.uid}/connections`)); // Create new presence thing.
onDisconnect(presencecon).remove();
set(presencecon, true);
onDisconnect(ref(rtdb, `users/${user.uid}/lastOnline`)).set(serverTimestamp());
onDisconnect(ref(rtdb, `users/${user.uid}/currentlyListening`)).remove();
}
});
}
MY SOLUTION
I could not figure out how to get RTDB to act consistently so I opted for the code below instead. Basically, it just updates a boolean at a specific path so that if/when the issue DOES happen, the next time the user is online, it will overwrite the failure with new online listener.
onValue(ref(rtdb, '.info/connected'), (snap) => {
isDBConnected = snap.val();
if (snap.val()) {
onDisconnect(presencecon).set(false);
presencecon = ref(rtdb, `users/${user.uid}/online`);
isDBConnected = snap.val();
}
else {
remove(presencecon);
}
}
The below code updates a boolean at a specific path so that if/when the issue DOES happen, the next time the user is online, it will overwrite the failure with a new online listener.
onValue(ref(rtdb, '.info/connected'), (snap) => {
isDBConnected = snap.val();
if (snap.val()) {
onDisconnect(presencecon).set(false);
presencecon = ref(rtdb, `users/${user.uid}/online`);
isDBConnected = snap.val();
}
else {
remove(presencecon);
}
}
An Example I have linked below, that shows the problem I have.
My Problem
I have these two functions
const updatedDoc = checkForHeadings(stoneCtx, documentCtx); // returns object
documentCtx.setUserDocument(updatedDoc); // uses object to update state
and
convertUserDocument(stoneCtx, documentCtx.userDocument);
// uses State for further usage
The Problem I have is, that convertUserDocument runs with an empty state and throws an error and then runs again with the updated state. Since it already throws an error, I cannot continue to work with it.
I have tried several different approaches.
What I tried
In the beginning my code looked like this
checkForHeadings(stoneCtx, documentCtx);
// updated the state witch each new key:value inside the function
convertUserDocument(stoneCtx, documentCtx.userDocument);
// then this function was run; Error
Then I tried the version I had above, to first put everything into an object and update the state only once.
HavingconvertUserDocument be a callback inside of checkForHeadings, but that ran it that many times a matching key was found.
My current try was to put the both functions in seperate useEffects, one for inital render and one for the next render.
const isFirstRender = useRef(true);
let init = 0;
useEffect(() => {
init++;
console.log('Initial Render Number ' + init);
console.log(documentCtx);
const updatedDoc = checkForHeadings(stoneCtx.stoneContext, documentCtx);
documentCtx.setUserDocument(updatedDoc);
console.log(updatedDoc);
console.log(documentCtx);
isFirstRender.current = false; // toggle flag after first render/mounting
console.log('Initial End Render Number ' + init);
}, []);
let update = 0;
useEffect(() => {
update++;
console.log('Update Render Number ' + update);
if (!isFirstRender.current) {
console.log('First Render has happened.');
convertUserDocument(stoneCtx.stoneContext, documentCtx.userDocument);
}
console.log('Update End Render Number ' + update);
}, [documentCtx]);
The interesting part with this was to see the difference between Codesandbox and my local development.
On Codesandbox Intial Render was called twice, but each time the counter didn't go up, it stayed at 1. On the other hand, on my local dev server, Initial Render was called only once.
On both version the second useEffect was called twice, but here also the counter didn't go up to 2, and stayed at 1.
Codesandbox:
Local Dev Server:
Short example of that:
let counter = 0;
useEffect(()=> {
counter++;
// this should only run once, but it does twice in the sandbox.
// but the counter is not going up to 2, but stays at 1
},[])
The same happens with the second useEffect, but on the second I get different results, but the counter stays at 1.
I was told this is due to a Stale Cloruse, but doesn't explain why the important bits don't work properly.
I got inspiration from here, to skip the initial render: https://stackoverflow.com/a/61612292/14103981
Code
Here is the Sandbox with the Problem displayed: https://codesandbox.io/s/nameless-wood-34ni5?file=/src/TextEditor.js
I have also create it on Stackblitz: https://react-v6wzqv.stackblitz.io
The error happens in this function:
function orderDocument(structure, doc, ordered) {
structure.forEach((el) => {
console.log(el.id);
console.log(doc);
// ordered.push(doc[el.id].headingHtml);
// if (el.children?.length) {
// orderDocument(el.children, doc, ordered);
// }
});
return ordered;
}
The commented out code throws the error. I am console.loggin el.id and doc, and in the console you can see, that doc is empty and thus cannot find doc[el.id].
Someone gave me this simple example to my problem, which sums it up pretty good.
useEffect(() => {
documentCtx.setUserDocument('ANYTHING');
console.log(documentCtx.userDocument);
});
The Console:
{}
ANYTHING
You can view it here: https://stackblitz.com/edit/react-f1hwky?file=src%2FTextEditor.js
I have come to a solution to my problem.
const isFirstRender = useRef(true);
useEffect(() => {
const updatedDoc = checkForHeadings(stoneCtx.stoneContext, documentCtx);
documentCtx.setUserDocument(updatedDoc);
}, []);
useEffect(() => {
if (!isFirstRender.current) {
convertUserDocument(stoneCtx.stoneContext, documentCtx.userDocument);
} else {
isFirstRender.current = false;
}
}, [documentCtx]);
Moving isFirstRender.current = false; to an else statement actually gives me the proper results I want.
Is this the best way of achieving it, or are there better ways?
I followed the tutorial pretty closely and am testing a "like" feature with a transaction. However, when I test it with 2 devices, the count doesn't seem to hold up well.
The error is when I click the like-button on both devices, there are times that the counter goes up by two, but that are times that the counter goes up then down making it increase by 0 (when in fact there were two like buttons). Similar problem when there is two un-liking buttons pressed at the same time.
Unliking both buttons (at the same time) could also cause the counters to increase by two instead... when it should be decreasing it by two.
var liked; // global variable to check if button has been liked
document.getElementById("like-button").onclick = function () {
console.log("click");
database.ref("brands/" + brand + "/" + dealId).transaction(function(post) {
console.log("post:" , post);
if (post) {
if (post.likes && liked) {
post.likes--;
liked = false;
}
else {
post.likes++;
liked = true;
}
}
return post;
});
}
Wondering what is the problem here given I followed this transaction pretty closely.
https://firebase.google.com/docs/database/web/read-and-write
Edit: JSON I have
Brand3
Brand3ID
impressions: 0
likes: 16
views: 0
Update: I noticed that the post logs 3 times when i click the button simultaneously on 2 devices - which could possibly explain the failure in handling the global flag, but am still unable to resolve why. Usually post should only log twice, one null and one when it detects the post (Firebase realtime database transaction handler gets called twice most of the time)
I believe I found the answer.
I've learnt that firebase transaction will run multiple times until successful (references: https://firebase.google.com/docs/database/web/read-and-write, Firebase transactions in NodeJS always running 3 times?), hence my liked flag was always changing depending on the number of times the transaction ran.
I fixed it by extracting the flag out so that it is not dependent on the number of transactions ran.
var liked; // global variable
document.getElementById("like-button").onclick = function () {
if (liked){
database.ref("brands/" + brand + "/" + dealId).transaction(function(post) {
if (post) {
if (post.likes) {
post.likes--;
}
}
console.log("post:" , post);
console.log("liked: ", liked);
return post;
});
liked = false;
}
else{ // not liked
database.ref("brands/" + brand + "/" + dealId).transaction(function(post) {
if (post) {
if (post.likes) {
post.likes++;
}
}
console.log("post:" , post);
console.log("liked: ", liked);
return post;
});
liked = true;
}
}
Feel free to let me know if there's a more elegant way.
I gotta a companion script for a serviceworker and I'm trialling right now.
The script works like so:
((n, d) => {
if (!(n.serviceWorker && (typeof Cache !== 'undefined' && Cache.prototype.addAll))) return;
n.serviceWorker.register('/serviceworker.js', { scope: './book/' })
.then(function(reg) {
if (!n.serviceWorker.controller) return;
reg.onupdatefound = () => {
let installingWorker = reg.installing;
installingWorker.onstatechange = () => {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
updateReady(reg.waiting);
} else {
// This is the initial serviceworker…
console.log('May be skipwaiting here?');
}
break;
case 'waiting':
updateReady(reg.waiting);
break;
case 'redundant':
// Something went wrong?
console.log('[Companion] new SW could not install…')
break;
}
};
};
}).catch((err) => {
//console.log('[Companion] Something went wrong…', err);
});
function updateReady(worker) {
d.getElementById('swNotifier').classList.remove('hidden');
λ('refreshServiceWorkerButton').on('click', function(event) {
event.preventDefault();
worker.postMessage({ 'refreshServiceWorker': true } );
});
λ('cancelRefresh').on('click', function(event) {
event.preventDefault();
d.getElementById('swNotifier').classList.add('hidden');
});
}
function λ(selector) {
let self = {};
self.selector = selector;
self.element = d.getElementById(self.selector);
self.on = function(type, callback) {
self.element['on' + type] = callback;
};
return self;
}
let refreshing;
n.serviceWorker.addEventListener('controllerchange', function() {
if (refreshing) return;
window.location.reload();
refreshing = true;
});
})(navigator, document);
I'm a bit overwhelmed right now by the enormity of the service workers api and unable to "see" what one would do with reg.installing returning a redundant state?
Apologies if this seems like a dumb question but I'm new to serviceworkers.
It's kinda difficult to work out what your intent is here so I'll try and answer the question generally.
A service worker will become redundant if it fails to install or if it's superseded by a newer service worker.
What you do when this happens is up to you. What do you want to do in these cases?
Based on the definition here https://www.w3.org/TR/service-workers/#service-worker-state-attribute I am guessing just print a log in case it comes up in debugging otherwise do nothing.
You should remove any UI prompts you created that ask the user to do something in order to activate the latest service worker. And be patient a little longer.
You have 3 service workers, as you can see on the registration:
active: the one that is running
waiting: the one that was downloaded, and is ready to become active
installing: the one that we just found, being downloaded, after which it becomes waiting
When a service worker reaches #2, you may display a prompt to the user about the new version of the app being just a click away. Let's say they don't act on it.
Then you publish a new version. Your app detects the new version, and starts to download it. At this point, you have 3 service workers. The one at #2 changes to redundant. The one at #3 is not ready yet. You should remove that prompt.
Once #3 is downloaded, it takes the place of #2, and you can show that prompt again.
Write catch function to see the error. It could be SSL issue.
/* In main.js */
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js')
.then(function(registration) {
console.log("Service Worker Registered", registration);
})
.catch(function(err) {
console.log("Service Worker Failed to Register", err);
})
}