只有当模拟器可用时,我才会尝试使用它。如果模拟器不可用(即:如果我没有运行firebase emulators:start
(,connectAuthEmulator
不会失败。稍后当我尝试提出请求时,它失败了。
为此,我将使用fetch
获取模拟器URL(http://localhost:9099
(。如果请求成功,那么我调用connectAuthEmulator
。否则,我什么都不做(使用配置的云服务(。
我在fetch
工作的地方遇到了一个问题,但connectAuthEmulator
抛出了一个错误:auth/emulator-config-failed
。出于某种奇怪的原因,这种情况似乎只有在我登录时才会发生,并且我在大约15秒内没有提出任何请求。但是,如果我发送垃圾邮件请求,则永远不会发生错误。
import { initializeApp } from "firebase/app";
import { getAuth, connectAuthEmulator } from "firebase/auth";
const app = initializeApp({ /** ... */ });
const auth = getAuth(app);
if (NODE_ENV === "development") {
(async () => {
try {
const authEmulatorUrl = "http://localhost:9099";
await fetch(authEmulatorUrl);
connectAuthEmulator(auth, authEmulatorUrl, {
disableWarnings: true,
});
console.info("🎮 Firebase Auth: emulated");
} catch (e) {
console.info("🔥 Firebase Auth: not emulated");
}
})()
}
export { auth };
知道为什么会发生这种情况以及如何解决吗?
解决方案#1
首先,尝试http://127.0.0.1:9099
而不是http://localhost:9099
(不要忘记在firebase.json
中将其设置为模拟器host
(。
解决方案#2
在使用解决方案#1的基础上,在Firebase相关的所有内容都初始化后,尝试渲染您的应用程序。您可以通过在模拟器连接的状态上创建侦听器来实现这一点。
src/configfirebase.ts
import { initializeApp } from "firebase/app";
import { getAuth, connectAuthEmulator } from "firebase/auth";
const app = initializeApp({ /** ... */ });
const auth = getAuth(app);
if (NODE_ENV === "development") {
// Create listener logic
window.emulatorsEvaluated = false;
window.emulatorsEvaluatedListeners = [];
window.onEmulatorsEvaluated = (listener: () => void) => {
if (window.emulatorsEvaluated) {
listener();
} else {
window.emulatorsEvaluatedListeners.push(listener);
}
};
(async () => {
try {
// Use 127.0.0.1 instead of localhost
const authEmulatorUrl = "http://127.0.0.1:9099";
await fetch(authEmulatorUrl);
connectAuthEmulator(auth, authEmulatorUrl, {
disableWarnings: true,
});
console.info("🎮 Firebase Auth: emulated");
} catch (e) {
console.info("🔥 Firebase Auth: not emulated");
}
// Indicate that the emulators have been evaluated
window.emulatorsEvaluated = true;
window.emulatorsEvaluatedListeners.forEach(
(listener: () => void) => {
listener();
}
);
})()
}
export { auth };
src/index.tsx
import React from "react";
import { createRoot } from "react-dom/client";
import "./config/firebase";
const root = createRoot(document.getElementById("root")!);
const Root = () => ({
/** Some JSX */
});
if (NODE_ENV === "development") {
window.onEmulatorsEvaluated(() => {
root.render(<Root />);
});
} else {
root.render(<Root />);
}
解决方案#3
强制将auth._canInitEmulator
(TS为(auth as unknown as any)._canInitEmulator
(的值更改为true
。这可能会产生一些意想不到的副作用(见答案(,因为一些请求可能会在模拟器启动之前进入您的云。这可以通过解决方案#2(应该可以防止请求触发(来缓解
(auth as unknown as any)._canInitEmulator = true;
connectAuthEmulator(auth, authEmulatorUrl, {
disableWarnings: true,
});
完整答案
这里的部分问题是,我在异步函数中执行connectAuthEmulator
,而文档清楚地指出,应该在getAuth
之后立即(同步(调用它。我知道这一点,但我没有看到任何其他选择来解决只有在模拟器可用时才连接到模拟器的问题。
我深入研究了connectAuthEmulator(permalink(的源代码,发现错误出现在这里:
_assert(
authInternal._canInitEmulator,
authInternal,
AuthErrorCode.EMULATOR_CONFIG_FAILED
);
错误发生时authInternal._canInitEmulator
为false。只有一个地方将此属性设置为false,在_performFetchWithErrorHandling
中,此处为(permalink(。这是因为这是它第一次为Auth服务执行API请求。因此,我得出的结论是,在使用auth
发出第一个请求后,Firebase不允许使用模拟器。这可能是为了防止在云上发出一些请求,然后切换到模拟器。因此,我打给connectAuthEmulator
的电话可能是在提出请求后完成的。我不知道在哪里。即使我的应用程序没有加载(解决方案#2(,错误仍然会发生。
我的结论是正确的,因为我后来发现了这个error.ts
文件,上面几乎写着:
[AuthErrorCode.EMULATOR_CONFIG_FAILED]:
'Auth instance has already been used to make a network call. Auth can ' +
'no longer be configured to use the emulator. Try calling ' +
'"connectAuthEmulator()" sooner.',
如果FirebaseError
显示了这条消息,而不仅仅是auth/emulator-config-failed
,那么整个调查本可以更早完成。如果你console.log(error.message)
,它是可见的,但我当时并不知道。
出于某种原因,使用localhost IP立即修复了它,我再也没有出现过错误。我添加了解决方案#2和#3作为之后的另一个度量。
我认为这个问题的根本原因来自React.StrictMode
。此模式将重新渲染两次。它可能也被启动了两次。我很确定,因为当我禁用React.StrictMode
并多次刷新浏览器时。错误不再显示。
当你在项目的app.js中初始化firebase应用程序时,假设你在Vue 上
import { initializeApp } from "firebase/app"
import { getAuth , connectAuthEmulator} from "firebase/auth"
const firebaseConfig = {firebase config}
initializeApp(firebaseConfig)
connectAuthEmulator( getAuth(), 'http://127.0.0.1:9099', { disableWarnings: true })
稍后,在任何组件上,只需导入getAuth((
const auth = getAuth()
//在这里,您不需要调用connectAuthEmulator
tryhttp://127.0.0.1:9099而不是http://localhost:9099(别忘了在firebase.json中将其设置为模拟器主机(.