login an anonymous user forever in Firebase with React Native - javascript

I'm doing a scheduler (calendar) app using React Native. You can add tasks to whatever day you want to do them. To store those tasks I'm using Firebase.
Now, I want that the tasks shown in each device are different. I though the best way to achieve this is logging the user anonymously (so that user doesn't have to sign up, which would be nonsense, in my opinion), and by doing so, only tasks owned by that user can show.
I have achieved signing up a new anonymous user (and I can see the new user in the project in Firebase web page). However, I'm struggling to make that logging last forever.
I had written this piece of code in the first function that my app renders:
useEffect(() => {
auth.onAuthStateChanged(function (user) {
if (user) {
navigation.navigate("Home");
} else {
auth.signInAnonymously().then(navigation.navigate("Home"));
}
});
});
where auth = firebase.auth(). However, each time that I reload my app, a new user is created.
Can anyone help me? Thanks in advance! :)

Have you tried to set RN persistence ?
import {
initializeAuth,
getReactNativePersistence,
} from 'firebase/auth/react-native'
import AsyncStorage from '#react-native-async-storage/async-storage'
export const auth = initializeAuth(app, {
persistence: getReactNativePersistence(AsyncStorage),
})

Related

Service Worker determine number of clients

We've got a SaaS app that's PWA capable and we're using Workbox for the grunt work. Up to now we've been following the tried and trusted recipe of displaying an update is available banner to the users, prompting them to update their web app.
Viewing usage data (via Sentry.io) we've noticed that most users simply seem to ignore the update banner and continue with the version they're on.
So we're looking at trying something different. This is to perform the update automatically when they change the route in the web app (when we know there's an update available).
Testing this shows that it does work. However there's a side-effect, and that is if they've got web app open in multiple tabs, then all the tabs get updated. This could be problematic for users' if they've got an un-saved form open in one of the tabs in the background - they'll potentially loose their work.
This happens during this piece of code:
// app shell page, created lifecycle hook
document.addEventListener('swUpdated', this.SetPwaRegistration, { once: true })
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (this.refreshing) {
return
}
this.refreshing = true
window.location.reload()
})
// app shell page, method in methods collection
SetPwaRegistration (event) {
// call mutation to pass the registration object to Vuex
this.PWA_REGISTRATION_SET({ pwaRegistration: event.detail })
}
// main.js
router.afterEach((to, from) => {
// retrieve the registration object from Vuex
const pwaRegistration = app.$store.getters.pwaRegistration
if (pwaRegistration) {
pwaRegistration.waiting.postMessage('skipWaiting')
}
})
the above code is from our Vue.js app code-base, the this.refreshing is set to false by default in the data property collection.
What I'd like to know if whether it is possible to determine if the Service Worker has only one client under it's control (i.e. the web app is only open in 1 browser tab), and if this is the case, the auto-update can happen without potential issues. If there's more than one client, then we'll display the update banner as usual.
As a brief update to this, I've come across code examples similar to this:
self.clients.matchAll().then(clients => {
const clientCount = clients.length
// store count of client in Vuex
})
Which looks like an option (i.e. count how many clients there are, store this in Vuex store), I'm just not sure where it should be used.
If you want to orchestrate the reload from entirely within the service worker, you can effectively do that by using the WindowClient interface to programmatically navigate to whatever the current URL is, which is roughly equivalent to a reload.
The two things to keep in in mind that navigate() will only work on WindowClients (not Workers) and that you can only call it if the service worker controls the WindowClient.
Putting that together, here's something to try:
// This can be run, e.g., in a `message` handler in your SW:
self.clients.matchAll({
// These options are actually the defaults, but just
// to be explicit:
includeUncontrolled: false,
type: 'window',
}).then((clients) => {
if (clients.length === 1) {
clients[0].navigate(clients[0].url);
}
})

Authentication with Firebase and context API in react give me a warning. Is this the right approach?

