Race condition in Amplify.Hub signIn handler when using oath - javascript

Example code:
Hub.listen('auth', event => {
const { event: type, data } = event.payload;
if (type === 'signIn') {
const session = data.signInUserSession;
console.log('SESSION', data.signInUserSession);
setTimeout(() => {
console.log('SESSION', data.signInUserSession);
}, 100);
}
});
When using oath, after the provider redirects to my app, the Hub fires a signIn event. However, the signInUserSession property is null when the event is fired, but gets a value some time later (within 100 ms). This does not seem to occur when using Auth.signIn(email, password) directly; signInUserSession is populated when the event is fired.
What is happening here, and how can I get around it? Currently, I have an explicit delay in the code, which is a terrible hack.

Perhaps the old way of JavaScript for waiting for value to be populated is useful to ensure that code does not fail even if the it takes longer than expected in populating the value.
Here is a sample code that I normally use when no other options are available.
waitForValue(){
if(myVar!= null && typeof myVar !== "undefined"){
//value exists, do what you want
console.log(myVar)
}
else{
setTimeout(() => {this.waitForValue()}, 100);
}
}
You can refactor this sample code as per your need.
Alternatively, AWS Amplify also have other ways to get current logged in user session. e.g. Auth.currentAuthenticatedUser() and Auth.currentSession() return promise. They can be used like this
private async getUser(){
let user = null;
try {
user = await Auth.currentAuthenticatedUser();
//console.log(user);
} catch (err) {
//console.log(err);
}
//return user;
}

i am not used to aws amplify - just read some github and so far i can see we will need info about your userPool implementation - i guess some weird callback issue
But for a workaround you can proxy the reference:
const event = {type: "signIn", data: {signInProperty: "null"}}
setTimeout(()=>event.data.signInProperty = "{Stack: Overflow}", 1000)
// mock events
function emit(type, args){
console.log(type, args)
}
//initialize
let watchedValue = event.data.signInProperty
document.getElementById("app").innerHTML = event.data.signInProperty
// protect reference
Object.defineProperty(event.data, "signInProperty", {
set(newValue){
watchedValue = newValue
document.getElementById("app").innerHTML = newValue
emit("event:signInCompleted", event.data)
},
get(){
return watchedValue
}
})
<div id="app"></div>

Related

Best way to turn JSON change into event

