Angular / Ionic mobile app ios does not fetch from Firebase using angularfire - ios

I am trying to test a little Ionic/Angular sample app on an iOS Emulator.
On the web, all the requests to firestore using angularfire work perfectly fine.
Somehow if I try to execute the same app on the emulator, it keeps loading for the response of the request (if it was a empty response it would say that no results could be retrieved).
What is going on? Do i need to set something specifically for the Emulator to work and perform requests to Firestore?

import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { Capacitor } from '#capacitor/core';
import { initializeAuth, indexedDBLocalPersistence } from 'firebase/auth';
import { getAuth } from 'firebase/auth';
const firebaseApp = initializeApp({
apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.VUE_APP_FIREBASE_DATABASE_URL,
projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId:
process.env.VUE_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.VUE_APP_FIREBASE_APP_ID,
});
function whichAuth() {
let auth
if (Capacitor.isNativePlatform()) {
auth = initializeAuth(firebaseApp, {
persistence: indexedDBLocalPersistence
})
} else {
auth = getAuth()
}
return auth
}
export const auth = whichAuth()
const db = getFirestore();
export const auth = whichAuth();
export { firebaseApp, db };
Then in your component, cal your method like this await signInAnonymously(auth);. Don't forget to import the auth we exported at the top.