I am trying to set up an authentication on my web application in React using firebase and Context API.
I am using Context API since as long as I understood I cannot save my jwt token in local storage in order to not be vulnerable to XSS attack and at the moment I do not want to use Redux.
in my App.js I have:
const {setUserInfo} = useContext(userInfoContext);
useEffect(() => {
auth.onAuthStateChanged(user => {
if (user) {
setUserInfo({jwtToken: user.za});
} else {
setUserInfo({jwtToken: null});
}
console.log(user);
});
}, [setUserInfo]);
The methos "auth.onAuthStateChanged" is triggered every time I logged in or I logged out using firebase.auth.
The compiler tell me that to eliminate the warning I should have "[setUserInfo]" instead of "[]". However, doing as he say, the method setUserInfo is executed twice. There is a better way to achieve the result without a warning?
Your problem is that you don't clean up your effect when it is recomputed. As soon as you add setUserInfo to the dependency array, the effect is executed whenever its value changes. This means that you could potentially register many auth.onAuthStateChanged if the value of setUserInfo changes.
auth.onAuthStateChanged returns an unsubscribe function. You can simply return this function inside your effect, which will make react execute the unsubscribe function whenever the hook is executed again and prevent you from having multiple active listeners. I suggest you read more about this topic here.

Having trouble with persisting React state using localStorage with a SSR (Next.js) app

The app I'm making has customizable settings. I'd like to load default settings, then when a user makes any changes, the custom settings will be stored in localStorage. Now the next time a user comes back, we'll load the setting from their localStorage.
I'm using React context like so...
const SettingsContextProvider = (props: any) => {
const [settings, setSettings] = useState(getSettings());
useEffect(() => {
localStorage.setItem('settings', JSON.stringify(settings))
}, [settings]);
return (...some jsx...);
}
Then getSettings()...
getSettings() {
// get from local storage
if (process.browser) {
const localSettings = localStorage.getItem('settings');
if (localSettings) return JSON.parse(localSettings );
}
// fall back to default settings
return "Something else";
}
The issue I'm having is that the server side load (on the node side), we don't have local storage (which is why I check for process.browser), so it falls back to default settings. THEN when it gets to the browser, it seems to call getSettings() again, in which case we DO have local storage and it loads from there.
That works, but then I get an error:
Text content did not match. Server: "Something else" Client: "Something custom"
So I understand that the server isn't matching the client and it's upset about it. I get WHY it's happening, but I have NO IDEA how to fix it. Maybe I need to implement reducers or use cookies?
Has anyone come across this?
I'm more than happy to share my full repo code if it'll help.
I'm fairly comfortable with react but have pretty much NEVER used react hooks in my own code.
However, I feel like I wouldn't put the updating of the localStorage into a useEffect function like that (and I could be totally wrong about that).
So, first, I would check that that useEffect function is being called when you're expecting it to be.
And then, I would write a function
const updateSettings = (newSettings) => {
localStorage.setItem('settings', JSON.stringify(newSettings))
setSettings(newSettings);
}
And use that updateSettings function to pass down to components.
BUT, I could be totally off there, like I said I don't use react hooks and only have a theoretical understanding.

check if user first visit on app with react-native

I'm new to react and I want to show new users illustration of how my app work.
how can I achieve that?
Am I need to download something from npm?
You may use AsyncStorage for that. AsyncStorage is an unencrypted, asynchronous, persistent, key-value storage system that is global to the app. It should be used instead of LocalStorage.
import {AsyncStorage} from 'react-native';
const tutorialShown = async () => {
return await AsyncStorage.getItem('#myApp:TUTORIAL_SHOWN');
}
// somewhere in your render method
if (!tutorialShown()) {
return <Tutorial />
}
//somewhere in your Tutorial's componentDidMount
AsyncStorage.setItem('#myApp:TUTORIAL_SHOWN')
Details: https://facebook.github.io/react-native/docs/asyncstorage

How do I integrate the cognito hosted UI into a react app?

