我仅在模拟器可用时才尝试使用它。如果模拟器不可用(即:如果我没有运行
connectAuthEmulator
),firebase emulators:start
不会失败。当我稍后尝试提出请求时,它失败了。
为此,我使用
http://localhost:9099
获取模拟器 URL (fetch
)。如果请求成功,我会致电 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 };
知道为什么会发生这种情况以及如何解决它吗?
首先,尝试使用
http://127.0.0.1:9099
而不是 http://localhost:9099
(不要忘记将其设置为 host
中的模拟器 firebase.json
)。
除了使用解决方案 #1 之外,尝试在与 Firebase 相关的所有内容都已初始化后渲染您的应用程序。您可以通过在模拟器连接状态上创建侦听器来实现此目的。
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 };
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 />);
}
将
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
、此处(永久链接)。这是因为这是第一次对 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)
,它是可见的,但我当时并没有意识到这一点。
出于某种原因,使用本地主机 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
我的案例中的问题是 NextJS 服务器组件。
尽管 firebase 配置文件有一个
'use client'
指令,但从中导出的 auth
、db
和其他值都在服务器组件中使用。在模拟器连接周围添加一个 try
块帮助我弄清楚了。
解决方案是在连接模拟器之前检查代码是否在浏览器环境中执行。
尝试 http://127.0.0.1:9099 而不是 http://localhost:9099 (不要忘记在 firebase.json 中将其设置为模拟器主机)。