react native with firestore get count online users - firebase

I build app in react native with firebase/firestore.
I'm looking a way to check the count of users online in app, so I found a way to do with this tutorial
var userRef = new Firebase('https://<demo>.firebaseio.com/presence/' + userid);
userRef.on('value', function(snapshot) {
if (snapshot.val() === true) {
// User is online, update UI.
} else {
// User logged off at snapshot.val() - seconds since epoch.
}
});
I'm looking a way to do with firestore and react native. is there any implementation i can see how do that?
I found this way to do with firestore
import { Platform } from 'react-native';
import firebase from 'react-native-firebase';
function rtdb_and_local_fs_presence() {
// [START rtdb_and_local_fs_presence]
// [START_EXCLUDE]
var uid = firebase.auth().currentUser.uid;
console.log('uid',uid)
var userStatusDatabaseRef = firebase.database().ref('status/' + uid);
var isOfflineForDatabase = {
state: 'offline',
last_changed: firebase.database.ServerValue.TIMESTAMP,
};
var isOnlineForDatabase = {
state: 'online',
last_changed: firebase.database.ServerValue.TIMESTAMP,
};
// [END_EXCLUDE]
var userStatusFirestoreRef = firebase.firestore().doc('status/' + uid);
// Firestore uses a different server timestamp value, so we'll
// create two more constants for Firestore state.
var isOfflineForFirestore = {
state: 'offline',
last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};
var isOnlineForFirestore = {
state: 'online',
last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};
firebase.database().ref('.info/connected').on('value', function(snapshot) {
if (snapshot.val() == false) {
// Instead of simply returning, we'll also set Firestore's state
// to 'offline'. This ensures that our Firestore cache is aware
// of the switch to 'offline.'
userStatusFirestoreRef.set(isOfflineForFirestore);
return;
};
userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
userStatusDatabaseRef.set(isOnlineForDatabase);
// We'll also add Firestore set here for when we come online.
userStatusFirestoreRef.set(isOnlineForFirestore);
});
});
// [END rtdb_and_local_fs_presence]
}
function fs_listen() {
var uid = firebase.auth().currentUser.uid;
var userStatusFirestoreRef = firebase.firestore().doc('status/' + uid);
// [START fs_onsnapshot]
userStatusFirestoreRef.onSnapshot(function(doc) {
var isOnline = doc.data().state == 'online';
// ... use isOnline
});
}
firebase.auth().signInAnonymouslyAndRetrieveData().then((user) => {
rtdb_and_local_fs_presence();
fs_listen();
});
it really update the status collection with the right uid when I'm online, but when I disconnect from app, it not update to offline. how can I do that?

It will be working when user closes the app completly.
onStatusOffline(user){
firebase.database().ref(`users/${user.uid}`)
.onDisconnect()
.update({
online: false,
});
}

Related

Stripe JS subscription rerouting

This is my first time using Stripe in a project and I want to set up a subsciption system just to see if its possible. When I am trying to follow a tutorial for how to set it up, it always returns an error. Currently, I am at the part where I am trying to redirect the current user to the purchase page where they can sign up for the subscription, but I cannot seem to figure out a few key details. I am sharing the code for the checout session that I created.
import getStripe from "./initializeStripe";
import { db } from "../configs/firebase-config";
import { addDoc, collection, doc, onSnapshot } from "firebase/firestore";
export async function createCheckoutSession(uid) {
const location = "users/" + uid;
const docRef = collection(db, location, "checkout_sessions");
const checkoutSessionRef = await addDoc(docRef, {
price: "NEXT_PUBLIC_PRICE_KEY",
success_url: window.location.origin,
cancel_url: window.location.origin,
});
console.log();
onSnapshot(checkoutSessionRef, async (doc) => {
const { sessionId } = doc.data();
console.log(sessionId);
if (sessionId) {
const stripe = await getStripe();
stripe.redirectToCheckout({ sessionId });
}
}, [doc]);
}
Please let me know if you have any questions and I would be happy to provide with more code. I am working in NextJs 12 and Firebase Version 9
Edit: Let me add the initalizeStripe function too for more context.
import { Stripe, loadStripe } from "#stripe/stripe-js";
export const initializeStripe = async ({lineItems}) => {
let stripePromise = Stripe | null;
const getStripe = () => {
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE);
return stripePromise;
};
// const stripe = await getStripe();
await stripe.redirectToCheckout({
mode: 'payment',
lineItems,
successUrl: `${window.location.origin}?session_id={CHECKOUT_SESSION_ID}`,
cancelUrl: window.location.origin,
})
return getStripe();
};

