firebase-tools
firebase-tools copied to clipboard
Allow setting webSocketPort on the firestore emulator
[REQUIRED] Environment info
firebase-tools: 9.20.0
Platform: Linux
[REQUIRED] Test case
The tools-ui seems to handle config for the firestore websocket https://github.com/firebase/firebase-tools-ui/blob/f1178c0d8fa1f35cc978f5615eccc0ada98804f3/src/store/config/types.ts#L25-L28
The firebase-tools schema doesn't support it: https://github.com/firebase/firebase-tools/blob/master/schema/firebase-config.json
[REQUIRED] Steps to reproduce
- have a
firebase.json
that includes the firestore emulator and ui -
firebase emulators:start
[REQUIRED] Expected behavior
I should be able to configure which port the firestore emulator websocket server uses in firebase.json
.
[REQUIRED] Actual behavior
firestore emulator starts with a randomly assigned websocket port
As a workaround, I think you can directly start the firestore with the --webchannel_port flag
Usage: cloud-firestore-emulator [options]
You can also fork the tool repo directly and add your new config entries.
UPDATE: Seems to set a webchannel port but does not affect the ui tools websocket portt
The --webchannel_port
does not set the port the websocket listens on, it's another service of the emulator (I believe it's a gRPC server?).
To force the websocket port, your best bet for now is to patch the emulator. Fortunately, the Java archive is not obfuscated so JADX is able to recover the source code for the part we're interested in. Here's how you can do it (I'm hardcoding port 8093, you can change it on the line + WebSocketServer websocket = new WebSocketServer(...
).
# Get the latest emulator from https://github.com/firebase/firebase-tools/blob/master/src/emulator/downloadableEmulators.ts#L41
wget https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.13.1.jar
# Extract the class initializing the websocket
mkdir classes
unzip cloud-firestore-emulator-v1.13.1.jar com/google/cloud/datastore/emulator/firestore/CloudFirestore\* -d classes
# Decompile the class to plain java with JADX
mkdir java
jadx -ds java classes/com/google/cloud/datastore/emulator/firestore/*
# Patch it!
cat <<EOF >firestore-emulator-v1.13.1.patch
diff -ur java/com/google/cloud/datastore/emulator/firestore/CloudFirestore.java java-patched/com/google/cloud/datastore/emulator/firestore/CloudFirestore.java
--- java/com/google/cloud/datastore/emulator/firestore/CloudFirestore.java 2021-11-08 16:21:43.000000000 +0100
+++ java-patched/com/google/cloud/datastore/emulator/firestore/CloudFirestore.java 2021-11-08 16:22:49.000000000 +0100
@@ -9,7 +9,7 @@
import com.google.cloud.datastore.emulator.impl.ExportImportUtil;
import com.google.cloud.datastore.emulator.impl.FirestoreEmulatorConfig;
import com.google.cloud.datastore.emulator.impl.events.FunctionsEmulatorEventPublisher;
-import com.google.common.annotations.VisibleForTesting;
+// import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.google.common.net.HostAndPort;
@@ -65,7 +65,7 @@
if (opts.seedFromExport != null) {
config.seedData(ExportImportUtil.fetchEntities(new java.io.File(opts.seedFromExport), ImmutableList.of()));
}
- WebSocketServer websocket = new WebSocketServer(opts.host, findOpenPort(), config.build().timeSource());
+ WebSocketServer websocket = new WebSocketServer(opts.host, 8093, config.build().timeSource());
config.requestsListener(websocket.getRequestsListener());
FirestoreEmulator firestore = FirestoreEmulator.fromConfig(config.build());
GrpcServer server = buildGrpcServer(opts.host, opts.port, firestore);
@@ -88,7 +88,7 @@
webchannel.shutdown();
}
- @VisibleForTesting
+ // @VisibleForTesting
public static GrpcServer buildGrpcServer(String hostname, int port, FirestoreEmulator emulator) throws IOException {
GrpcServer server = new GrpcServer(hostname, port, ImmutableList.of(ServerInterceptors.intercept(FirestoreV1GrpcAdapter.build(emulator), new ServerInterceptor[]{new FirebaseAuthInterceptor(), new FirebaseDatabaseRefInterceptor()}), ServerInterceptors.intercept(FirestoreV1Beta1GrpcAdapter.build(emulator), new ServerInterceptor[]{new FirebaseAuthInterceptor(), new FirebaseDatabaseRefInterceptor()}), ServerInterceptors.intercept(EmulatorAuxiliaryGrpcAdapter.build(emulator), new ServerInterceptor[]{new FirebaseAuthInterceptor(), new FirebaseDatabaseRefInterceptor()})));
ManagedChannel channel = NettyChannelBuilder.forAddress(hostname, port).usePlaintext().maxInboundMessageSize(Integer.MAX_VALUE).build();
@@ -115,8 +115,8 @@
return localPort;
} catch (Throwable th) {
th.addSuppressed(th);
+ throw th;
}
- throw th;
}
/* access modifiers changed from: private */
EOF
cd java
patch -p1 -i ../firestore-emulator-v1.13.1.patch
cd ..
# Compile back the java class
javac -cp ./cloud-firestore-emulator-v1.13.1.jar java/com/google/cloud/datastore/emulator/firestore/CloudFirestore.java
# Put it back in the jar file
cd java
zip ../cloud-firestore-emulator-v1.13.1.jar com/google/cloud/datastore/emulator/firestore/*.class
# Done!
# java -jar cloud-firestore-emulator-v1.13.1.jar
⚠️ If you run your emulator in a custom environment (e.g. Docker), your Java version might differ from the one you've compiled your class on. Make sure to run javac
in the same environment where you'll run the emulator.
To get the firebase CLI to use your custom JAR file, you'll have to host it somewhere then patch downloadableEmulators.ts - which is bundled as downloadableEmulators.js. Here's a small Node.JS script doing it:
const fs = require("fs"),
path = "/usr/local/lib/node_modules/firebase-tools/lib/emulator/downloadableEmulators.js",
r = /(remoteUrl:\s*")[a-z:\/_.-]+\/cloud-firestore-emulator-[v0-9.]+\.jar(",\s*expectedSize: )\d+(,\s*expectedChecksum:\s*")[a-f0-9]+(")/,
originalData = fs.readFileSync(path).toString(),
url = "<URL OF YOUR JAR FILE>",
size = <SIZE OF YOUR JAR FILE IN BYTES>,
checksum = "<MD5 CHECKSUM OF YOUR FILE>",
patchedData = originalData.replace(r, (_,a,b,c,d) => a + url + b + size + c + checksum + d);
assert(patchedData !== originalData);
fs.writeFileSync(path, patchedData);
... I tried to do it with Sed or Perl but we need Node.JS to run the Firebase CLI anyway 😄
Hi, this is Yuchen from the Emulator Suite team. First things first, the --webchannel_port
flag is an implementation detail for the Firestore Emulator. And as both of you observed, WebChannel has nothing to do with WebSocket. We do not recommend setting that flag and it will not solve OP's problem.
For context, the Firestore Emulator serves normal traffic on the main port (configurable via port
in firebase.json
), but also exposes an auxiliary WebSocket server that exposes requests and rules evaluations for the Requests Monitor feature in Emulator UI. For the latter, a random available port is picked to minimize the chance of conflicts. If I understand correctly, the feature request from OP is to make that port configurable. We haven't thought about that yet since the Emulator UI discovers the auxiliary port through an discovery API call to the normal port and there's no need for configuration in most use cases. @dannycoates would you share some background on why you'd like such a feature? This will help us prioritize.
Depending on your exact use cases, there may be other workarounds too. For example, you may get the WebSocket port by using the discovery API at http://$FIRESTORE_EMULATOR_HOST/ws/discovery
:
$ curl http://127.0.0.1:8080/ws/discovery
{"url": "ws://127.0.0.1:55885"}
The Firestore Emulator also prints the port picked on start, as a log line like INFO: Started WebSocket server on ws://127.0.0.1:55885
. When launched from the Firebase CLI, this line ends up in firestore-debug.log
, which can be extracted with a simple grep
command for scripting use cases.
Please let us know if none of these workarounds work for you and we'll see what we can do.
While not the original poster, I am using the emulator in a container hosted in k8s. I am also using port forwarding to expose the web ui. I restart the emulator several times throughout a dev day and this assigns another random port. Constantly changing the exposed port on the k8s deployment and the tunnel is not really tenable with dozens of restarts.
On Mon, Nov 8, 2021 at 2:51 PM Yuchen Shi @.***> wrote:
Hi, this is Yuchen from the Emulator Suite team. First things first, the --webchannel_port flag is an implementation detail for the Firestore Emulator. And as both of you observed, WebChannel has nothing to do with WebSocket. We do not recommend setting that flag and it will not solve OP's problem.
For context, the Firestore Emulator serves normal traffic on the main port (configurable via port in firebase.json), but also exposes an auxiliary WebSocket server that exposes requests and rules evaluations for the Requests Monitor feature in Emulator UI. For the latter, a random available port is picked to minimize the chance of conflicts. If I understand correctly, the feature request from OP is to make that port configurable. We haven't thought about that yet since the Emulator UI discovers the auxiliary port through an discovery API call to the normal port and there's no need for configuration in most use cases. @dannycoates https://github.com/dannycoates would you share some background on why you'd like such a feature? This will help us prioritize.
Depending on your exact use cases, there may be other workarounds too. For example, you may get the WebSocket port by using the discovery API at http://$FIRESTORE_EMULATOR_HOST/ws/discovery:
$ curl http://127.0.0.1:8080/ws/discovery {"url": "ws://127.0.0.1:55885"}
The Firestore Emulator also prints the port picked on start, as a log line like INFO: Started WebSocket server on ws://127.0.0.1:55885. When launched from the Firebase CLI, this line ends up in firestore-debug.log, which can be extracted with a simple grep command for scripting use cases.
Please let us know if none of these workarounds work for you and we'll see what we can do.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/firebase/firebase-tools/issues/3837#issuecomment-963606823, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEKBOMFHDCNC7NCWOZNZ43ULBA65ANCNFSM5GC35HAQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
I'm running the firebase emulator suite in a containerized environment too : I'm replicating my project stack locally with docker-compose, which allows every developer to work on a separate environment. Our app relies on Firestore but we also use other components (a Redis cache, ElasticSearch,...). Docker compose makes it easy for us to expose the services to each component. Of course, for the app to be able to connect to the firebase emulator, we expose ports from the Firebase emulator container.
Container ports cannot be changed dynamically : they are set once for all on container creation. The WebUI tries to connect to the WebSocket port, but cannot since we can't open it beforehand. Being able to set it would allow me to open the port on the container, thus making the host (on which the webui runs) able to connect to it.
With the patch I proposed above, I'm able to open port 8093 on the container running the firebase emulator and the WebUI is able to show the firestore requests history.
@yuchenshi thanks, like cchatfield and TPXP we're running the emulator in a container and exposing the ports so in order to reach the websocket from outside we need a static port number.
Thanks for the use cases and I've tracked this as a feature request. We are unable to promise any timeline for this, but if others also have this request, adding a +1 on this issue can help us prioritize adding this to the roadmap.
(Googler-only internal tracking bug: b/207011409)
@yuchenshi I wanted to reach out to see if this was ever prioritized? I'm guessing not...
+1
Resolving this issue would reduce the tedium of spinning up the emulator suite in any environment requiring the use of static port forwards for whichever reason - containers, remote dev, remote testing, anything non localhost. The fact that all the other ports are configurable sticks out like a sore thumb here.
+1
I'm unable to see Firestore requests in the emulator UI due to this issue as I am also running the emulator inside Docker. Just like all the other emulator ports can be configured for this reason we need the ability to configure this as well. Or instead use the port specified in Firebase.json + 100, or whatever.
Based on @TPXP 's instructions, I ended up building support for specifying a web socket port via environment variables into our docker container: https://hub.docker.com/repository/docker/fixl/firebase-emulator-suite
Here's the commit for those who are interested in replicating this: https://gitlab.com/fixl/docker-firebase-emulator-suite/-/commit/21eec64a0d4141514558d5b6865efeeb05f1d017
+1
+1 for containerized environments to work properly.
Whooop, +10 Thank You's to all involved, will be testing this ASAP.
Thanks for adding support for this natively.
I'm still running into issues when I try to run this inside a docker container.
With the following config:
{
"emulators": {
"ui": {
"enabled": true,
"host": "0.0.0.0",
"port": 4000
},
"hosting": {
"host": "0.0.0.0",
"port": "5000"
},
"functions": {
"host": "0.0.0.0",
"port": "5001"
},
"firestore": {
"host": "0.0.0.0",
"port": "8081",
"websocketPort": "54321"
},
"pubsub": {
"host": "0.0.0.0",
"port": "8085"
},
"database": {
"host": "0.0.0.0",
"port": "9000"
},
"auth": {
"host": "0.0.0.0",
"port": "9099"
},
"storage": {
"host": "0.0.0.0",
"port": "9199"
}
},
"storage": {
"rules": "/storage.rules"
}
}
I get the following error:
firestore: Port 54321 is not open on 0.0.0.0, could not start websocket server for Firestore emulator.
firestore: To select a different port, specify that port in a firebase.json config file:
{
// ...
"emulators": {
"firestore": {
"host": "HOST",
...
"websocketPort": "WEBSOCKET_PORT"
}
}
To reproduce, you can clone https://gitlab.com/fixl/docker-firebase-emulator-suite/-/tree/feature/move-to-native-websocket and then run:
make build
docker compose -f docker-compose.example.yaml up
Am I missing something?
@fixl we think this is a specific issue - could you open a new Issue on GH so we can track it better?