I'm creating a YouTube upload notification bot for a Discord Server I am in using the YouTube RSS Feed and am having problems with it. I have issues with the bot sending the same video twice even though I've tried everything to fix it. The bot cycles through different users in a for loop and checks the user's latest video's ID with one stored in a JSON file. If they do not match, it sends a message and updates the JSON. Here is my current code:
function update(videoId, n) {
var u = JSON.parse(fs.readFileSync("./jsons/uploads.json"))
u[n].id = videoId
fs.writeFile("./jsons/uploads.json", JSON.stringify(u, null, 2), (err) => {
if (err) throw err;
// client.channels.cache.get("776895633033396284").send()
console.log('Hey, Listen! ' + n + ' just released a new video! Go watch it: https://youtu.be/' + videoId + "\n\n")
});
}
async function uploadHandler() {
try {
var u = require('./jsons/uploads.json');
var users = require('./jsons/users.json');
for (i = 0; i < Object.keys(users).length; i++) {
// sleep(1000)
setTimeout(function(i) {
var username = Object.keys(users)[i]
let xml = f("https://www.youtube.com/feeds/videos.xml?channel_id=" + users[username]).text()
parseString(xml, function(err, result) {
if (err) {} else {
let videoId = result.feed.entry[0]["yt:videoId"][0]
let isMatch = u[username].id == videoId ? true : false
if (isMatch) {} else {
if (!isMatch) {
u[username] = videoId
update(videoId, username)
}
}
}
});
}, i * 1000, i)
}
} catch (e) {
console.log(e)
}
}
My code is rather simple but I've had the same issue with other codes that use this method; therefore what would be the best way to accomplish this? Any advice is appreciated
There are a few issues with your code that I would call out right off the bat:
Empty blocks. You use this especially with your if statements, e.g. if (condition) {} else { // Do the thing }. Instead, you should negate the condition, e.g. if (!condition) { // Do the thing }.
You declare the function uploadHandler as async, but you never declare that you're doing anything asynchronously. I'm suspecting that f is your asynchronous Promise that you're trying to handle.
You've linked the duration of the timeout to your incrementing variable, so in the first run of your for block, the timeout will wait zero seconds (i is 0, times 1000), then one second, then two seconds, then three...
Here's a swag at a refactor with some notes that I hope are helpful in there:
// Only require these values once
const u = require('./jsons/uploads.json');
const users = require('./jsons/users.json');
// This just makes the code a little more readable, I think
const URL_BASE = 'https://www.youtube.com/feeds/videos.xml?channel_id=';
function uploadHandler() {
Object.keys(users).forEach(username => {
// We will run this code once for each username that we find in users
// I am assuming `f` is a Promise. When it resolves, we'll have xml available to us in the .then method
f(`${URL_BASE}${username}`).then(xml => {
parseString(xml, (err, result) => {
if (!err) {
const [videoId] = result.feed.entry[0]['yt:videoId']; // We can use destructuring to get element 0 from this nested value
if (videoId !== u[username].id) {
// Update the in-memory value for this user's most recent video
u[username].id = videoId;
// Console.log the update
console.log(`Hey listen! ${username} just released a new video! Go watch it: https://youtu.be/${videoId}\n\n`);
// Attempt to update the json file; this won't affect the u object in memory, but will keep your app up to date
// when you restart it in the future.
fs.writeFile('./jsons/uploads.json', JSON.stringify(u, null, 2), err => {
if (err) {
console.err(`There was a problem updating uploads.json with the new videoId ${videoId} for user ${username}`);
}
});
}
}
});
})
// This .catch method will run if the call made by `f` fails for any reason
.catch(err => console.error(err));
});
}
// I am assuming that what you want is to check for updates once every second.
setInterval(uploadHandler, 1000);

Using async function to stockfish postMessage

I tried working with stockfish.js and I have this one problem. I also used #ninjapixel/chess for easier methods than the original.
(async function() {
console.log('test1`');
console.log(chess.ascii());
await stockfish.postMessage('go depth 15');
console.log(chess.ascii()); // this should change, but it doesn't
console.log('test2');
})();
Why does the async function with await keyword didn't work?
Why did the test2 was logged first before the test3 since I'm using an async function, it should work, but it didn't, why is that?
Here's how I handled onmessage:
stockfish.onmessage = (e) => {
let info = e.data ? e.data : e;
if (info.includes('bestmove') || info.includes('ponder')) {
const bestmove = info.split(/ +/)[1];
const ponder = info.split(/ +/)[3];
chess.move(bestmove, {
promotion: 'q',
sloppy: true
});
// test2 was logged first before this
console.log('test3'); // ascii worked in this part
stockfish.postMessage(`position fen ${chess.fen()}`); // update fen of stockfish
}
};
If there's no way to solve this then should I actually depend on the onmessage respond? Because what I wanna do is after I do postMessage I want to actually respond after the postMessage NOT directly inside the onmessage response.
Like something this:
let bestMove = null;
stockfish.onmessage = (e) => {
let info = e.data ? e.data : e;
if (info.includes('bestmove') || info.includes('ponder')) {
bestMove = info.split(/ +/)[1];
// set bestmove after calculating...
}
}
(async function() {
await stockfish.postMessage('go depth 15');
// do the move after the message
chess.move(bestMove);
await stockfish.postMessage(`position fen ${chess.fen()}`);
})();
The await keyword, didn't have any effect in your code because the function stockfish.postMessage doesn't return a Promise. postMessage emits an event that will be processed in the next tick. This is why console.log('test3') was logged after console.log('test2'). After emitting the event it will return synchronously, logging console.log('test2').
stockfish.onmessage will need to be the control centre of all events. It will need to understand the different messages, and do the appropiate actions. You dont need to put the implemention of the actions in the onmessage. You can create a function and called it from stockfish.onmessage... and then inside the action function you could call to stockfish.postMessage if you need to notify stockfis of a change... and so on.... and on...

Firebase Functions: Why do they sometimes fail? Why do they often complete without error but don't fulfill all tasks?

This perplexes me. I'm six months into a firebase project and have been using Javascript for firebase-functions. I've learned a lot along the way by adding transactions, promises, batch writes and neat tricks. However, it seems like complete luck for a function to execute correctly. More often than not, the functions do execute correctly, but there are strange periods when bursts of consecutive function calls where functions half complete with no errors in the logs.
For example. I have a function for when a new user joins my app. It does a little bit of server data construction and also notifies the two admins that a new user has joined. Last night I did a test run with two new users and got no notification, but their user profiles constructed correctly on the server database. I checked the function logs and there were no errors.
Am I not handling Promises in the correct way? If a firebase function hangs, does it mess up the next few function calls?
exports.onNewUser = functions.firestore
.document('/users/{userId}')
.onCreate(async (snapshot, context) => {
user = snapshot.data().username;
//Notification payload
const payload = {
notification: {
title: `New user!`,
body: `${user} has joined [AppName]`
}
};
var promises = [];
//Check if usename unique
var passed = true;
promises.push(db.runTransaction(async t => {
const docRef = db.collection('users').doc('index');
const doc = await t.get(docRef);
var newIndex = doc.data().usernames;
if (newIndex[user.toUpperCase()] == true) {
t.delete(snapshot.ref);
passed = false;
return null;
} else {
newIndex[user.toUpperCase()] = true;
t.set(docRef, { 'usernames': newIndex });
}
}));
if (!passed) return Promise.all(promises);
//add new user to Algolia database
const algoliasearch = require('algoliasearch');
const algoliaClient = algoliasearch(functions.config().algolia.appid, functions.config().algolia.apikey);
const collectionIndex = algoliaClient.initIndex(collectionIndexName);
await saveDocumentInAlgolia(snapshot, collectionIndex);
//Notify Admins
db.collection('notificationTokens')
.doc(admin1)
.get().then((doc) => {
if (doc.exists && doc.data().notificationToken != null)
promises.push(pushNotification(doc.data().notificationToken, payload));
});
db.collection('notificationTokens')
.doc(admin2)
.get().then((doc) => {
if (doc.exists && doc.data().notificationToken != null)
promises.push(pushNotification(doc.data().notificationToken, payload));
});
return Promise.all(promises);
});
Just change
return Promise.all(promises);
to
return await Promise.all(promises);
You have to wait till the promises resolve before you return the function, as that would stop the instance of the cloud function.

Await a promise multiple times in parallel

I have some server API methods I call during startup of my application at different places, which can't be refactored in terms of where they are called, let's say getSettings() and getSchedule(). Both methods are based on a login(): Promise<Account> method as settings as well as the schedule are user-based.
Now I have solved the beginning of getSettings() and getSchedule() like the following:
class UserFunctions {
private fetch_login: Promise<AccountItem> = undefined;
async getSchedule(): Promise<any> {
var account = getAccount();
if (!account) {
// No account loaded
let isLogginIn = this.fetch_login;
if (!this.fetch_login) {
// Not logging in from any parallel method
this.fetch_login = login();
}
account = await this.fetch_login;
this.fetch_login = undefined;
}
// Now get schedule...
}
}
The idea behind is that the login() function is only called once no matter how often it could be called. That's why I keep a reference on the Promise to await it multiple times. This works, but I noticed that sometimes when login() is done getSettings() gets earlier the okay to continue work and getSchedule() stays a few seconds until it continues executing. Sometimes it's the other way around and sometimes both methods return in the same time.
Here I have a print of the output:
06-05 16:46:08.126 27376 27397 I ReactNativeJS: Logging in back
06-05 16:46:08.690 27376 27397 I ReactNativeJS: Logged in back
06-05 16:46:08.696 27376 27397 I ReactNativeJS: Schedule downloaded
06-05 16:46:09.274 27376 27397 I ReactNativeJS: Logged in back
Do you have any idea how the code can be improved that once login() is done, both methods continue working?
I think you've got the right idea, but the logic needs a little tweaking:
var account = getAccount();
if (!account) {
// No account loaded
if (!this.fetch_login) {
// Not logging in from any parallel method
this.fetch_login = login();
}
account = await this.fetch_login;
}
Basically the idea is that you'll set fetch_login to have the value of the promise the first time it gets called. After that, you can await that same promise as many times as you need to.
Two things:
You're calling fetch_login. You should just be referring to it:
account = await this.fetch_login; // <== No ()
Remove this line:
this.fetch_login = undefined;
E.g.:
async getSchedule(): Promise<any> {
var account = getAccount();
if (!account) {
// No account loaded
if (!this.fetch_login) {
// Not logging in from any parallel method
this.fetch_login = login();
}
account = await this.fetch_login;
}
// Now get schedule...
}
You keep the promise resulting from the login attempt, so you can await it each time. (Nothing about awaiting a promise prevents your doing so again.)
It seems like this should be handled at the level of getAccount and login, though, rather than within UserFunctions. getAccount and login appear to be global (within your app or module or whatever). I would probably have them manage this, with getAccount returning a promise:
var accountPromise = null;
function getAccount() {
if (!accountPromise) {
accountPromise = login();
}
return accountPromise;
}
then
async getSchedule(): Promise<any> {
var account = await getAccount();
// Now get schedule...
}
Example:
var accountPromise = null;
function getAccount() {
if (!accountPromise) {
accountPromise = login();
}
return accountPromise;
}
function login() {
return new Promise(resolve => {
console.log("Logging in...");
setTimeout(() => {
console.log("Logged in");
resolve({}); // {} = the account
}, 800);
});
}
class UserFunctions {
/*private*/ fetch_login/*: Promise<AccountItem>*/ = undefined;
async getSchedule()/*: Promise<any>*/ {
var account = await getAccount();
// Now get schedule...
console.log("Got account, getting schedule");
}
}
// The first time it's used
const u1 = new UserFunctions();
u1.getSchedule(); // Logs in and gets schedule
// The second time it's used
setTimeout(() => {
const u2 = new UserFunctions();
u2.getSchedule(); // Gets schedule (already logged in)
}, 2000);

How to pause a Javascript asynchronous function without setTimeout?

I have a function that checks the existence of token in the database. The problem is it takes some amount of time to return that bool value and I kinda need to pause the function so that the function realizes that the token has existed and run the query again.
const registerToken = dispatch => {
var tokenExisted = null
do {
let token = generateRandomToken();
firebase.database().ref(`/Token`).orderByChild("token").equalTo(token).once("value", snapshot => { // check whether token exists
if (!snapshot.val()) { // if token not exist
token = false;
// register token to firebase
} else {
token = true; // continue the loop to generate a new token and query again
}
})
} while (tokenExisted === true);
}
My setup is basically a do-while loop, when the function first gets call
tokenExisted = null, then a random 4 digit token will be generated and a query will be dispatched to firebase and verify it token has existed.
If token has existed, then tokenExisted = true. I expect it the assignment to be executed but the single threaded nature of Javascript will reach the end of the loop before the query return anything.
I figured to use setTimeout and periodically add some small amount of time whenever tokenExisted = null to kinda safe guard so that the function will always catch when query function returns anything.
Has anyone had a better approach to achieve the same thing?
You might want to call the function itself recursively, as such.
const registerToken = dispatch => {
let token = generateRandomToken();
const tokenObjectRef = firebase.database().ref(`/Token`);
tokenObjectRef.orderByChild("token").equalTo(token).once("value")
.then(snapshot => {
if (!snapshot.val()) {
// success!
} else {
registerToken(dispatch) // call itself again
}
})
.catch(error => {} ))
}
The logic is that token will be refreshed during each new iteration, should the process fails and a new query is needed (if this is what you need).
Note: avoid using do-while in async logic. Plan carefully ahead as you might encounter lots of logic error and it is hard to trace.
Call the function recursively.
function get_token_then(callback_when_token_found) {
firebase.database().etc.etc(function (data) {
if (data == what_you_want) {
callback_when_token_found(data);
} else {
// You might want to wrap this in setTimeout in order to throttle your database calls
get_token_then(callback_when_token_found);
}
}
}

Categories

Resources