Batched Write/Transaction in Cloud Function keeps failing

I'm trying to make changes to several documents in a cloud function once I receive a callback. My code was working when I only had to update one document, but now I need to update several documents atomically in the same function.
I need to read a certain document and then update other documents based on the information held in an array in the original document. I tried to do this using forEach but I get this error in the console whether I'm using a transaction or a batched write:
Error: Cannot modify a WriteBatch that has been committed.
at WriteBatch.verifyNotCommitted (/workspace/node_modules/#google-cloud/firestore/build/src/write-batch.js:126:19)
at WriteBatch.update (/workspace/node_modules/#google-cloud/firestore/build/src/write-batch.js:315:14)
at loyaltyIds.forEach (/workspace/index.js:323:31)
at process._tickCallback (internal/process/next_tick.js:68:7)
Error: Process exited with code 16
at process.on.code (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:92:22)
at process.emit (events.js:198:13)
at process.EventEmitter.emit (domain.js:448:20)
at process.exit (internal/process/per_thread.js:168:15)
at sendCrashResponse (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/logger.js:44:9)
at process.on.err (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:88:44)
at process.emit (events.js:198:13)
at process.EventEmitter.emit (domain.js:448:20)
at emitPromiseRejectionWarnings (internal/process/promises.js:140:18)
at process._tickCallback (internal/process/next_tick.js:69:34)
And what I end up with is the document outside the for loop is updated but the documents inside the for loop are not - which defeats the purpose of an atomic operation.
It also takes a long time to complete the write operation to Firestore. Where am I going wrong?
Below is what I've tried:
Using batched write:
const txDoc = await txRef.get();
if (txDoc.exists) {
console.log('Transaction Document Found');
const userId = txDoc.data().userId;
const loyaltyIds = txDoc.data().loyaltyIds;
const pointsAwardedMap = txDoc.data().pointsAwarded;
let batch = db.batch();
loyaltyIds.forEach(async lpId => {
// There are 2 elements in the loyaltyIds lis
console.log('Inside for loop');
console.log(lpId);
let cardId = 'u_' + userId + '-l_' + lpId; // 'u_$userId-l_$lpId'
let cardRef = db.collection('users').doc(userId).collection('userLoyaltyCards').doc(cardId);
let lpMap = pointsAwardedMap[lpId];
// Get the user LC doc
let cardDoc = await cardRef.get();
if (cardDoc.exists) {
batch.update(cardRef, {
'pointsBalance': cardDoc.data().pointsBalance + lpMap['points'],
'totalSpend': cardDoc.data().totalSpend + txDoc.data().transactionAmount,
'numberOfPurchases': cardDoc.data().numberOfPurchases + 1,
'pointsEarned': cardDoc.data().pointsEarned + lpMap['points'],
'lastPurchaseDate': admin.database.ServerValue.TIMESTAMP,
});
}
});
// Then we update the tx doc
batch.update(txRef, {
transactionCode: `${receiptNo}`,
transactionType: "purchase",
transactionSuccess: true,
}); // only this gets update
console.log('Firebase Transaction success');
return batch.commit();
} else { return null; }
Using transaction operation:
await db.runTransaction(async t => {
const txDoc = await t.get(txRef);
if (txDoc.exists) {
// userId
// For each lp we update the user loyalty card that goes with it
const userId = txDoc.data().userId;
const loyaltyIds = txDoc.data().loyaltyIds;
const pointsAwardedMap = txDoc.data().pointsAwarded;
// What the pointsAwarded map looks like from the transaction:
// var pointsAwarded = {
// lp1: {
// lpName: 'Jeff',
// lpId: 'lp.lpId',
// points: 'points1',
// cashbackPct: 'lp.cashbackPct',
// vendorId: 'lp.vendorId',
// vendorName: 'lp.vendorName',
// },
// lp2: {
// lpName: 'Susan',
// lpId: 'lp.lpId',
// points: 'points2',
// cashbackPct: 'lp.cashbackPct',
// vendorId: 'lp.vendorId',
// vendorName: 'lp.vendorName',
// },
// };
loyaltyIds.forEach(async (lpId) => {
// We update the user loyalty cards
console.log('Inside for loop');
console.log(lpId);
let cardId = 'u_' + userId + '-l_' + lpId; // 'u_$userId-l_$lpId'
let cardRef = db.collection('users').doc(userId).collection('userLoyaltyCards').doc(cardId);
let lpMap = pointsAwardedMap[lpId];
// Get the user LC doc
let cardDoc = await t.get(cardRef);
// We create the initial loyalty card doc without relying on the cloud function
if (cardDoc.exists) {
// Users LC found, we simply update with this transaction
// `${mpesaReceiptNo}`, this is how to add a var as a field value in firestore
t.update(cardRef, {
'pointsBalance': cardDoc.data().pointsBalance + lpMap['points'],
'totalSpend': cardDoc.data().totalSpend + txDoc.data().transactionAmount,
'numberOfPurchases': cardDoc.data().numberOfPurchases + 1,
'pointsEarned': cardDoc.data().pointsEarned + lpMap['points'],
'lastPurchaseDate': admin.database.ServerValue.TIMESTAMP,
});
}
}); // end of loyalty card update loop
// Then we update the transaction doc
console.log('Transaction Document Found')
t.update(txRef, {
transactionCode: `${mpesaReceiptNo}`,
transactionType: "purchase",
transactionSuccess: true,
});
console.log('Firebase Transaction success');
}
});
UPDATE
I've tried to use a normal for loop but I still get the same errors. I even tried to incorporate the batch.commit statement in the loop so it only executes when the loop completes. Still - same errors.
try {
return txRef.get().then( async txDoc => {
if (txDoc.exists) {
const userId = txDoc.data().userId;
const loyaltyIds = txDoc.data().loyaltyIds;
const pointsAwardedMap = txDoc.data().pointsAwarded;
const batch = db.batch();
// loyaltyIds.forEach(lpId => {
for (let i = 0; i < loyaltyIds.length; i++) {
// We update the user loyalty cards
const lpId = loyaltyIds[i];
console.log('Inside for loop');
console.log(lpId);
const cardId = 'u_' + userId + '-l_' + lpId; // 'u_$userId-l_$lpId'
const cardRef = db.collection('users').doc(userId).collection('userLoyaltyCards').doc(cardId);
const lpMap = pointsAwardedMap[lpId];
// Get the user LC doc
cardRef.get().then(cardDoc => {
// We created the initial loyalty card doc without relying on the cloud function
if (cardDoc.exists) {
console.log('Card found');
// Users LC found, we simply update with this transaction
// `${mpesaReceiptNo}`, this is how to add a var as a field value in firestore
batch.update(cardRef, {
'pointsBalance': cardDoc.data().pointsBalance + lpMap['points'],
'totalSpend': cardDoc.data().totalSpend + txDoc.data().transactionAmount,
'numberOfPurchases': cardDoc.data().numberOfPurchases + 1,
'pointsEarned': cardDoc.data().pointsEarned + lpMap['points'],
'lastPurchaseDate': admin.database.ServerValue.TIMESTAMP,
});
}
});
if (i + 1 == loyaltyIds.length) {
console.log('Loyalty card loop complete, now going to update other things and commit the batch.');
// Update the transaction document
batch.update(txRef, {
transactionCode: `${mpesaReceiptNo}`,
transactionType: "purchase",
transactionSuccess: true,
});
console.log('Committing the batch');
return batch.commit();
}
} // end of for loop
} else {
console.log('Transaction Doc not found, terminating function.');
return null;
}
}).then(function () {
console.log("SUCCESS")
return null;
}
).catch(function (error) {
console.log("UNABLE TO EXECUTE TX BATCH");
console.log(error);
// throw new functions.https.HttpsError('unknown', 'An error occurred when trying to sort the posts.');
return null;
});
I think your problem is related to promises. You must await for the batch.commit(), which was not done in your code. No need to use the await for batch.update(), only for the batch.commit().
Usage of the map with the Promise.all is very important here to ensure you await for all the loop operations to be completed.
I updated your code using awaits, I could not test it since I don't have access to your DB, but I think it should solve your problem with the batch.
try {
const txDoc = await txRef.get();
if (txDoc.exists) {
const userId = txDoc.data().userId;
const loyaltyIds = txDoc.data().loyaltyIds;
const pointsAwardedMap = txDoc.data().pointsAwarded;
const batch = db.batch();
await Promise.all(loyaltyIds.map(async (lpId, i) => {
console.log(lpId);
const cardId = 'u_' + userId + '-l_' + lpId; // 'u_$userId-l_$lpId'
const cardRef = db.collection('users').doc(userId).collection('userLoyaltyCards').doc(cardId);
const lpMap = pointsAwardedMap[lpId];
const cardDoc = await cardRef.get();
if (cardDoc.exists) {
batch.update(cardRef, {
'pointsBalance': cardDoc.data().pointsBalance + lpMap['points'],
'totalSpend': cardDoc.data().totalSpend + txDoc.data().transactionAmount,
'numberOfPurchases': cardDoc.data().numberOfPurchases + 1,
'pointsEarned': cardDoc.data().pointsEarned + lpMap['points'],
'lastPurchaseDate': admin.database.ServerValue.TIMESTAMP,
});
}
if (i + 1 == loyaltyIds.length) {
batch.update(txRef, {
transactionCode: `${mpesaReceiptNo}`,
transactionType: "purchase",
transactionSuccess: true,
});
}
}));
await batch.commit();
return null;
} else {
console.log('Transaction Doc not found, terminating function.');
return null;
}
} catch (error) {
console.log(error);
return null;
}

Firebase listUsers fails to get All users after a certain page

I'm using a pubsub firebase function (cron), and inside this function Im calling firebase auth users, to fill some missing data in a profile collection
Im paginating with the pageToken, the first token passed is undefined then I save it in a config db and read the token to get the next page
The issue is that I have 170K records, and listusers returns an undefined token at the 6th page (6k users) which is frsutrating
here is the code:
functions.pubsub
.schedule('*/30 * * * *')
.onRun(async () => {
const page = firestore.collection('config').doc('pageToken');
const doc = (await page.get()).data();
// Check if last page don't run again
const last = doc?.last;
if (last) return;
// Load page
const pageToken = doc?.pageToken || undefined;
let pageNumber = doc?.pageNumber as number;
return firebaseAdmin
.auth()
.listUsers(1000, pageToken)
.then(async listUsersResult => {
for (const userRecord of listUsersResult.users) {
// Fetch Profile
try {
const profile = await firestore
.collection('profiles')
.doc(userRecord.uid);
// data foramtting here
// compared profile data & fixed data
const payload = JSON.parse(
JSON.stringify({
...profileData,
...{
lastName,
firstName,
language,
...(!userRecord.phoneNumber && {
phone,
}),
},
})
);
// Profile doesn't exist : Create
if (!profileData && payload) {
await profile.create({
...payload,
...{
Migrated: true,
},
});
} else if (profileData && payload) {
const data = compare(profileData, payload);
if (data) {
// Profile exists: Update
await profile.update(data);
if (userRecord.phoneNumber)
await profile.update({
phone: firebaseAdmin.firestore.FieldValue.delete(),
});
}
}
} catch (err) {
functions.logger.error('Some Error', err);
}
}
if (!listUsersResult.pageToken) {
return await firestore
.collection('config')
.doc('pageToken')
.update({
last: true,
});
}
// List next batch of users.
pageNumber++;
return await firestore
.collection('config')
.doc('pageToken')
.update({
pageToken: listUsersResult.pageToken,
pageNumber,
});
});
});
so after in page 6, I have a last:true property added to the firestore however there is 164k data are missing
any idea ?

How can I build a one to one (private) chat application in react native using fire-base as a back-end?

I want to add a chat feature in my application, but the problem is while working with react-native-gifted-chat and firebase as a backend and its secured rules that gives an error of missing _id and user.
I tried using the firebase database and without using secured rules but the issue is it seems to be like a group chat rather than one to one (private) chat.
async UNSAFE_componentWillMount() {
const name = auth().currentUser.displayName;
const friendName = this.state.friendName;
this.setState({ name: name });
const ref = await database().ref(`chatmessages/`);
// Fetch the data snapshot
const snapshot = await ref.once('value');
console.log(snapshot, "Snapshot")
console.log(ref, "database");
}
componentDidMount() {
this.on(message => {
console.log(this.state.messages, 'old message')
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, message),
})
)
});
}
componentWillUnmount() {
this.off();
}
get uid() {
return (auth().currentUser || {}).uid;
}
get ref() {
return database().ref(`chatmessages/`)
// .set();
}
parse = async snapshot => {
const data = snapshot.val();
const userID = auth().currentUser.uid;
const friendID = this.state.friendID;
const validate = data.friend === friendID && data.user._id === userID ||
data.user._id === friendID && data.friend === userID;
console.log(data.user, data.user._id, data.user.name, "MEssage Data")
if (validate) {
const { timestamp: numberStamp, text, user, friend } = await data;
const { key: _id } = snapshot;
console.log(_id, user,'Firebase Message Id')
const timestamp = new Date(numberStamp);
const message = {
_id,
timestamp,
text,
user: data.user,
friend
};
console.log(message, "Gifted")
return message;
}
};
on = callback =>
this.ref
.limitToLast(20)
.on('child_added', snapshot => callback(this.parse(snapshot)));
get timestamp() {
return firebase.database.ServerValue.TIMESTAMP;
}
// send the message to the Backend
send = messages => {
for (let i = 0; i < messages.length; i++) {
const { text, user } = messages[i];
const message = {
text,
user,
friend: this.state.friendID,
timestamp: this.timestamp,
};
this.append(message);
}
};
append = message => this.ref.push(message);
// close the connection to the Backend
off() {
this.ref.off();
}
get user() {
return {
name: auth().currentUser.displayName,
_id: this.uid
};
}
render() {
<GiftedChat
text={this.state.text}
onInputTextChanged={text => this.setState({ text: text })}
messages={this.state.messages}
isAnimated
onSend={messages => this.send(messages)}
user={this.user}
renderActions={this.renderCustomActions}
/>
);
}
}
I want a one to one chat created with firebase and react-native-gifted-chat
It's essentially the same except you limit it to just two people. This article explains more on how to handle one to one chat https://medium.com/#edisondevadoss/react-native-chat-using-firebase-d4c0ef1ab0b5

