How to request permissions in React Native / Expo? - javascript

EDIT: As of 9/12/2021, this method of requesting permissions has been depreciated for anything passed Expo SKD Version 40.
I am trying to request a user's location. I tried writing an async function to tell me if my request was processed, but it is ignored. I am prompted with a "location request" but I believe it is actually the Expo app and not my function.
Below is some of my code:
import React, { useState, useEffect, Component }from "react";
import { Permissions , Request } from 'expo-permissions'
//This is the async function I wrote to prompt the user to give permission
async function getLocationAsync(){
const { status, permissions } = await Permissions.askAsync( Permissions.LOCATION);
if (status === 'granted'){
console.log('It worked!')
}
else {
throw new Error('Location permission not granted');
}
}
//This logs the terminal and lets me know that the user's current location has been isolated (mounting). When the app no longer needs their location, it dismounts to prevent a memory leak.
const Screen = ({navigation})=> {
const [user_latitude, setUserLatitude] = useState(0)
const [user_longitude, setUserLongitude] = useState(0)
const [position_error, setPositionError] = useState(null)
useFocusEffect(
React.useCallback(()=> {
let isActive = true;
const fetchGeoPosition = () => {
navigator.geolocation.getCurrentPosition(
position => {
if (isActive){
setUserLatitude(position.coords.latitude);
setUserLongitude(position.coords.longitude);
setPositionError(null);
console.log('Location Accessed')
}
setIsLoading(false)
},
error => isActive && setPositionError(error.message),
{enableHighAccuracy: true, timeout: 0, maximumAge: 1000}
);
}
fetchGeoPosition()
return () =>{
isActive = false
console.log('Location Severed')
}
},
[],
),
)

Check this library for Permission on react-native
Here's https://www.npmjs.com/package/react-native-permissions.
For Android only there a default Package in react-native. ( PermissionAndroid)
https://reactnative.dev/docs/permissionsandroid
Update your manifest file also. Indicating that the application going to use external resource which requires user permission.
https://developer.android.com/guide/topics/manifest/uses-permission-element
And For iOS update info.plist file
https://www.iosdev.recipes/info-plist/permissions/

Related

Is there a reason why a promise will be undefined on Reactjs build but gets resolved on the localhost?

I have a react project setup with Redux and Axios. This is a function I am using to get data from an endpoint in my Redux actions:
export const getCSEfirstStageApplicants = () => async (dispatch) => {
try {
dispatch(LOADING());
const response = await axios.get(
`${baseUrl}/Franchisee/CSEFirstStageApplication`
);
if (response.status === 200) {
const { message, data } = response?.data || {};
return { message, data };
}
} catch (error) {
const { message } = error?.response?.data || {};
return message;
} finally {
dispatch(STOP_LOADING());
}
};
My component looks something like this:
import { useState, useEffect } from "react";
import {getCSEfirstStageApplicants} from "../../../redux/user/actions";
import { useDispatch } from "react-redux";
const MyComponent = () => {
const [cseApplicants, setCseApplicants] = useState([]);
const dispatch = useDispatch();
const getFirstStage = async () => {
const response = await dispatch(getCSEfirstStageApplicants());
if (response && response.data) {
console.log(response);
setCseApplicants(response.data);
return;
}
setCseApplicants([]);
};
useEffect(() => {
getFirstStage();
}, [dispatch]);
}
Apparently, this is working fine on my localhost. But when I build the app and push it to the server, it is giving an error on Chrome and Firefox and is working on Edge (browsers I have tested), indicating that response is undefined.
Chrome shows this error:
Firefox shows this error:
At first I thought it was the way the network call was made as preflight seemed to come after the xhr request. But checking Chrome showed that wasn't the error.
Another indication was an error that showed up as asyncgenerator error. I haven't been able to find a relation with this.
add properties to the empty object
const { message, data } = response?.data || {data:[], message:''};

Next JS with magic login, trying to grab the router after being redirected from email login

I am using magic login in javascript and next js to add users to my app, it works just fine, but the problem I am having is. When a user lands back on the page I have to manually refresh the page to get my data. I have tried checking for the url param, and reloading if it exists then changing the changing the url to not have the url param so it doesn't loop.
But the router isn't even found after clicking the login button from the email sent from magic login.
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { gql, useQuery } from "#apollo/client";
import useSWR from "swr";
import { useEffect } from "react";
import Layout from "../components/layout";
import { useRouter } from "next/router";
export const GET_DAD_HAT = gql`
query FindUserByID($id: ID!) {
findUserByID(id: $id) {
_id
hats {
data {
name
}
}
}
}
`;
const fetcher = (url) => fetch(url).then((r) => r.json());
function useAuth() {
const { data: user, error, mutate } = useSWR("/api/user", fetcher);
const loading = user?.token === false || user === undefined;
return {
user,
loading,
error,
};
}
export default function Profile() {
const { user, loading } = useAuth();
const router = useRouter();
useEffect(() => {
console.log("window", window);
console.log(Object.keys(router.query)[0]);
if (Object.keys(router.query)[0] === "magic_credentials") {
router.reload(window.location.pathname);
window.history.pushState({}, document.title, "/" + "profile");
}
if (loading) {
}
}, []);
return (
<Layout>
<main>{loading ? "Loading..." : <Data user={user} />}</main>
</Layout>
);
}
const Data = ({ user }) => {
const { loading, error, data } = useQuery(GET_DAD_HAT, {
variables: { id: user.id },
});
if (loading) return <h1>Loading...</h1>;
if (error) return <h1>{error.message}</h1>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
};
What happens is the data is just stuck on Loading until I manually hit refresh, after being redirected to the app from the email login flow.
UPDATE: I made a reproducible sandbox. https://omg5u.sse.codesandbox.io/login-magic
Enter your email, click the login link sent to email.
Feel free to use disposable email service like https://getnada.com/
if it's not blocked
When you arrive on profile page see that it is just loading, until you hit refresh then it should show your user id, and an empty array for data.
UPDATE: It looks like when I first land on the page the cookie lookup for fauna_client in the fetch to the user api route returns undefined. However after refreshing it returns the cookie. If I inspect the cookie is there before hitting refresh, but if I look in the terminal for next, the cookie is created after it is looked for. I think it has something to do with Magic redirect in the login api route creating the cookie after magic is logged in.
Still quite confused.
Had to do the following on a auth-verify page and a login-verify api route
useEffect(() => {
finishEmailRedirectLogin();
}, [router.query]);
const finishEmailRedirectLogin = () => {
if (router.query.magic_credential)
magicClient.auth
.loginWithCredential()
.then((didToken) => authenticateWithServer(didToken));
};
// Send token to server to validate
const authenticateWithServer = async (didToken) => {
let res = await fetch("/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + didToken,
},
});
if (res.status === 200) {
// Set the UserContext to the now logged in user
let userMetadata = await magicClient.user.getMetadata();
// await setUser(userMetadata);
router.push("/profile");
}
};
import { Magic } from "#magic-sdk/admin";
// Initiating Magic instance for server-side methods
const magic = new Magic(process.env.MAGIC_SECRET_KEY);
export default async function login(req, res) {
try {
const didToken = req.headers.authorization.substr(7);
await magic.token.validate(didToken);
res.status(200).json({ authenticated: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
}

Expo AuthSession promptAsync - no way to wait for return when getting a new access token

We have a managed Expo app using AuthSession specifically to deploy our app to a web environment. Authenticating using Auth0 and a PKCE grant.
Have run into an issue trying to get a new access token when our current access token has expired. Before making an HTTP request we check to see if our access token has expired and if it has we use AuthSession.promptAysnc() to get a new one. The problem is we have no way of waiting for promptAsync to finish before making our HTTP request.
Have we missed something?
const [request, result, promptAsync] = AuthSession.useAuthRequest(
{
redirectUri,
clientId,
scopes,
prompt: AuthSession.Prompt.Login,
extraParams: {
audience
}
},
{ authorizationEndpoint, tokenEndpoint, revocationEndpoint }
);
const refreshAuthSessionAsync = async () => {
...
promptAsync({ useProxy });
// sleep hack - wait until we have something to return
await new Promise(r => setTimeout(r, 1000));
return authState;
};
const handleFetchAsync = async (url, request) => {
...
if (Date.parse(expiresAt) < (new Date()).getTime()) {
newAuthState = await refreshAuthSessionAsync();
return newAuthState;
}
...
}
I'm working through a similar problem with an Expo managed app trying to access a refresh token after authenticating through Auth0 using a PKCE flow.
I came across this repo
https://github.com/tiagob/create-full-stack/blob/master/packages/cfs-expo-auth0/src/index.tsx
while reading through the comments of this Expo issue about refresh tokens and Auth0
https://github.com/expo/examples/issues/209.
I haven't implemented anything from this repo yet, but it seems like some of its approach may solve the issue I'm having and hopefully your problem.
I used a useEffect hook to "wait" for the request to finish and a useState hook to save the current state.
useEffect(() => {
if (state !== stateMode.request_running) return;
if (request === null || response === null || response.type !== "success") {
// Request not done
}
else {
// Request done
onLogin();
setState(stateMode.request_done);
}
});
The state I used
const stateMode = {
initialized: 1,
request_running: 2,
request_done: 3
};
const [state, setState] = useState(stateMode.initialized);
And before calling promptAsync I set
setState(stateMode.request_running);

Show notification on foreground react native firebase v6

I am using the latest react native version 0.62 and latest version of react-native-firebase i.e. v6. I am able to get the notification and it working fine on the background but its not displaying on foreground.
Here is the screenshot:
And here is my code:
checkPermission = async () => {
const enabled = await messaging().hasPermission();
console.log('enabled ******* ',enabled)
if (enabled) {
this.getFcmToken();
} else {
this.requestPermission();
}
};
getFcmToken = async () => {
const fcmToken = await messaging().getToken();
if (fcmToken) {
console.log('Your Firebase Token is:', fcmToken);
// this.showAlert('Your Firebase Token is:', fcmToken);
} else {
console.log('Failed', 'No token received');
}
};
requestPermission = async () => {
try {
await messaging().requestPermission();
// User has authorised
} catch (error) {
// User has rejected permissions
}
};
messageListener = async () => {
console.log('inside message listener ****** ')
messaging().onMessage(async remoteMessage => {
Alert.alert('A new FCM message arrived!', JSON.stringify(remoteMessage));
};
showAlert = (title, message) => {
Alert.alert(
title,
message,
[{ text: 'OK', onPress: () => console.log('OK Pressed') }],
{ cancelable: false },
);
};
componentDidMount() {
this.checkPermission();
this.messageListener();
}
By default rnfirebase not supporting displaying notification popup when app is in foreground state as they mentioned here. So push notification pop up only displayed when app is in background state or closed.
So if you want to display push notification on foreground mode also then you have to use extra library which will be display fired push notification as local notification as mention in their documentation.
If the RemoteMessage payload contains a notification property when sent to the onMessage handler, the device will not show any notification to the user. Instead, you could trigger a local notification or update the in-app UI to signal a new notification.
So as a solution you can use react-native-push-notification to fire push notification when app in foreground.
To do so, just install it by command :
npm i react-native-push-notification
For android you don't need to follow any native installation steps just install library by this command and then you can fire local push notification as below :
Create a file called NotificationController.android.js :
import React, { useEffect } from 'react';
import { Alert } from 'react-native';
import messaging from '#react-native-firebase/messaging';
import PushNotification from 'react-native-push-notification';
const NotificationController = (props) => {
useEffect(() => {
const unsubscribe = messaging().onMessage(async (remoteMessage) => {
PushNotification.localNotification({
message: remoteMessage.notification.body,
title: remoteMessage.notification.title,
bigPictureUrl: remoteMessage.notification.android.imageUrl,
smallIcon: remoteMessage.notification.android.imageUrl,
});
});
return unsubscribe;
}, []);
return null;
};
export default NotificationController;
Now, when app is in foreground state and if onMessage receive any message from firebase then PushNotification will fire local notification.
Update: For iOS
For iOS you have to install #react-native-community/push-notification-ios using this command:
npm i #react-native-community/push-notification-ios
Also follow all the native installation steps as suggested in document.
Then you can create file called NotificationController.ios.js where you can handle notification for iOS.
import { useEffect } from 'react';
import { Alert } from 'react-native';
import messaging from '#react-native-firebase/messaging';
import PushNotification from 'react-native-push-notification';
import PushNotificationIos from '#react-native-community/push-notification-ios';
const NotificationController = (props) => {
const navigation = useNavigation();
// Called when application is open by clicking on notification
// and called when application is already opend and user click on notification
PushNotification.configure({
onNotification: (notification) => {
if (notification) {
console.log(notification);
Alert.alert('Opened push notification', JSON.stringify(notification));
}
},
});
useEffect(() => {
// Usesd to display notification when app is in foreground
const unsubscribe = messaging().onMessage(async (remoteMessage) => {
PushNotificationIos.addNotificationRequest({
id: remoteMessage.messageId,
body: remoteMessage.notification.body,
title: remoteMessage.notification.title,
userInfo: remoteMessage.data,
});
});
return unsubscribe;
}, []);
return null;
};
export default NotificationController;
Now, call <NotificationController /> in you Home screen or App initial routing file.
I agree with all the above solutions...
I just wanted to add that, if you don't have channel id the use
PushNotification.createChannel(
{
channelId: 'fcm_fallback_notification_channel', // (required)
channelName: 'My channel', // (required)
channelDescription: 'A channel to categorise your notifications', // (optional) default: undefined.
soundName: 'default', // (optional) See `soundName` parameter of `localNotification` function
importance: 4, // (optional) default: 4. Int value of the Android notification importance
vibrate: true, // (optional) default: true. Creates the default vibration patten if true.
},
created => console.log(`createChannel returned '${created}'`),
);
and be careful while using
const dat = {
channelId: 'fcm_fallback_notification_channel', // (required)
channelName: 'My channel',
//... You can use all the options from localNotifications
message: notification.body, // (required)
title: notification.title,
};
console.log(dat)
PushNotification.localNotification(dat);
In some case when title: undefined, or title: Object{}, same for message might be happening so console log every thing and put it inside localNotification fuction
Following #Kishan Bharda solution, I had to do something different for IOS foreground notifications (here, I have the code in index.js instead of a different file):
import { AppRegistry, Platform } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
import PushNotificationIOS from "#react-native-community/push-notification-ios";
import PushNotification from "react-native-push-notification";
if (Platform.OS === 'ios') {
// Must be outside of any component LifeCycle (such as `componentDidMount`).
PushNotification.configure({
onNotification: function (notification) {
console.log("NOTIFICATION:", notification);
const { foreground, userInteraction, title, message } = notification;
if (foreground && (title || message) && !userInteraction) PushNotification.localNotification(notification);
notification.finish(PushNotificationIOS.FetchResult.NoData);
}
});
}
AppRegistry.registerComponent(appName, () => App);

Setting a timer for a long period of time, i.e. multiple minutes

I want to use firebase auth with react native for Login and Signup but I got a yellow error:
Setting a timer for a long period of time, i.e. multiple minutes, is a performance and correctness issue on Android as it keeps the timer module awake, and timers can only be called when the app is in the foreground. See (https://github.com/facebook/react-native/issues/12981) for more info. (Saw setTimeout with duration 111862ms)
How Can I Fix That?
I don't want to ignore that, I want to understand this error and solve that with the best and Standard way.
And This is my Code:
export default class Login extends Component {
constructor(props) {
super(props)
this.state = {
email: '',
password: '',
response: ''
}
this.signUp = this.signUp.bind(this)
this.login = this.login.bind(this)
}
async signUp() {
try {
await firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password)
this.setState({
response: 'Account Created!'
})
setTimeout(() => {
this.props.navigator.push({
id: 'App'
})
}, 1500)
} catch (error) {
this.setState({
response: error.toString()
})
}
}
async login() {
try {
await firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password)
this.setState({
response: 'user login in'
})
setTimeout(() => {
this.props.navigator.push({
id: 'App'
})
})
} catch (error) {
this.setState({
response: error.toString()
})
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.containerInputes}>
<TextInput
placeholderTextColor="gray"
placeholder="Email"
style={styles.inputText}
// onChangeText={(email) => this.setState({ email })}
onChangeText={(email) => {console.log(email);}}
/>
<TextInput
placeholderTextColor="gray"
placeholder="Password"
style={styles.inputText}
password={true}
onChangeText={(password) => this.setState({ password })}
/>
</View>
<TouchableHighlight
onPress={this.login}
style={[styles.loginButton, styles.button]}
>
<Text
style={styles.textButton}
>Login</Text>
</TouchableHighlight>
<TouchableHighlight
onPress={this.signUp}
style={[styles.loginButton, styles.button]}
>
<Text
style={styles.textButton}
>Signup</Text>
</TouchableHighlight>
</View>
)
}
}
I Reported to Google Firebase Team: (https://github.com/firebase/firebase-js-sdk/issues/97)
To fix this issue...
Navigate to your node_modules/react-native/Libraries/Core/Timers/JSTimers.js file.
Look for the variable: MAX_TIMER_DURATION_MS
Change its value to: 10000 * 1000
Save the changes (with auto format turned off) and re-build your app.
Found this answer on https://github.com/firebase/firebase-js-sdk/issues/97#issuecomment-485410026
Just add these two lines
import { LogBox } from 'react-native';
LogBox.ignoreLogs(['Setting a timer']);
Old
This fixes the yellow box and the console log. It even fixes it for Expo.
Simply place the following script at the beginning of your codebase.
import { YellowBox } from 'react-native';
import _ from 'lodash';
YellowBox.ignoreWarnings(['Setting a timer']);
const _console = _.clone(console);
console.warn = message => {
if (message.indexOf('Setting a timer') <= -1) {
_console.warn(message);
}
};
New
YellowBox is deprecated and is replaced by LogBox
import { LogBox } from 'react-native';
import _ from 'lodash';
LogBox.ignoreLogs(['Warning:...']); // ignore specific logs
LogBox.ignoreAllLogs(); // ignore all logs
const _console = _.clone(console);
console.warn = message => {
if (message.indexOf('Setting a timer') <= -1) {
_console.warn(message);
}
};
Josh Crowther Software Engineer at Google Said:
Using multi-step short duration setTimeouts doesn't actually fix the problem though. The Timer module still stays active and the app is still subject to the performance issues indicated in the warning. The issue here is that, we have use cases that require long timers, and react-native doesn't optimize for that use case.
So the net of all that is: This bug can't be fixed here we can only work around the error there are workarounds available (see Setting a timer for a long period of time, i.e. multiple minutes) that disable the warning. Doing the work to disable the warning in our code, doesn't help the issue (beyond disabling the warning), and adds additional SDK code/weight that is completely unnecessary.
I'd recommend chiming in on the issue mentioned above (i.e. facebook/react-native#12981) if you aren't comfortable w/ the workaround provided
If you are using react-native 0.63 then do the followings in your App.js file:
import the LogBox from react-native
import { LogBox } from 'react-native';
and after all the imports in your App.js file just add this line, it is not necessary to add this line in any useEffect call.
LogBox.ignoreLogs(['Setting a timer for a long period of time'])
See the docs to learn more.
OR
If you are using react-native 0.62 then do the followings in your App.js file:
import the YellowBox from react-native
import { YellowBox } from 'react-native';
and after all the imports in your App.js file just add this line, it is not necessary to add this line in any useEffect call.
YellowBox.ignoreWarnings(['Setting a timer for a long period of time']);
See the docs to learn more.
Work around issue with yellow warning 'Setting a timer' .
copy & import following file (as fast as you can ;-))
import {Platform, InteractionManager} from 'react-native';
const _setTimeout = global.setTimeout;
const _clearTimeout = global.clearTimeout;
const MAX_TIMER_DURATION_MS = 60 * 1000;
if (Platform.OS === 'android') {
// Work around issue `Setting a timer for long time`
// see: https://github.com/firebase/firebase-js-sdk/issues/97
const timerFix = {};
const runTask = (id, fn, ttl, args) => {
const waitingTime = ttl - Date.now();
if (waitingTime <= 1) {
InteractionManager.runAfterInteractions(() => {
if (!timerFix[id]) {
return;
}
delete timerFix[id];
fn(...args);
});
return;
}
const afterTime = Math.min(waitingTime, MAX_TIMER_DURATION_MS);
timerFix[id] = _setTimeout(() => runTask(id, fn, ttl, args), afterTime);
};
global.setTimeout = (fn, time, ...args) => {
if (MAX_TIMER_DURATION_MS < time) {
const ttl = Date.now() + time;
const id = '_lt_' + Object.keys(timerFix).length;
runTask(id, fn, ttl, args);
return id;
}
return _setTimeout(fn, time, ...args);
};
global.clearTimeout = id => {
if (typeof id === 'string' && id.startsWith('_lt_')) {
_clearTimeout(timerFix[id]);
delete timerFix[id];
return;
}
_clearTimeout(id);
};
}
import { YellowBox } from 'react-native';
construct() {
YellowBox.ignoreWarnings(['Setting a timer']);
}
This ignores the warning for me. You should add this to the constructor of every page that shows the warning.
Ignoring it is not the best approach, but if you're using Firebase Realtime Database. They are looking into solving this issue with their library, even though the issue is 2 years old.
What I did and it's working with me but still I don't knwo if it's a good practice or not
Navigated to file
node_modules\react-native\Libraries\Core\Timers\JSTimers.js
there is a function const MAX_TIMER_DURATION_MS = 60 * 1000 and I increased the time to be 60 * 100000 and it stopeed appearing
I got the same issue and I think it's problem of firebase web SDK so I decided to drop firebase web SDK because it runs in js thread, not react-native thread.
And I found react-native-firebase . It's better than firebase web SDK with higher performance and this issue went away
Facing the same issue.. Seems like, we'd have to hide the warning for the time being. Here's the shortest way to do it:
componentDidMount() { console.disableYellowBox = true; ... }
For firebase/firestore users: Rather than trying to mask / hide the warning, I simply reverted to the REST API. Works like a charm :)
I'm using the firebase node package for auth
I'm using regular HTTP calls for firestore (which also gets rid of the otherwise necessary atob and bto hacks)
Snippet below just uses an HTTP client (https://github.com/hardcodet/httpclient-js), you can use whatever works for you (e.g. fetch or axios).
// Firebase App (the core Firebase SDK) is always required and
// must be listed before other Firebase SDKs
import * as firebase from "firebase/app";
import "firebase/auth";
import {LoginStatus} from "./LoginStatus";
import {auth, User} from "firebase";
import {ApiResponse, DelegateBearerAuthClient, HttpClient} from "#hardcodet/httpclient";
import {Env} from "../../Env";
export class FirebaseClient {
/**
* Creates a simple API client to use with Firestore.
* We don't want to use the JS package's implementation since it has issues with
* long-running timers - see https://github.com/firebase/firebase-js-sdk/issues/97
* #param idToken The user's ID token as retrieved through firebase auth
*/
private static getFirestoreClient(idToken: string) {
const projectId = Env.firebaseProjectId;
const baseUri = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/`
const authClient = new DelegateBearerAuthClient(async () => idToken);
return new HttpClient(baseUri, {authClient});
}
/**
* Use firebase auth for login etc. because lazy.
*/
public static async userLogin(email: string, password: string): Promise<User> {
try {
const credentials: firebase.auth.UserCredential = await auth().signInWithEmailAndPassword(email, password);
return credentials.user;
} catch (error) {
console.error(error);
return undefined;
}
}
private static resolveStatus(errorCode: string): { user: User, status: LoginStatus } {
switch (errorCode) {
case "auth/invalid-email":
return {user: undefined, status: LoginStatus.InvalidEmailAddress};
case "auth/user-not-found":
return {user: undefined, status: LoginStatus.UnknownUserId};
case "auth/wrong-password":
return {user: undefined, status: LoginStatus.WrongPassword};
case "auth/email-already-in-use":
return {user: undefined, status: LoginStatus.EmailAddressAlreadyInUse};
case "auth/weak-password":
return {user: undefined, status: LoginStatus.WeakPassword};
case "auth/user-disabled":
return {user: undefined, status: LoginStatus.UserDisabled};
case "auth/expired-action-code":
return {user: undefined, status: LoginStatus.InvalidActionCode};
default:
return {user: undefined, status: LoginStatus.Undefined};
}
}
/**
* Resolve the user's keys from the backend.
*/
public static async getSomeUserData(firebaseUserId: string, idToken: string): Promise<...> {
const client: HttpClient = FirebaseClient.getFirestoreClient(idToken);
// userData here is the name of my collection in firestore. i'm using the user ID as the document ID
var result = await client.getAs<any>(`userData/${firebaseUserId}?key=${Env.firebaseApiKey}`);
if (result.success) {
const json = result.value;
const foo = json.fields.foo.stringValue;
const bar = json.fields.bar.stringValue;
return ...
} else {
if (result.notFound) {
// that document with that key doesn't exist
console.warn("no document with key " + firebaseUserId);
return undefined;
}
throw result.createError();
}
}
public static async writeSomeData(idToken: string, firebaseUserId: string, foo: string, bar: string): Promise<...> {
const data = {
"fields": {
"foo": {
"stringValue": foo
},
"bar": {
"stringValue": bar
}
}
};
// again, just do an HTTP post, use the firebase user ID as the document key
const client: HttpClient = FirebaseClient.getFirestoreClient(idToken);
const result: ApiResponse = await client.post(`userData?documentId=${firebaseUserId}&key=${Env.firebaseApiKey}`, data);
if (result.success) {
return ...
} else {
throw result.createError();
}
}
/**
* Gets the currently logged in user, if any.
*/
public static getCurrentUser(): User {
return auth().currentUser;
}
public static async sendPasswordReset(email: string): Promise<LoginStatus> {
try {
await auth().sendPasswordResetEmail(email);
return LoginStatus.Success;
} catch (error) {
return FirebaseClient.resolveStatus(error.code).status;
}
}
/**
* Clears the current user from the session.
*/
public static async signOut() {
await auth().signOut();
}
}
Note the idToken parameter - this is simply the ID token you get off the firebase User class:
const user: User = await FirebaseClient.userLogin("me#domain.com", "mypassword");
const idToken= await user.getIdToken(false);
const data = await FirebaseClient.getSomeUserData(user.uid, idToken);
In login(), your setTimeout() call is missing an interval value. As a general, browsers now do not fire timeouts/intervals if the window/tab is in the background, or at least they are not expected to be timely. This is to prevent abusive script behaviour, and to reduce power consumption by scripts that may be polling.
Your code should work in principle, if the user switches away from the window whilst the timer is running, it will complete when they return. This is probably what you want from a UX point of view, because the users sees the transition, rather than it happening in the background when they are not looking. It helps them maintain mental context.
The yellow box is because you are setting an excessively long timer according to the message (nearly two minutes) and that is unlikely to be contextually what you want. The JS environment is warning you that what you are doing it not likely what you intend. You can mute the warning if it is what you want.
I think the most close-aide solution to this problem (until RN fixed this internally) is the solution mentioned here.
// add this code to your project to reset all timeouts
const highestTimeoutId = setTimeout(() => ';');
for (let i = 0; i < highestTimeoutId; i++) {
clearTimeout(i);
}
Being used that - against Firebase call I still receive one yellow box warning (on settimeout), but any concurrent warnings never come thereafter. I'm not sure calling this at which point may not trigger the only warning also, but not any concurrent warning throws anymore after Firebase's async calls.
Another approach is to use the react-native-ignore-warnings module.
https://github.com/jamrizzi/react-native-ignore-warnings
https://www.npmjs.com/package/react-native-ignore-warnings
This even works for Expo apps.
You would use it the following way . . .
import ignoreWarnings from 'react-native-ignore-warnings';
ignoreWarnings('Setting a timer');
can run with android too..?
I'm disabling the warning with this code
import { YellowBox } from 'react-native';
YellowBox.ignoreWarnings(['Setting a timer']);

Categories