Firebase Firestore db.collection is not a function - javascript

I am developing a shift scheduler and I use firebase authentication and firestore. My idea is that when a user signs up it creates a document in collection "workers" and sets the doc id to the user's email. When the user adds a shift I want to add the shift info into a sub-collection "shifts" inside that user's document where all the shifts will be stored. I have read and seen many guides but I can't get the syntax/logic right, and due to firebase changes of syntax I am including most of the settings I use.
firebase.js:
import { getAuth } from "firebase/auth";
import "firebase/firestore";
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore"
require('firebase/auth');
const firebaseConfig = {
...
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth(app);
export default app
SignUp.js:
import { db } from "../firebase";
import { collection, addDoc, setDoc, doc } from "firebase/firestore";
import { useAuth } from '../contexts/AuthContext';
const { signup } = useAuth();
const handleSubmit = async (event) => {
event.preventDefault();
setError("");
try{
const data = new FormData(event.currentTarget);
await signup ( data.get('email'), data.get('password'));
const docRef = await setDoc(doc(db, "workers", data.get('email')), {
firstName: data.get('firstName'),
lastName: data.get('lastName'),
email: data.get('email'),
numberOfShifts: 0
});
}
catch(e){
console.error("Error adding document: ", e);
setError("Failed to create an account");
};
The sign up works nicely and the document id is the email. The error is when I try to add shift to that document (the collection shifts is not created at this stage)
Datasheed.js: (where the user inputs their shifts)
import { auth } from "../firebase"
import { db } from "../firebase";
const commitChanges = async ({ added, changed, deleted }) => {
if (added) {
try {
db.collection("workers")
.doc(auth.currentUser.email)
.collection("shifts")
.add(added);
} catch (e) {
console.error("Error adding document: ", e);
}
}
For now I am only trying to add, and the caught exception I am getting is:
Error adding document: TypeError: firebase__WEBPACK_IMPORTED_MODULE_5_.db.collection is not a function.
From what I have read the problem is that I use firebase modular and it doesn't have db.collection anymore and it uses collection refs. Do I need the collection ref for the sub-collection as well? What changes do I need to do in order to implement this?

You are using Modular SDK and also modular syntax in signup.js. You should use the same syntax everywhere else. Try refactoring like this:
const commitChanges = async ({ added, changed, deleted }) => {
if (added) {
try {
await addDoc(collection(db, "workers", auth.currentUser.email, "shifts"), added)
} catch (e) {
console.error("Error adding document: ", e);
}
}
}
You can learn more about the new syntax in the documentation.

Related

Firebase Two-Factor Authentication with Typescript: Error (auth/argument-error)

I have followed the code from the official docs. When you scroll down to the full code, it has two things that make problems. First of all, this seems weird:
const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
const auth = getAuth();
They define auth after using it for recaptchaVerifier. But that seems like a typo, so I just switched these two lines.
But I cannot resolve the second issue. Their code is in JavaScript, my code is in TypeScript. They use undefined as an argument in the definition of recaptchaVerifier:
const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undefined, auth);
The second argument of the constructor is undefined. Since TypeScript does not allow that, I tried many things, for example these:
const undef: any = undefined; const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', undef, auth);
const recaptchaVerifier = new RecaptchaVerifier('recaptcha-container-id', { size: 'invisible' }, auth);
But it ALWAYS gives this error in the console:
ERROR FirebaseError: Firebase: Error (auth/argument-error).
at createErrorInternal (index-0bb4da3b.js:474:41)
at _assert (index-0bb4da3b.js:480:15)
at new RecaptchaVerifier (index-0bb4da3b.js:7369:9)
I could not find anything that helped me fix this error in the internet.
Here is my full code:
LogIn(email: string, password: string) {
const auth = getAuth();
const undef: any = undefined;
const recaptchaVerifier = new RecaptchaVerifier(
'recaptcha-container-id',
undef,
auth
);
/* It never reaches this code below here since new RecaptchaVerifier() always throws an error */
return signInWithEmailAndPassword(auth, email, password)
.then((result) => {
this.afAuth.authState.subscribe((user) => {
if (user) {
this.router.navigate(['home']);
}
});
})
.catch((error) => {
if (error.code == 'auth/multi-factor-auth-required') {
// The user is a multi-factor user. Second factor challenge is required.
const auth = getAuth();
let resolver = getMultiFactorResolver(auth, error);
const phoneInfoOptions = {
multiFactorHint: resolver.hints[0],
session: resolver.session
};
// Send SMS verification code.
const phoneAuthProvider = new PhoneAuthProvider(auth);
phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
.then((verificationId) => {
// verificationId will be needed for sign-in completion.
// Ask user for the SMS verification code via prompt (yeah, very bad UI)
const verificationCode = prompt("Enter the verification code we sent to your number");
if (verificationCode !== null) {
const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
// Complete sign-in.
return resolver.resolveSignIn(multiFactorAssertion);
} else {
this.toast.error("Entered wrong code");
return null;
}
})
.then((userCredential) => {
// User successfully signed in with the second factor phone number.
this.toast.success("Code is correct. Logged in");
this.afAuth.authState.subscribe((user) => {
if (user) {
this.router.navigate(['home']);
}
});
})
.catch((error) => {
console.log(error);
// failed
this.toast.error(error.message);
});
} else if (error.code == 'auth/wrong-password') {
this.toast.error(error.message);
}
});
}
I am using Angular and angularfire. The code above is not called directly from a component, but from a service. That service though is called from my LoginComponent.
Edit. My imports are:
import { Injectable, NgZone } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/compat/auth';
import {
AngularFirestore,
} from '#angular/fire/compat/firestore';
import { Router } from '#angular/router';
import { child, get, getDatabase, ref, set } from "firebase/database";
import { HotToastService } from '#ngneat/hot-toast';
import firebase from "firebase/compat/app";
import { getAuth, getMultiFactorResolver, GoogleAuthProvider, PhoneAuthProvider, PhoneMultiFactorGenerator, RecaptchaVerifier, signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth';
As we talked in the comments for this to work you need an empty div with the passed id, like:
<div id="recaptcha-container-id"></div>

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '$store') in Nuxt js and firebase authentication

I am implementing firebase authentication to Nuxt js application and I am so close. The problem is I want to commit a vuext mutation inside firebase's default function onAuthStateChanged(). But when ever I load the page it shows the following error:
"Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '$store')"
Can you guys please help me out with this problem.
Thanks.
import firebase from '#/plugins/firebase'
import {
getAuth,
signInWithEmailAndPassword,
onAuthStateChanged
} from "firebase/auth"
export const state = () => ({
user: null,
authIsReady: false
})
export const mutations = {
updateUser(state, payload) {
state.user = payload
console.log('user is updated', state.user)
},
setAuthIsReady(state, payload) {
state.authIsReady = payload
console.log(state.authIsReady)
}
}
export const actions = {
async signIn(context, {
email,
password
}) {
console.log('sign in action')
const res = await signInWithEmailAndPassword(getAuth(), email, password)
if (res) {
context.commit('updateUser', res.user)
} else {
throw new Error('could not complete sign in')
}
}
}
// this function is causing the problem
const unsub = onAuthStateChanged(getAuth(), (user) => {
this.$store.commit('updateUser', user)
unsub()
})
The firebase.js file that I'm importing "auth" from below, is just all the regular setting up Firebase in Nuxt stuff... and the important lines are:
const auth = getAuth()
export { auth }
Try the code below ... I have mine in a file named "fireauth.js" in the plugins folder (don't forget to import the "fireauth.js" file in your nuxt.config.js)
import {
auth
} from "~/plugins/firebase.js";
export default (context) => {
const {
store
} = context
return new Promise((resolve, reject) => {
auth.onAuthStateChanged((user) => {
if (user) {
return resolve(store.dispatch('onAuthStateChangedAction', user))
}
return resolve()
})
})
}
In your store/index.js file add the following async function in your actions setting:
async onAuthStateChangedAction(vuexContext, authUser) {
if (!authUser) { //in my case I'm just forcing user back to sign in page, only authorized users allowed//redirect from here this.$router.push({
path: '/signin',
})
}else {
//call your commits or do whatever you want to do
vuexContext.commit("setUser", authUser.email);
}
},
The first part of the code ensures that when the auth state changes in Firestore, this change is communicated to the action that you just created in the store. The second part of the code, the async function in the store accomplishes whatever you want it to do within the store.

Reducing Firebase SDK bundle size?

I've used the new tree-shakeable version of Firebase SDK, which still seems to produce quite a large chunk, considering what I use it for.
I only use the Firestore part, and have actually moved all Firestore operations to the backend, the only thing I'm doing on the front end is initiating the Firebase app instance and starting a snapshot listener (due to the real-time ability), yet the bundle produced is still 275kb large.
import { initializeApp } from 'firebase/app'
import { getFirestore, collection, doc, onSnapshot, query, where, orderBy } from 'firebase/firestore'
const documents = ref(null)
const firebaseApp = initializeApp(firebaseConfig)
const db = getFirestore(firebaseApp)
const ref = query(collection(db, 'documents'), where(`something`, '==', 'something'), orderBy('createdAt', 'desc'))
const unsub = onSnapshot(ref, (snap) => {
let results = []
snap.docs.forEach(el => {
el.data().createdAt && results.push({ ...el.data(), id: el.id })
})
documents.value = results
}, err => {
console.log(err.message)
documents.value = null
})
Is there a way to reduce that size slightly, without sacrificing the real-time ability?

Adding documents to firestore using javascript

I'm trying to create a function to add documents to firestore. At the moment this function could be called two different ways. 1) Adding a user to a collection called 'Users'. 2) adding a site to a collection called 'Sites'.
This function should take the following parameters:
The name of the collection (required)
The users uid (make this optional)
An object with the data for a site (make this optional)
I'm also trying to utilize JS modules to keep my code better organised. At the moment my folder looks something like this:
myApp
assets
img
dist
index.html
index.js
modules
auth.js
firestore.js
Inside auth.js I have a signUp() inside witch I want to call a function called addToFirestore() (this comes from firestore.js). My code looks something like this:
firestore.js
import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.6.4/firebase-
app.js';
import { getFirestore, setDoc, doc } from
'https://www.gstatic.com/firebasejs/9.6.4/firebase-firestore.js';
const firebaseConfig = {
...
};
// Initialize Firebase
initializeApp(firebaseConfig);
const db = getFirestore;
function addToFirestore(collName, user = 0, data = 0) {
// check if adding user
if (user != 0 && data == 0){
//adding user to firestore
try {
setDoc(doc(db, collName, user.uid), {
email: user.email,
});
} catch (e) {
console.error('Error adding document: ', e);
}
// check if adding site
} else if (data != 0 && user == 0) {
setDoc(doc(db, collName), data);
}
export { addToFirestore};
Inside auth.js calling function like this:
// * Auth
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword } from
'https://www.gstatic.com/firebasejs/9.6.4/firebase-auth.js';
import {addToFirestore} from '/modules/firestore.js';
function signUp(email, password) {
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
addToFirestore('Users', user);
})
.then(() => {
openApp();
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
alertAuth.classList.remove('d-none');
alertAuth.classList.add('show');
alertAuth.innerHTML = `<strong>Error: </strong> ${errorCode}`;
});
}
And inside index.js calling signUp():
btnAuthSignUp.addEventListener('click', function () {
event.preventDefault();
let email = inpAuthEmail.value;
let password = inpAuthPassword.value;
signUp(email, password);
});
And it is giving me an error like this:
firestore.js:31 Error adding document: FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
I tried doing everything in one file and still got the same problem. Any help would be greatly appreciated.
As stated on the error you encountered:
Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore.
Firestore expects a collection reference to pass the data to. You didn't specify your collection reference. You should put a collection reference by using given code below:
const collectionRef = doc(db, 'collection_name', user.uid);
db here is not a collection reference. Its just an instance of Firestore:
const db = getFirestore;
Then use it as code below:
setDoc(collectionRef, {
email: user.email,
});
You could also check Add data to Cloud Firestore for more information.

