Loading the dungeon crawler against a local Mac stack:
✅ GameSignalRService: Connected to GameHub
WebSocket connection to 'wss://localhost:7777/ws?token=...' failed
Uncaught (in promise) Event {isTrusted: true, type: 'error', target: WebSocket, ...}
The GameHub line is a red herring — that's SignalR on the API (:33333/hubs/dungeon-crawler) and it works. The game server is a separate service on :7777.
Three causes, all needed
1. game-server isn't in the Mac stack at all.COMPOSE_SERVICES in scripts/mac-dev-common.sh never included it, so nothing listens on 7777.
2. Redis + depends_on are cluster-shaped.game-server and game-cache-initializer hardcode redis-node-1:6379,redis-node-2:6379,redis-node-3:6379, but the Mac stack runs a single Redis Stack node — 2 and 3 don't resolve. game-cache-initializer also depends_on: cluster-initiator, which requires all six nodes (the same trap redisinsight already dodges with !override).
3. Even started, it serves plain ws — this is the subtle one.
The Angular dev page is HTTPS and game-server.service.ts derives its URL from window.location.protocol (environment.development.ts defines no gameServerUrl), so the browser asks for wss://. GameServerOptions.UseHttps already defaults true — but the compose service runs ASPNETCORE_ENVIRONMENT=Production, and appsettings.Production.json carries no Ssl* keys. So ConfigureHttpsListenOptions falls through both cert branches to bare listenOptions.UseHttps(), finds no ASP.NET dev cert inside the container, and hits:
catch(InvalidOperationException){Log.Warning("No certificate available - falling back to HTTP (insecure)");}
The container then serves plain ws and reports itself perfectly healthy. Only the browser notices, as a bare failed with no detail. appsettings.Development.jsondoes set UseHttps + SslCertificatePath: ssl/gameserver.pfx, but the container never runs under Development.
Fix
--game flag on mac-dev-up.sh (mirrors --full) adding game-cache-initializer, game-server, game-events-handler. Not default — 3 image builds on first use. game-events-handler is included so game events don't pile up unconsumed on RabbitMQ.
docker-compose.mac.yml: single-node Redis, depends_on: !override to drop the cluster chain, and bind-mount the same dev PEM the native API uses (CN=localhost, already trusted in the login keychain) with GameServer__SslCertPath/SslKeyPath.
mac-dev-up.sh now exports the dev cert in step 1b, before compose up — the container reads it at startup, so it must exist first.
Bring-up gates on the TLS handshake, not just the port: a certless server still accepts TCP, which is exactly how this stays invisible.
Verified
Using PEM certificate from /app/certs/backend-cert.pem / Kestrel configured for HTTPS/WSS on port 7777 (correct branch taken, not the fallback).
Cert presented on :7777 has SHA1 46:08:4F:CB:…:FC:A8 — byte-identical to the trusted dev cert, so the browser accepts it.
curl --http1.1 WebSocket upgrade to https://localhost:7777/ws → 101 Switching Protocols.
Use --http1.1 when testing by hand — curl negotiates HTTP/2 over ALPN by default and the upgrade returns a misleading 400.
Follow-up worth considering
environment.development.ts has no gameServerUrl while environment.ts has ws://localhost:7777 and prod has wss://gameserver.spikersoft.com. The runtime derivation happens to produce the right value, but an explicit dev entry would remove the dependency on page protocol.
## Symptom
Loading the dungeon crawler against a local Mac stack:
```text
✅ GameSignalRService: Connected to GameHub
WebSocket connection to 'wss://localhost:7777/ws?token=...' failed
Uncaught (in promise) Event {isTrusted: true, type: 'error', target: WebSocket, ...}
```
The GameHub line is a red herring — that's SignalR on the API (`:33333/hubs/dungeon-crawler`) and it works. The game server is a **separate service** on :7777.
## Three causes, all needed
**1. `game-server` isn't in the Mac stack at all.** `COMPOSE_SERVICES` in `scripts/mac-dev-common.sh` never included it, so nothing listens on 7777.
**2. Redis + `depends_on` are cluster-shaped.** `game-server` and `game-cache-initializer` hardcode `redis-node-1:6379,redis-node-2:6379,redis-node-3:6379`, but the Mac stack runs a single Redis Stack node — 2 and 3 don't resolve. `game-cache-initializer` also `depends_on: cluster-initiator`, which requires all six nodes (the same trap `redisinsight` already dodges with `!override`).
**3. Even started, it serves plain `ws` — this is the subtle one.**
The Angular dev page is HTTPS and `game-server.service.ts` derives its URL from `window.location.protocol` (`environment.development.ts` defines no `gameServerUrl`), so the browser asks for `wss://`. `GameServerOptions.UseHttps` already defaults `true` — but the compose service runs `ASPNETCORE_ENVIRONMENT=Production`, and `appsettings.Production.json` carries no `Ssl*` keys. So `ConfigureHttpsListenOptions` falls through both cert branches to bare `listenOptions.UseHttps()`, finds no ASP.NET dev cert **inside the container**, and hits:
```csharp
catch (InvalidOperationException)
{
Log.Warning("No certificate available - falling back to HTTP (insecure)");
}
```
The container then serves plain `ws` and reports itself perfectly healthy. Only the browser notices, as a bare `failed` with no detail. `appsettings.Development.json` *does* set `UseHttps` + `SslCertificatePath: ssl/gameserver.pfx`, but the container never runs under Development.
## Fix
- `--game` flag on `mac-dev-up.sh` (mirrors `--full`) adding `game-cache-initializer`, `game-server`, `game-events-handler`. Not default — 3 image builds on first use. `game-events-handler` is included so game events don't pile up unconsumed on RabbitMQ.
- `docker-compose.mac.yml`: single-node Redis, `depends_on: !override` to drop the cluster chain, and bind-mount the **same dev PEM the native API uses** (`CN=localhost`, already trusted in the login keychain) with `GameServer__SslCertPath`/`SslKeyPath`.
- `mac-dev-up.sh` now exports the dev cert in **step 1b, before `compose up`** — the container reads it at startup, so it must exist first.
- Bring-up gates on the **TLS handshake**, not just the port: a certless server still accepts TCP, which is exactly how this stays invisible.
## Verified
- `Using PEM certificate from /app/certs/backend-cert.pem` / `Kestrel configured for HTTPS/WSS on port 7777` (correct branch taken, not the fallback).
- Cert presented on :7777 has SHA1 `46:08:4F:CB:…:FC:A8` — byte-identical to the trusted dev cert, so the browser accepts it.
- `curl --http1.1` WebSocket upgrade to `https://localhost:7777/ws` → **101 Switching Protocols**.
- `game-cache-initializer` exits 0; Redis caches initialize; `game-events-handler` clean.
Use `--http1.1` when testing by hand — curl negotiates HTTP/2 over ALPN by default and the upgrade returns a misleading 400.
## Follow-up worth considering
`environment.development.ts` has no `gameServerUrl` while `environment.ts` has `ws://localhost:7777` and prod has `wss://gameserver.spikersoft.com`. The runtime derivation happens to produce the right value, but an explicit dev entry would remove the dependency on page protocol.
spikersoft-backend PR #515 — --game flag adds game-cache-initializer / game-server / game-events-handler to the Mac stack, with single-node Redis, depends_on: !override to drop the cluster chain, and the trusted dev PEM mounted so :7777 actually serves wss. Bring-up now gates on the TLS handshake rather than the port, since a certless server still accepts TCP.
spikersoft-angular PR #610 — gameServerUrl defined explicitly (wss://localhost:7777) in environment.development.ts, and the base environment.ts corrected from ws:// to wss://; smoke spec locks it.
Verified end to end: Using PEM certificate from /app/certs/backend-cert.pem, the cert presented on :7777 is byte-identical to the trusted dev cert, and a WebSocket upgrade to /ws returns 101 Switching Protocols.
Closing.
Both halves merged:
- **spikersoft-backend PR #515** — `--game` flag adds `game-cache-initializer` / `game-server` / `game-events-handler` to the Mac stack, with single-node Redis, `depends_on: !override` to drop the cluster chain, and the trusted dev PEM mounted so `:7777` actually serves `wss`. Bring-up now gates on the TLS handshake rather than the port, since a certless server still accepts TCP.
- **spikersoft-angular PR #610** — `gameServerUrl` defined explicitly (`wss://localhost:7777`) in `environment.development.ts`, and the base `environment.ts` corrected from `ws://` to `wss://`; smoke spec locks it.
Verified end to end: `Using PEM certificate from /app/certs/backend-cert.pem`, the cert presented on :7777 is byte-identical to the trusted dev cert, and a WebSocket upgrade to `/ws` returns 101 Switching Protocols.
Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Symptom
Loading the dungeon crawler against a local Mac stack:
The GameHub line is a red herring — that's SignalR on the API (
:33333/hubs/dungeon-crawler) and it works. The game server is a separate service on :7777.Three causes, all needed
1.
game-serverisn't in the Mac stack at all.COMPOSE_SERVICESinscripts/mac-dev-common.shnever included it, so nothing listens on 7777.2. Redis +
depends_onare cluster-shaped.game-serverandgame-cache-initializerhardcoderedis-node-1:6379,redis-node-2:6379,redis-node-3:6379, but the Mac stack runs a single Redis Stack node — 2 and 3 don't resolve.game-cache-initializeralsodepends_on: cluster-initiator, which requires all six nodes (the same trapredisinsightalready dodges with!override).3. Even started, it serves plain
ws— this is the subtle one.The Angular dev page is HTTPS and
game-server.service.tsderives its URL fromwindow.location.protocol(environment.development.tsdefines nogameServerUrl), so the browser asks forwss://.GameServerOptions.UseHttpsalready defaultstrue— but the compose service runsASPNETCORE_ENVIRONMENT=Production, andappsettings.Production.jsoncarries noSsl*keys. SoConfigureHttpsListenOptionsfalls through both cert branches to barelistenOptions.UseHttps(), finds no ASP.NET dev cert inside the container, and hits:The container then serves plain
wsand reports itself perfectly healthy. Only the browser notices, as a barefailedwith no detail.appsettings.Development.jsondoes setUseHttps+SslCertificatePath: ssl/gameserver.pfx, but the container never runs under Development.Fix
--gameflag onmac-dev-up.sh(mirrors--full) addinggame-cache-initializer,game-server,game-events-handler. Not default — 3 image builds on first use.game-events-handleris included so game events don't pile up unconsumed on RabbitMQ.docker-compose.mac.yml: single-node Redis,depends_on: !overrideto drop the cluster chain, and bind-mount the same dev PEM the native API uses (CN=localhost, already trusted in the login keychain) withGameServer__SslCertPath/SslKeyPath.mac-dev-up.shnow exports the dev cert in step 1b, beforecompose up— the container reads it at startup, so it must exist first.Verified
Using PEM certificate from /app/certs/backend-cert.pem/Kestrel configured for HTTPS/WSS on port 7777(correct branch taken, not the fallback).46:08:4F:CB:…:FC:A8— byte-identical to the trusted dev cert, so the browser accepts it.curl --http1.1WebSocket upgrade tohttps://localhost:7777/ws→ 101 Switching Protocols.game-cache-initializerexits 0; Redis caches initialize;game-events-handlerclean.Use
--http1.1when testing by hand — curl negotiates HTTP/2 over ALPN by default and the upgrade returns a misleading 400.Follow-up worth considering
environment.development.tshas nogameServerUrlwhileenvironment.tshasws://localhost:7777and prod haswss://gameserver.spikersoft.com. The runtime derivation happens to produce the right value, but an explicit dev entry would remove the dependency on page protocol.Both halves merged:
--gameflag addsgame-cache-initializer/game-server/game-events-handlerto the Mac stack, with single-node Redis,depends_on: !overrideto drop the cluster chain, and the trusted dev PEM mounted so:7777actually serveswss. Bring-up now gates on the TLS handshake rather than the port, since a certless server still accepts TCP.gameServerUrldefined explicitly (wss://localhost:7777) inenvironment.development.ts, and the baseenvironment.tscorrected fromws://towss://; smoke spec locks it.Verified end to end:
Using PEM certificate from /app/certs/backend-cert.pem, the cert presented on :7777 is byte-identical to the trusted dev cert, and a WebSocket upgrade to/wsreturns 101 Switching Protocols.Closing.