[Edit: updated with instructions Firebase JS SDK version 9 (modular)]
This error occurs because Firebase Auth incorrectly detects its environment as a normal browser environment and tries to load remote Google APIs, which results in the error you see in the console:
TypeError: undefined is not an object (evaluating 'gapi.iframes.getContext')
Fortunately, Firebase Auth already has logic to handle running in Cordova/Ionic apps, you just need to tell it which platform it's on.
For Firebase JS SDK version 9 (modular)
Simply import the Cordova Firebase Auth implementation:
import { getAuth } from 'firebase/auth';
For Firebase JS SDK <9 or the compatibility modules (auth/compat)
In capacitor.config set server: { iosScheme: "ionic" }:
// capacitor.config.json
{
"server": {
"iosScheme": "ionic"
}
}
There's a check in the auth/compat library here which, when it sees the URL scheme "ionic://", uses its Ionic/Cordova loading logic, and otherwise falls back to normal browser logic which fails with the error above.
Recent versions of Capacitor changed the URL scheme to "capacitor://" which fails this test but you can override it in your capacitor.config file (see the config option iosScheme).
(See also #alistairheath's comment here).

Been struggling a lot with this issue too but I managed to fix it. For those who need help here's my code.
You can delete all Firebase related imports from app.module.ts since this solution only uses Firebase.
The packages rxfire and #angular/fire can be removed from your package.json. The only dependency I have is "firebase": "^9.6.1".
I used observables for the getObject and list functions since that's what I'm used to and I didn't want to rewrite my original code.
import { Injectable } from '#angular/core';
import { Capacitor } from '#capacitor/core';
import { environment } from '#environment';
import { initializeApp } from 'firebase/app';
import { Auth, getAuth, indexedDBLocalPersistence, initializeAuth, signInWithCustomToken } from 'firebase/auth';
import { Database, getDatabase, onValue, orderByChild, query, ref } from 'firebase/database';
import { Observable, Observer, from } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class FirebaseService {
private readonly database: Database;
private readonly auth: Auth;
constructor() {
const firebaseApp = initializeApp(environment.firebase);
if (Capacitor.isNativePlatform()) {
initializeAuth(firebaseApp, {
persistence: indexedDBLocalPersistence
});
}
this.database = getDatabase(firebaseApp);
this.auth = getAuth(firebaseApp);
}
connectFirebase(firebaseToken) {
return from(signInWithCustomToken(this.auth, firebaseToken));
}
disconnectFirebase() {
return from(this.auth.signOut());
}
getObject<T>(path: string): Observable<T> {
return new Observable((observer: Observer<T>) => {
const dbRef = ref(this.database, path);
const listener = onValue(dbRef, snapshot => {
const data = snapshot.val();
observer.next(data);
});
return {
unsubscribe() {
listener();
}
};
});
}
public list<T>(path: string, orderChildBy?: string): Observable<Array<T>> {
return new Observable<Array<T>>((observer: Observer<Array<T>>) => {
const dbRef = ref(this.database, path);
const dbReference = !orderChildBy ? dbRef : query(dbRef, orderByChild(orderChildBy));
const listener = onValue(dbReference, snapshot => {
const data = Object.values<T>(snapshot.val() || {});
console.log(path, data);
observer.next(data);
});
return {
unsubscribe() {
listener();
}
};
});
}
}
For those who can't see the error message thrown by firebase try the following command in your Safari console to see the error.
window.location.reload()

The real problem: firebase-js-sdk on mobile iOS assumes google API (gapi) exists on the window, even when it isn't used.
I found a work around: Mock window.gapi before using firebase auth login:
window['gapi'] = {
load: (name: string) => Promise.resolve(),
iframes: {
getContext: () => {
return {
iframe: {
contentWindow: {
postMessage: (message: any) => {
console.log("gapi iframe message:", message);
}
}
}
}
}
}
} as any;

Related

Expo React Native app. Error using expo-secure-store methods [The method or property SecureStore.setItemAsync is not available on ios]

I've followed expo documentation to include this library to my expo managed react native application. SecureStore
I am using:
expo: 44.0.5
react-native: 0.64.3 (SDK 44)
expo-secure-store: 11.1.0
expo-dev-client: 0.8.6
react & react-dom 18.0.0
typescript
In my App.tsx:
import 'expo-dev-client'
import { deleteValueFor, getValueFor, save } from './src/core/infrastructure/storage/secureStore'
import { REFRESH_TOKEN } from './src/core/infrastructure/config/constants'
....
export default function App(): JSX.Element | null {
....
useEffect(() => {
;(async () => {
try {
const refreshToken = await getValueFor(REFRESH_TOKEN)
...
// things I do whit refreshToken
...
} catch (e) {
console.warn(e)
}
})()
}, [])
const login = async (authUser: AuthUser) => {
const { token, refreshToken, user } = authUser
if (!user) {
throw 'Error al obtener los datos del usuario desde el context'
}
setToken(token)
save({ key: REFRESH_TOKEN, value: refreshToken }) // <---- The error occurs here
}
}
In secureStore.ts
import * as SecureStore from 'expo-secure-store'
export async function save({ key, value }: { key: string; value: string }): Promise<void> {
await SecureStore.setItemAsync(key, value)
}
export async function getValueFor(key: string): Promise<string | null> {
try {
return await SecureStore.getItemAsync(key)
} catch (error) {
return null
}
}
export async function deleteValueFor(key: string): Promise<void> {
await SecureStore.deleteItemAsync(key)
}
export async function checkAvailability(): Promise<boolean> {
return SecureStore.isAvailableAsync()
}
I execute this command to run the app in simulator:
expo start --dev-client --ios
The application is running fine inside the simulator, no errors with that. And after I fill login credentials and press in login button, this is the error message I'm getting:
[Unhandled promise rejection: Error: The method or property SecureStore.setItemAsync is not available on ios, are you sure you've linked all the native dependencies properly?]
at http://192.168.1.3:8082/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&strict=false&minify=false:111339:321 in _createSuperInternal
at node_modules/expo-modules-core/build/errors/CodedError.js:10:8 in constructor
at http://192.168.1.3:8082/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&strict=false&minify=false:111384:321 in _createSuperInternal
at node_modules/expo-modules-core/build/errors/UnavailabilityError.js:9:42 in constructor
at node_modules/expo-secure-store/build/SecureStore.js:103:14 in setItemAsync
at node_modules/expo-secure-store/build/SecureStore.js:97:7 in setItemAsync
at src/core/infrastructure/storage/secureStore.ts:4:8 in save
at src/core/infrastructure/storage/secureStore.ts:3:7 in save
at App.tsx:87:4 in login
I don't know what's wrong. Please help.
If anybody is working on a similar issue.
According to this issue
https://github.com/expo/expo/issues/16906
I had to rebuild my application due to expo-dev-client lib I'd being using in order to generate a new artifact. After that I could save my token using the expo-secure-store functions without problem.
I've fixed the error with replacing the expo package with:
https://github.com/react-native-async-storage/async-storage
The other alternatives are:https://reactnative.directory/?search=storage

React native app through Expo, getting firestore permission error:

This is my first post here so please let me know if I'm not posting this correctly.
I keep getting the following error in the debug logs of my react native Expo app on the iOS simulator when I have an authenticated user trying to retrieve a firestore document:
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Here is firebase.js config file:
import "firebase/firestore";
import "firebase/storage";
import * as firebase from 'firebase';
// Initialize Firebase
const firebaseConfig = {
apiKey: ... //removed for this post, but it is correct and validated
};
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
const auth = firebase.auth();
export { auth };
export default db;
Here is my App.js:
import React, { useEffect, useState } from 'react';
import db, { auth } from './firebase';
const getUserData = async(uid) => {
try {
const doc = await db.collection('users').doc(uid).collection('info').doc(uid).get();
if (doc.exists) {
console.log(doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No user info was found for the authenticated user");
}
} catch(err) {
console.log(err);
}
};
useEffect(() => {
auth.onAuthStateChanged((authUser) => {
if (authUser) {
//user is logged in
getUserData(authUser.uid); //retrieve the user's profile data
} else {
//user is logged out
auth.signOut();
}
});
}, []);
My security rules shouldn't be the problem because it works for my web react app with the same logic and user, and the get request is only sent when there is a uid because the user is authenticated. I've printed out the uid after onAuthStateChanged and it is the correct uid.
//Security Rules in Firestore
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function signedInAndSameUser(uid) {
return request.auth != null && request.auth.uid == uid;
}
match /users/{uid} {
allow read: if request.auth != null;
match /private/{privateId} {
allow read: if signedInAndSameUser(privateId);
}
}
I've seen similar posts that recommended to downgrade to firebase#4.6.2 but I also ran into issues and couldn't get it to work. I'm wondering if firebase still hasn't fixed this issue even after version 8 (In react native app (through Expo) using firestore permissions - request.auth is always null)
This is my current firebase and expo version in my package.json:
//package.json
"expo": "~41.0.1",
"firebase": "8.2.3",
Thank you so much if you can help, I've been stuck on this issue for many hours and can't seem to understand why this works in my react.js web app, but the same logic, user, and security rules won't work in my react native Expo iOS app.

React native expo implementing Apple App Tracking Transparency (ATT) for iOS 14.5

What is the best way of implementing the Apple App Transparency Tracker (ATT) feature on react native expo? My app keeps getting rejected by apple even after I add:
app.json file :
"infoPlist": {
"NSUserTrackingUsageDescription": "App requires permission...."
}
On iOS 14 Apple introduced the App Tracking Transparency permission to access IDFA.
You need to prompt the user whether it allows your app to use libraries that track them or not, adding it on infoPlist just allows you to use this API within your application.
Expo doesn't have this feature yet, but some libraries you can use to prompt the permission
Example: https://docs.expo.io/versions/v41.0.0/sdk/facebook/#facebookgetpermissionsasync
You can use other libraries , such as https://github.com/mrousavy/react-native-tracking-transparency
where you can request the App tracking like
import { getTrackingStatus } from 'react-native-tracking-transparency';
const trackingStatus = await getTrackingStatus();
if (trackingStatus === 'authorized' || trackingStatus === 'unavailable') {
// enable tracking features
}
import { requestTrackingPermission } from 'react-native-tracking-transparency';
const trackingStatus = await requestTrackingPermission();
if (trackingStatus === 'authorized' || trackingStatus === 'unavailable') {
// enable tracking features
}
This might need an update in a near future, as expo releases a new SDK version with a solution for that.
EDIT
From Expo 44+
Expo now have a library for TrackTransparency: (https://docs.expo.dev/versions/latest/sdk/tracking-transparency/)
expo install expo-tracking-transparency
For bare applications: https://github.com/expo/expo/tree/main/packages/expo-tracking-transparency#installation-in-bare-react-native-projects
You can add it as a plugin at your app.json
{
"expo": {
"plugins": [
[
"expo-tracking-transparency",
{
"userTrackingPermission": "This identifier will be used to deliver personalized ads to you."
}
]
]
}
}
And now you can use like this:
import React, { useEffect } from 'react';
import { Text, StyleSheet, View } from 'react-native';
import { requestTrackingPermissionsAsync } from 'expo-tracking-transparency';
export default function App() {
useEffect(() => {
(async () => {
const { status } = await requestTrackingPermissionsAsync();
if (status === 'granted') {
console.log('Yay! I have user permission to track data');
}
})();
}, []);
return (
<View style={styles.container}>
<Text>Tracking Transparency Module Example</Text>
</View>
);
}
You need to request Tracking permissions first (I used react-native-permissions):
import { request, RESULTS, PERMISSIONS } from 'react-native-permissions'
export const requestPermissionTransparency = async () => {
return await request(PERMISSIONS.IOS.APP_TRACKING_TRANSPARENCY)
}
useEffect(() => {
;(async () => {
const result = await requestPermissionTransparency()
if (result === RESULTS.GRANTED) {
//You need to enable analytics (fb,google,etc...)
await firebase.analytics().setAnalyticsCollectionEnabled(true)
console.log('Firebase Analytics: ENABLED')
}
})()
}, [])
Remember to add this file in the root project:
// <project-root>/firebase.json
{
"react-native": {
"analytics_auto_collection_enabled": false
}
}
References: https://rnfirebase.io/analytics/usage
The solution I ended up using from expo was using the Facebook.getPermissionsAsync()
https://expo.canny.io/feature-requests/p/expo-permissions-add-support-to-apptrackingtransparency-permission-on-ios
Expo 41+
TrackingTransparency:
https://docs.expo.io/versions/latest/sdk/tracking-transparency/
import { requestTrackingPermissionsAsync } from 'expo-tracking-transparency';
const { status } = await requestTrackingPermissionsAsync();
if (status === 'granted') // do something
Expo 40 and below
Admob: https://docs.expo.io/versions/latest/sdk/admob/
import { requestPermissionsAsync } from 'expo-ads-admob'
const { status } = await requestPermissionsAsync()
if (status === 'granted') // do something

ionic native storage does not work on iOS

I use Ionic 3 on one of my projects with an authentication system. I use native storage when the user wants to connect. It works on Android but on iOS, it redirects me to the login screen even using platform.ready (). I saw that several people were a similar problem but no answer, so I wanted to know if someone was facing the same problem and if he found a solution. Here is my code:
this.plt.ready().then(() => {
this.nativeStorage.setItem('userStorage', { stayConnected: (typeof this.stayConnected == "undefined" || this.stayConnected == false ? '' : 'stayConnected'), userId: (result as any).id, userLogin: (result as any).login })
.then(
() => {
this.loader.dismiss();
this.navCtrl.setRoot(HomePage);
},
error => {
this.loader.dismiss();
this.presentToast(this.languageLogin.error, 3000, "bottom");
}
)
},
error => {
this.loader.dismiss();
this.presentToast(this.languageLogin.error, 3000, "bottom");
});
thank you for your answers.
I would put 2 function storeUser() and getUser() into the same provider UserService like belows
Then add UserService to the constructor of any pages required.
It works for both IOS, Android and web
import {Storage} from '#ionic/storage';
import {Observable} from 'rxjs/Observable';
#Injectable()
export class UserService {
constructor(private storage: Storage){}
public storeUser(userData): void {
this.storage.set('userData', userData);
}
public getUser(): Observable<any>
return Observable.fromPromise(this.storage.get('userData').then((val) => {
return !!val;
}));
}
Yes, I have faced issues while using ionic native storage plugins. So I turned to javascript Window localStorage Property and it's working completely fine.
Syntax for SAVING data to localStorage:
localStorage.setItem("key", "success");
Syntax for READING data from localStorage:
var lastname = localStorage.getItem("key");
Syntax for REMOVING data from localStorage:
localStorage.removeItem("key");
and now you can write your code with this property, like this -
if (lastname == "success"){
this.navCtrl.setRoot(HomePage);
} else{
alert("Not matched")
}
You are inside a platform.ready(), which is good. The storage package also has a .ready() that you may want to leverage, which specifically checks if storage itself is ready. If this runs at startup there is a decent chance storage is initializing.
Also, this starts to get into some crazy promise chaining messiness. I'd suggest diving into async/await. Something like the (untested) code below.
try{
await this.plt.ready();
await this.nativeStorage.ready();
let stayConnectedValue = (this.stayConnected) ? 'stayConnected' : '';
await this.nativeStorage.setItem('userStorage', { stayConnected: stayConnectedValue , userId: (result as any).id, userLogin: (result as any).login });
this.navCtrl.setRoot(HomePage);
}
catch(err){
this.presentToast(this.languageLogin.error, 3000, "bottom");
}
finally{
this.loader.dismiss();
}

Unable to dispatch redux action after receiving firebase DB data

I'm using react native app to get data from firebase database. The idea is to dispatch an action to initialise store.appState as soon as data is received from FirebaseDB. The issue is that dispatch is not fired when data is received. What am I missing?
I'm using redux-thunk middleware.
// database.js
import * as firebase from 'firebase'
const config = {
apiKey: "...",
authDomain: "...",
databaseURL: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "..."
}
const Database = firebase
.initializeApp(config)
.database()
.ref()
export default Database
// actions.js
import database from 'database'
const setInitDataFromFirebase = data => {
return ({
type: SET_DATA_INIT,
payload: {
data
}
})
}
export const loadAction = () => {
return dispatch => {
Database.once('value', snapshot=>{
console.log(snapshot.val()) // i get the log with correct data
dispatch(setInitDataFromFirebase(snapshot.val())) // this is not dispatched
})
}
}
// container.js
const View = (appState, loadData) => {
if (!appState) loadData() // appState is null initially
}
const matStateToProps (...) // passes appState
const mapDispatchToProps (...) // passes loadData
export default connect(mapStateToProps, mapDispatchToProps)(View)
}
I got this figured out. Turns out I made a very basic typo when I was importing redux-thunk. I should have done
import ReduxThunk from 'redux-thunk'
const store = createStore(
RootReducer,
applyMiddleware(ReduxThunk, logger)
)
What I had wrongly done was
import thunk from 'redux-thunk'
const store = createStore(
RootReducer,
applyMiddleware(thunk, logger)
)

Resources