Я использую комбинацию React Hook, Next.js и Firebase Auth для создания записи пользовательской монеты в Firestore. Это означает, что как только новый пользователь зарегистрируется, для него в Firestore должен быть автоматически создан новый документ с пометкой uid
вместе с полями coins
и createdAt
.
Однако, когда я вхожу в систему в первый раз, я получаю сообщение об ошибке, в котором говорится:
гидратация-ошибка-информация.js: 27 Ошибка в createUserCoinRecord: FirebaseError: Отсутствуют или недостаточны разрешения».
Однако, если я выйду из системы и войду снова, ошибка больше не возникает.
При проверке базы данных Firebase я заметил, что uid
для пользователя не создается после первого входа в систему, а создается только после второго входа.
Вот код React Hook:
export function useCoinRecord() {
const { data: session } = useSession();
const userEmailRef = useRef(null);
const [created, setCreated] = useState(false);
async function createUserCoinRecord(uid) {
await setDoc(doc(db, "users", uid), {
coins: 100000,
createdAt: serverTimestamp(),
});
}
useEffect(() => {
async function createCoinRecordIfNeeded() {
if (session) {
if (userEmailRef.current !== session.user.email) {
userEmailRef.current = session.user.email;
try {
await createUserCoinRecord(session.user.email);
setCreated(true);
} catch (error) {
console.error("Error in createUserCoinRecord:", error);
}
}
} else {
userEmailRef.current = null;
}
}
createCoinRecordIfNeeded();
}, [session]);
return { created };
}
И это мои правила Firestore:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid} {
allow read, write: if request.auth != null && request.auth.uid == uid;
}
match /users/{uid}/{document=**} {
allow read, write: if request.auth != null && request.auth.uid == uid;
}
}
}
Кнопка входа:
import { signIn } from "next-auth/react";
//...
<button onClick = {() => signIn("google")}>
Sign In
</button>
Войдите с помощью следующей аутентификации:
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
export const authOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
};
export default NextAuth(authOptions);
Затем используйте useEffect
для входа в Firebase:
import { useSession } from "next-auth/react";
import { auth, db } from "../firebase/firebase";
//...
const { data: session } = useSession();
const { coinBalance, setCoinBalance } = useCoinBalanceContext();
const [readyToFetchBalance, setReadyToFetchBalance] = useState(false);
useEffect(() => {
if (session) {
signInWithFirebase(session).then((uid) => {
if (uid) {
setReadyToFetchBalance(true);
}
});
}
}, [session]);
const signInWithFirebase = async (session) => {
const response = await fetch("/api/firebase/create-custom-token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ user: session.user }),
});
const customToken = await response.json();
return signInWithCustomToken(auth, customToken.token)
.then((userCredential) => {
return userCredential.user.uid;
})
.catch((error) => {
console.error("Firebase sign-in error:", error);
});
};
const { created } = useCoinRecord();
useEffect(() => {
if (readyToFetchBalance && created && session?.user?.email) {
(async () => {
const balance = await getCoinBalance(session.user.email);
balance && setCoinBalance(balance);
})();
}
}, [session, readyToFetchBalance, created]);
/api/firebase/create-custom-token
import { adminAuth } from "@/firebase/firebaseAdmin";
export default async function handler(req, res) {
if (req.method !== "POST") {
res.status(405).json({ message: "Method not allowed" });
return;
}
const { user } = req.body;
const uid = user.email;
try {
const customToken = await adminAuth.createCustomToken(uid);
res.status(200).json({ token: customToken });
} catch (error) {
console.error("Error creating custom token:", error);
res.status(500).json({ message: "Error creating custom token" });
}
}
firebase.js
import { getApp, getApps, initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: process.env.FIREBASE_APIKEY,
authDomain: process.env.FIREBASE_AUTHDOMAIN,
databaseURL: process.env.FIREBASE_DATABASEURL,
projectId: process.env.PROJECTID,
storageBucket: process.env.FIREBASE_STORAGEBUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGINGSENDERID,
appId: process.env.FIREBASE_APPID,
measurementId: process.env.FIREBASE_MEASUREMENTID,
};
const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
export { db, auth };
firebaseAdmin.js
import admin from "firebase-admin";
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_KEY);
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
}
const adminDb = admin.firestore();
const adminAuth = admin.auth();
export { adminDb, adminAuth };
Моя база данных устроена так:
/users/[email protected]/coins
где [email protected]
это uid
.
Что может быть причиной FirebaseError и как я могу это исправить?
request.auth
автоматически заполняется, когда вы подписываете пользователя с аутентификацией Firebase, вызывая одну из функций signInWith...
, но ни один из кодов, которыми вы поделились, этого не делает.
@FrankvanPuffelen Спасибо, Фрэнк. Я добавил больше файлов. И спасибо за ссылку, Алекс.
@FrankvanPuffelen Я добавил к этому больше контекста: когда я вхожу в систему в первый раз, я сталкиваюсь с ошибкой. Однако, если я выйду из системы и войду снова, ошибка больше не возникает. При проверке базы данных Firebase я заметил, что uid
для пользователя не создается после первого входа в систему, а создается только после второго входа.
Я нашел проблему и написал решение. Спасибо ребята.
Я понял, что существует гонка между двумя крюками, которые работают одновременно.
Проблема в том, что useCoinRecord()
пытается создать новый документ до того, как пользователь будет аутентифицирован. Чтобы исправить это, я удалил useCoinRecord()
и изменил хук useEffect()
, чтобы включить необходимую логику для создания пользовательской записи о монетах, прежде чем устанавливать флаг ReadyToFetchBalance
в значение true.
Обновленный код выглядит так:
useEffect(() => {
if (session) {
signInWithFirebase(session).then(async (uid) => {
if (uid) {
await createUserCoinRecord(uid);
setReadyToFetchBalance(true);
}
});
}
}, [session]);
Это должно решить проблему состояния гонки и гарантировать, что документ будет создан только после аутентификации пользователя.
Может этот ресурс поможет.