Recaptcha is working, Email is Verified, 2FA turned on in console... yet auth/internal-error when running .verifyPhoneNumber()

I have checked other StackOverflow posts and haven't seen anything that addresses the issue. I am trying to set up multi-factor auth for my app. As far as I've understood the basic steps are:
Enable 2FA in firebase console & Google Cloud Console ✔️
Set up a reCaptcha ✔️
Get the session ✔️
And send a verification message with phoneAuthProvider.verifyPhoneNumber ❌
I'm not sure why as all I am getting is FirebaseError: Firebase: Error (auth/internal-error)
Imports
import 'firebase/auth';
import * as firebase2 from 'firebase/auth';
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import firebaseApp from '../../../../src/config';
import { getAuth } from 'firebase/auth';
Here is the recaptcha:
useEffect(() => {
try {
const auth22 = getAuth();
// Recaptcha only ran once, stored in state
const recaptchaVerifier = new firebase2.RecaptchaVerifier(
'multifactor-form',
{
size: 'invisible',
callback: (response) => {
console.log('successfully created the captcha');
console.log(response);
},
},
auth22
);
console.log(recaptchaVerifier);
setCaptchaVerifier(recaptchaVerifier);
} catch (e) {
console.log(e);
}
}, []);
And here's the function I run when I click send SMS:
const sendSMS = async function (phoneNumber: any) {
console.log(phoneNumber);
console.log(typeof phoneNumber);
try {
let verificationId: any;
const auth = firebaseApp.auth();
const user = auth.currentUser;
const newNumber: string = `+1${phoneNumber}`;
const session = await user.multiFactor.getSession();
const phoneOpts = {
newNumber,
session,
};
const phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
verificationId = await phoneAuthProvider.verifyPhoneNumber(phoneOpts, recaptchaVerfifier);
//Nothing runs after the line above this one
alert('sms text sent!');
} catch (e) {
console.log(e);
}
};
Can anyone see anything wrong with what I'm doing?
If needed Here are the tutorials, and guides, I've been following along with:
https://fireship.io/lessons/two-factor-auth-firebase/#identity-platform
https://cloud.google.com/identity-platform/docs/web/mfa?_ga=2.210928085.-1381314988.1638978774
I had the same error. Some info in docs is incorrect https://cloud.google.com/identity-platform/docs/web/mfa#web-version-8_21
Incorrect !!!
// Specify the phone number and pass the MFA session.
var phoneInfoOptions = {
phoneNumber: phoneNumber,
session: resolver.session
};
Correct version is in the complete example code at the bottom of docs:
var phoneInfoOptions = {
multiFactorHint: resolver.hints[selectedIndex],
session: resolver.session
};

Categories