How to persist a Firebase login?

I'm doing an app with Ionic Framework and Firebase. I made a custom login to get data inside Firebase, but every single time the app is restarted I need to login again. How can I persist the login? The user should login the first time, and not need to do it again.
Here is my service:
(function() {
'use strict';
angular
.module('mytodo.login')
.factory('LoginService', LoginService);
LoginService.$inject = ['$state', '$ionicLoading', '$firebaseAuth', '$firebaseObject','$rootScope', '$timeout', 'fb', '$q'];
function LoginService($state, $ionicLoading, $firebaseAuth, $firebaseObject, $rootScope, $timeout, fb, $q){
var service = {
CustomLogin: CustomLogin,
GetCurrentUser: GetCurrentUser,
RegisterUser: RegisterUser,
};
return service;
function CustomLogin(email, password) {
if(email ==null | password == null){
console.log('Preencha todos os campos!');
return;
}
$ionicLoading.show({
showBackdrop: false,
template: '<p>Carregando...</p><ion-spinner icon="android" style="stroke: #1d9c9e;fill:#1d9c9e;"></ion-spinner>'
});
$firebaseAuth().$signInWithEmailAndPassword(email, password).then(function(authData) {
$rootScope.currentUser = GetCurrentUser(authData.uid);
$timeout(function() {
$ionicLoading.hide();
$state.go('tab.todo', {});
}, 1000);
}).catch(function(error) {
showToast();
$ionicLoading.hide();
console.log(error);
});
}
function showToast(){
ionicToast.show('Usuário ou senha inválido', 'middle', false, 1500);
}
function GetCurrentUser(userId) {
var query = fb.child('/users/' + userId);
var currentUser = $firebaseObject(query)
return currentUser;
}
function SaveUser(authData) {
console.log(authData.uid);
var deffered = $q.defer();
var uid = authData.uid;
var user = {
displayName: authData.displayName,
name: authData.displayName,
photoURL: authData.photoURL,
email: authData.email,
emailVerified: authData.emailVerified,
providerId: authData.providerData[0].providerId
};
var ref = fb.child('/users/' + uid);
ref.once("value")
.then(function(snapshot) {
if (snapshot.exists()) {
console.log('User already exists');
} else {
ref.set(user);
}
deffered.resolve(snapshot);
});
return deffered.promise;
};
function RegisterUser(user) {
var deffered = $q.defer();
$ionicLoading.show();
$firebaseAuth().$createUserWithEmailAndPassword(user.email, user.password).then(function(authData) {
var newUser = {
name: user.name,
email: user.email,
providerId: authData.providerData[0].providerId
};
var userId = authData.uid;
var ref = fb.child('/users/' + userId);
ref.once("value")
.then(function(snapshot) {
if (snapshot.exists()) {
//console.log('User already exists');
} else {
ref.set(newUser).then(function(user){
$rootScope.currentUser = GetCurrentUser(userId);
})
}
deffered.resolve(snapshot);
CustomLogin(user.email, user.password);
});
}).catch(function(error) {
$ionicLoading.hide();
var errorCode = error.code;
console.log(errorCode);
if(errorCode === 'auth/weak-password')
ionicToast.show('Erro, a senha precisa ter no mínimo 6 digitos.', 'middle', false, 3000);
if(errorCode === 'auth/email-already-in-use')
ionicToast.show('Erro, o email: ' + user.email + ' já existe em nossa base de dados.', 'middle', false, 3000);
})
return deffered.promise;
};
}
})();
To re-iterate the point of don't persist the login yourself, firebase does this for you. I am referencing this from typescript FYI.
In the official docs() :
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
Where local is on disk.
Then later in your code all you need to do is subscribe to the onAuthStateChanged observable.
this.firebase.auth.onAuthStateChanged(user => {
if (user){
Do not persist the plain text password yourself!!!! Firebase persists a user with uid, session API keys etc.
Just follow the Firebase docs. Persisting plain text password will result in a bad security audit.
Newer version
Initialize the app like this to keep the user logged in even after the browser is closed and reopened on the same device.
import { initializeApp } from 'firebase/app';
import { getAuth, browserLocalPersistence, setPersistence } from 'firebase/auth'
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
(async () => {
await setPersistence(auth, browserLocalPersistence);
})();
To get the user object you can use React Firebase Hooks:
import { useAuthState } from 'react-firebase-hooks/auth';
const [user, loading, error] = useAuthState(auth);
You shouldn't persist username and password to storage, if you have to then at least store the password as a hash.
Firebase has the following for signing in again:
firebase.auth().onAuthStateChanged(user => {
});
I've figured out how to do this. Maybe it's not the most correct anwser for it, but it worked for me. I used localSotrage to store the username and password. I could store the tolken as well, but I want to create a "remember password" screen.
When I do my first login I do this in my service.
service.js when I store the user data;
localStorage.setItem("uPassword",password);
localStorage.setItem("uEmail",email);
And I add the following if statement in my controller. If i already did the login, I use the e-mail and password to login again. If I dont, I wait to user press the button and call de function in my service.
controller.js if statement:
if(localStorage.getItem("uEmail")!==undefined && localStorage.getItem("uPassword")!==undefined) {
LoginService.CustomLogin(localStorage.getItem("uEmail"),localStorage.getItem("uPassword"))
}

Resources