I am creating a react app - using create-react-app and amplify - and I am trying to set up authentication. I don't seem to be able to handle the federated logins using the hosted UI.
There are some pages which require no authentication to reach and then some which require a user to be logged in. I would like to use the hosted UI since that's prebuilt. I have been following the getting started docs here: https://aws-amplify.github.io/docs/js/authentication
For background I have the following components:
- Amplify - an amplify client which wraps calls in methods like doSignIn doSignOut etc. The idea is to keep all this code in one place. This is a plain javascript class
- Session - provides an authentication context as a React context. This context is set using the amplify client. It has HOC's for using the context
- Pages - some wrapped in the session HOC withAuthentication which only renders the page if the user has logged in
This structure is actually taken from a Firebase tutorial: https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial/
Maybe this is just not feasible with Amplify? Though the seem similar enough to me that it should work. The basic idea is that the Session provides a single auth context which can be subscribed to by using the withAuthentication HOC. That way any component that requires a user will be rendered as soon as a user has logged in.
Originally I wrapped the entire App component in the withAuthenticator HOC provided by amplify as described in the docs. However this means that no pages are accessible without being authenticated - home page needs to be accessible without an account.
Next I tried calling to the hosted UI with a sign in button and then handling the response. The problem is when the hosted UI has logged a user in then it redirects back to the app causing it to reload - which is not ideal for a single page app.
Then I tried checking if the user is authenticated every time the app starts - to deal with the redirect - but this becomes messy as I need to move a lot of the amplify client code to the Session context so that it can initialise correctly. The only way I can see to get this is using the Hub module: https://aws-amplify.github.io/docs/js/hub#listening-authentication-events The downside is that after logging in, the app refreshes and there's still a moment when you are logged out which makes the user experience weird.
I would have thought that there would be a way to not cause an application refresh. Maybe that's just not possible with the hosted UI. The confusing thing to me is that the documentation doesn't mention it anywhere. In actual fact there is documentation around handling the callback from the hosted UI which as far as I can see never happens because the entire page refreshes and so the callback can never run.
I've tried to trim this down to just what's needed. I can provide more on request.
Amplify:
import Amplify, { Auth } from 'aws-amplify';
import awsconfig from '../../aws-exports';
import { AuthUserContext } from '../Session';
class AmplifyClient {
constructor() {
Amplify.configure(awsconfig);
this.authUserChangeListeners = [];
}
authUserChangeHandler(listener) {
this.authUserChangeListeners.push(listener);
}
doSignIn() {
Auth.federatedSignIn()
.then(user => {
this.authUserChangeListeners.forEach(listener => listener(user))
})
}
doSignOut() {
Auth.signOut()
.then(() => {
this.authUserChangeListeners.forEach(listener => listener(null))
});
}
}
const withAmplify = Component => props => (
<AmplifyContext.Consumer>
{amplifyClient => <Component {...props} amplifyClient={amplifyClient} />}
</AmplifyContext.Consumer>
);
Session:
const provideAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: null,
};
}
componentDidMount() {
this.props.amplifyClient.authUserChangeHandler((user) => {
this.setState({authUser: user});
});
}
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withAmplify(WithAuthentication);
};
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
render() {
return (
<AuthUserContext.Consumer>
{user =>
!!user ? <Component {...this.props} /> : <h2>You must log in</h2>
}
</AuthUserContext.Consumer>
);
}
}
return withAmplify(WithAuthentication);
};
The auth context is provided once at the top level:
export default provideAuthentication(App);
Then pages that require authentication can consume it:
export default withAuthentication(MyPage);
What I would like to happen is that after the user signs in then I can set the AuthUserContext which in turn updates all the listeners. But due to the redirect causing the whole app to refresh the promise from Auth.federatedSignIn() can't resolve. This causes the user to be displayed with You must log in even though they just did.
Is there a way to block this redirect whilst still using the hosted UI? Maybe launch it in another tab or in a popup which doesn't close my app? Or am I going about this the wrong way? It just doesn't feel very 'Reacty' to cause full page refreshes.
Any help will be greatly appreciated. I can provide more details on request.
Instead of chaining onto the Auth's promise, you can use Amplify's build-in messaging system to listen to events. Here is how I do it in a custom hook and how I handle what gets rendered in Redux.
import { Auth, Hub } from 'aws-amplify';
import { useEffect } from 'react';
function useAuth({ setUser, clearUser, fetchQuestions, stopLoading }) {
useEffect(() => {
Hub.listen('auth', ({ payload: { event, data } }) => {
if (event === 'signIn') {
setUser(data);
fetchQuestions();
stopLoading();
}
if (event === 'signOut') {
clearUser();
stopLoading();
}
});
checkUser({ fetchQuestions, setUser, stopLoading });
}, [clearUser, fetchQuestions, setUser, stopLoading]);
}
async function checkUser({ fetchQuestions, setUser, stopLoading }) {
try {
const user = await Auth.currentAuthenticatedUser();
setUser(user);
fetchQuestions();
} catch (error) {
console.log(error);
} finally {
stopLoading();
}
}

Categories

Resources