Merge pull request 'feat(deploy): single-origin path-based deployment + project site' (#34) from feature/deploy-single-origin into development
This commit was merged in pull request #34.
This commit is contained in:
@@ -28,4 +28,4 @@ jobs:
|
||||
echo " 2. scp the .tar.gz bundles to the production host."
|
||||
echo " 3. ssh prod 'docker load -i ...' for backend / gateway / engine."
|
||||
echo " 4. ssh prod 'docker compose -f /opt/galaxy/docker-compose.yml up -d'."
|
||||
echo " 5. Probe https://api.galaxy.com/healthz and roll back on failure."
|
||||
echo " 5. Probe https://<public host>/healthz and roll back on failure."
|
||||
|
||||
@@ -24,6 +24,7 @@ on:
|
||||
- 'game/**'
|
||||
- 'pkg/**'
|
||||
- 'ui/**'
|
||||
- 'site/**'
|
||||
- 'go.work'
|
||||
- 'go.work.sum'
|
||||
- 'tools/dev-deploy/**'
|
||||
@@ -76,7 +77,11 @@ jobs:
|
||||
- name: Build UI frontend
|
||||
working-directory: ui/frontend
|
||||
env:
|
||||
VITE_GATEWAY_BASE_URL: https://api.galaxy.lan
|
||||
# Single-origin deployment: an empty base URL means the
|
||||
# gateway shares the document origin (REST at /api, Connect at
|
||||
# /rpc). The game UI is served under the /game/ base path.
|
||||
VITE_GATEWAY_BASE_URL: ""
|
||||
BASE_PATH: /game
|
||||
# Surface the synthetic-report loader and similar dev-only
|
||||
# affordances in the long-lived dev bundle. The prod build
|
||||
# path (`prod-build.yaml`) leaves this flag unset so the
|
||||
@@ -91,6 +96,14 @@ jobs:
|
||||
export VITE_GATEWAY_RESPONSE_PUBLIC_KEY="$(grep -E '^VITE_GATEWAY_RESPONSE_PUBLIC_KEY=' .env.development | cut -d= -f2)"
|
||||
pnpm build
|
||||
|
||||
- name: Install site dependencies
|
||||
working-directory: site
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build project site
|
||||
working-directory: site
|
||||
run: pnpm build
|
||||
|
||||
- name: Build galaxy-engine image
|
||||
working-directory: ${{ gitea.workspace }}
|
||||
run: |
|
||||
@@ -112,6 +125,14 @@ jobs:
|
||||
-v "${{ gitea.workspace }}/ui/frontend/build:/src:ro" \
|
||||
alpine sh -c 'rm -rf /dst/* /dst/.??* 2>/dev/null; cp -a /src/. /dst/'
|
||||
|
||||
- name: Seed site volume
|
||||
run: |
|
||||
docker volume create galaxy-dev-site-dist >/dev/null
|
||||
docker run --rm \
|
||||
-v galaxy-dev-site-dist:/dst \
|
||||
-v "${{ gitea.workspace }}/site/.vitepress/dist:/src:ro" \
|
||||
alpine sh -c 'rm -rf /dst/* /dst/.??* 2>/dev/null; cp -a /src/. /dst/'
|
||||
|
||||
- name: Seed geoip volume
|
||||
run: |
|
||||
# Copy the GeoIP test fixture into a named volume so the
|
||||
@@ -162,9 +183,12 @@ jobs:
|
||||
# `tls internal`) terminates and forwards into the edge
|
||||
# network. We accept the host's internal CA via -k because
|
||||
# the runner image has no reason to trust it.
|
||||
curl -sk --max-time 10 https://api.galaxy.lan/healthz \
|
||||
curl -sk --max-time 10 https://galaxy.lan/healthz \
|
||||
| tee /tmp/healthz
|
||||
test -s /tmp/healthz
|
||||
curl -sk --max-time 10 -o /dev/null -w '%{http_code}\n' \
|
||||
https://www.galaxy.lan/ | tee /tmp/www_status
|
||||
grep -qE '^(200|304)$' /tmp/www_status
|
||||
https://galaxy.lan/ | tee /tmp/site_status
|
||||
grep -qE '^(200|304)$' /tmp/site_status
|
||||
curl -sk --max-time 10 -o /dev/null -w '%{http_code}\n' \
|
||||
https://galaxy.lan/game/ | tee /tmp/game_status
|
||||
grep -qE '^(200|304)$' /tmp/game_status
|
||||
|
||||
@@ -16,6 +16,7 @@ on:
|
||||
- 'game/**'
|
||||
- 'pkg/**'
|
||||
- 'ui/**'
|
||||
- 'site/**'
|
||||
- 'go.work'
|
||||
- 'go.work.sum'
|
||||
- '.gitea/workflows/prod-build.yaml'
|
||||
@@ -93,7 +94,11 @@ jobs:
|
||||
- name: Build UI bundle
|
||||
working-directory: ui/frontend
|
||||
env:
|
||||
VITE_GATEWAY_BASE_URL: https://api.galaxy.com
|
||||
# Single-origin deployment: an empty base URL means the
|
||||
# gateway shares the document origin (REST at /api, Connect at
|
||||
# /rpc). The game UI is served under the /game/ base path.
|
||||
VITE_GATEWAY_BASE_URL: ""
|
||||
BASE_PATH: /game
|
||||
run: |
|
||||
# Production response-signing public key is not in the repo
|
||||
# yet (the dev key in `tools/local-dev/keys/` is for dev
|
||||
@@ -104,6 +109,14 @@ jobs:
|
||||
export VITE_GATEWAY_RESPONSE_PUBLIC_KEY="$(grep -E '^VITE_GATEWAY_RESPONSE_PUBLIC_KEY=' .env.development | cut -d= -f2)"
|
||||
pnpm build
|
||||
|
||||
- name: Install site dependencies
|
||||
working-directory: site
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build project site
|
||||
working-directory: site
|
||||
run: pnpm build
|
||||
|
||||
- name: Save images as artifact bundles
|
||||
run: |
|
||||
mkdir -p artifacts
|
||||
@@ -115,6 +128,8 @@ jobs:
|
||||
| gzip >"artifacts/game-engine-${{ steps.tag.outputs.tag }}.tar.gz"
|
||||
tar -C ui/frontend -czf \
|
||||
"artifacts/ui-dist-${{ steps.tag.outputs.tag }}.tar.gz" build
|
||||
tar -C site/.vitepress -czf \
|
||||
"artifacts/site-dist-${{ steps.tag.outputs.tag }}.tar.gz" dist
|
||||
|
||||
- name: Upload images
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
name: Build · Site
|
||||
|
||||
# Builds the VitePress project site so a broken site change fails its PR.
|
||||
# The dev-deploy / prod-build workflows build and ship the site
|
||||
# separately; this is the fast PR gate. No `!**/*.md` exclusion — the
|
||||
# site is Markdown, so content changes must be exercised too.
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'site/**'
|
||||
- '.gitea/workflows/site-build.yaml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'site/**'
|
||||
- '.gitea/workflows/site-build.yaml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 11.0.7
|
||||
dest: ${{ runner.temp }}/setup-pnpm
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: site/pnpm-lock.yaml
|
||||
|
||||
- name: Install site dependencies
|
||||
working-directory: site
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build project site
|
||||
working-directory: site
|
||||
run: pnpm build
|
||||
@@ -44,7 +44,8 @@ Branches:
|
||||
is manual through `deploy-prod.yaml`.
|
||||
- `development` — long-lived dev integration branch. Every merge into
|
||||
it auto-deploys to the dev environment via `dev-deploy.yaml`
|
||||
(reachable at `https://www.galaxy.lan` / `https://api.galaxy.lan`).
|
||||
(single origin `https://galaxy.lan`: site at `/`, game at `/game/`,
|
||||
gateway REST at `/api`).
|
||||
- `feature/*` — short-lived branches off `development`. Merged back
|
||||
via PR; only then do they reach the dev environment automatically.
|
||||
|
||||
|
||||
+35
-9
@@ -579,13 +579,25 @@ behaviour for any of its guarantees.
|
||||
|
||||
The authenticated edge listener is built on `connectrpc.com/connect` and
|
||||
natively serves the Connect, gRPC, and gRPC-Web protocols on a single
|
||||
HTTP/2 cleartext (`h2c`) port. Browser clients use Connect via
|
||||
`@connectrpc/connect-web`; native iOS / Android / desktop clients can
|
||||
use either Connect or raw gRPC framing against the same listener.
|
||||
Envelope, signature, freshness, and anti-replay rules below are
|
||||
protocol-agnostic — they apply identically to every supported wire
|
||||
HTTP/2 cleartext (`h2c`) port. The v1 service is `edge.v1.Gateway`;
|
||||
browser clients address its methods at `/rpc/edge.v1.Gateway/<Method>`
|
||||
and the edge strips the `/rpc` prefix so the gateway sees the
|
||||
proto-derived `/edge.v1.Gateway/<Method>` path. Browser clients use
|
||||
Connect via `@connectrpc/connect-web`; native iOS / Android / desktop
|
||||
clients can use either Connect or raw gRPC framing against the same
|
||||
listener. Envelope, signature, freshness, and anti-replay rules below
|
||||
are protocol-agnostic — they apply identically to every supported wire
|
||||
framing.
|
||||
|
||||
Both the authenticated `/rpc/*` surface and the gateway's public REST at
|
||||
`/api/*` are served same-origin with the game UI, so the gateway runs
|
||||
with CORS disabled by default: the
|
||||
`GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS` and
|
||||
`GATEWAY_AUTHENTICATED_GRPC_CORS_ALLOWED_ORIGINS` allow-lists are empty,
|
||||
which turns the CORS middleware off and emits no `Access-Control-*`
|
||||
headers. They would be repopulated only if a deployment fronted the
|
||||
gateway on a different host than the UI.
|
||||
|
||||
### Principles
|
||||
|
||||
- No browser cookies.
|
||||
@@ -775,6 +787,9 @@ domain tables.
|
||||
|
||||
### TLS and MITM
|
||||
|
||||
TLS terminates once, at the edge in front of the gateway, for the single
|
||||
public origin that serves the site, the game UI, and both gateway
|
||||
surfaces. A single certificate therefore covers the whole deployment.
|
||||
Native clients should use TLS pinning (SPKI-based) in addition to the
|
||||
signed exchange. Browser clients rely on browser-managed TLS and the
|
||||
signed exchange.
|
||||
@@ -845,8 +860,10 @@ Branches:
|
||||
way in is a PR merge from `development`.
|
||||
- `development` — long-lived dev integration branch. Every merge
|
||||
triggers an auto-deploy into the long-lived dev environment on the
|
||||
CI host, reachable through the host Caddy at
|
||||
`https://www.galaxy.lan` and `https://api.galaxy.lan`.
|
||||
CI host, reachable through the host Caddy at a single origin
|
||||
`https://galaxy.lan` (project site at `/`, game UI at `/game/`,
|
||||
gateway public REST at `/api/*` and `/healthz`, authenticated
|
||||
Connect/gRPC-Web at `/rpc/*`).
|
||||
- `feature/*` — short-lived branches off `development`. Merged back
|
||||
via PR; PRs run unit + integration checks before merge.
|
||||
|
||||
@@ -872,8 +889,9 @@ Environments:
|
||||
|
||||
- **`tools/local-dev/`** — single-developer playground. Bound to
|
||||
host ports, Vite dev server runs on the host. Not driven by CI.
|
||||
- **`tools/dev-deploy/`** — long-lived dev environment behind
|
||||
`*.galaxy.lan`, redeployed on every merge into `development`.
|
||||
- **`tools/dev-deploy/`** — long-lived dev environment behind the
|
||||
single origin `galaxy.lan`, redeployed on every merge into
|
||||
`development`.
|
||||
- **production** — future. Images come from the
|
||||
`galaxy-images-commit-<sha>` artifact produced by `prod-build.yaml`
|
||||
and are shipped to the production host via `docker save` →
|
||||
@@ -913,6 +931,14 @@ untouched by compose between deploys.
|
||||
|
||||
## 19. Deployment Topology (informational)
|
||||
|
||||
- The public edge is single-origin and path-based: one host (the dev
|
||||
host is `galaxy.lan`; prod takes the real host from
|
||||
`GALAXY_PUBLIC_HOST`) terminates TLS and routes by path —
|
||||
`/` → project site, `/game/` → game UI, `/api/*` and `/healthz` →
|
||||
gateway public REST (`galaxy-api:8080`), `/rpc/*` → gateway
|
||||
authenticated Connect/gRPC-Web (`galaxy-api:9090`, with the `/rpc`
|
||||
prefix stripped before the gateway). The same dev and prod shape is
|
||||
domain-agnostic: no host name is baked into the deployed artifacts.
|
||||
- MVP runs three executables: one `gateway` instance, one `backend`
|
||||
instance, and N `galaxy-game-{game_id}` containers managed by backend.
|
||||
- One Postgres database is shared by `backend` only.
|
||||
|
||||
+20
-5
@@ -94,8 +94,20 @@ The authenticated edge listener is built on
|
||||
the Connect, gRPC, and gRPC-Web protocols on a single HTTP/2 cleartext
|
||||
(`h2c`) port. Browser clients use `@connectrpc/connect-web`; native
|
||||
clients can use either Connect or raw gRPC framing against the same
|
||||
listener. Production TLS termination happens upstream of the gateway,
|
||||
matching the previous gRPC-only deployment posture.
|
||||
listener. TLS termination happens upstream of the gateway at the edge
|
||||
Caddy, which fronts both transports under one host.
|
||||
|
||||
Both transports are served same-origin under one host. The edge routes
|
||||
public REST and the health probe at `/api/*` and `/healthz` to the
|
||||
public listener, and the authenticated Connect/gRPC-Web surface at
|
||||
`/rpc/*` to the authenticated listener (the `/rpc` prefix is stripped
|
||||
before the gateway). Because the game UI is served from the same origin,
|
||||
the gateway runs with CORS disabled by default: both
|
||||
`GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS` and
|
||||
`GATEWAY_AUTHENTICATED_GRPC_CORS_ALLOWED_ORIGINS` are empty, an empty
|
||||
allow-list turns the CORS middleware off, and responses carry no
|
||||
`Access-Control-*` headers. Those knobs would only be repopulated if a
|
||||
deployment ever fronted the gateway on a different host than the UI.
|
||||
|
||||
### Public REST Surface
|
||||
|
||||
@@ -225,10 +237,13 @@ It binds the stream to `user_id` and `device_session_id` and starts by sending
|
||||
a signed service event that includes the current server time in milliseconds.
|
||||
|
||||
The v1 protobuf contract lives in
|
||||
`proto/galaxy/gateway/v1/edge_gateway.proto` under package
|
||||
`galaxy.gateway.v1` and service `EdgeGateway`.
|
||||
`proto/edge/v1/edge_gateway.proto` under package `edge.v1` and service
|
||||
`Gateway`. Browser and native clients address its methods at
|
||||
`/rpc/edge.v1.Gateway/<Method>`; the edge Caddy strips the `/rpc`
|
||||
prefix so the gateway listener sees the proto-derived
|
||||
`/edge.v1.Gateway/<Method>` path.
|
||||
Generated Go bindings are committed under
|
||||
`proto/galaxy/gateway/v1/` (gRPC stubs and `gatewayv1connect/` Connect
|
||||
`proto/edge/v1/` (gRPC stubs and `edgev1connect/` Connect
|
||||
handlers) and are regenerated with:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"galaxy/gateway/authn"
|
||||
"galaxy/gateway/internal/clock"
|
||||
"galaxy/gateway/internal/downstream"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
// commandRoutingService translates the verified authenticated request context
|
||||
// into an internal downstream command and signs successful unary responses.
|
||||
type commandRoutingService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
subscribeDelegate gatewayv1.EdgeGatewayServer
|
||||
subscribeDelegate edgev1.GatewayServer
|
||||
router downstream.Router
|
||||
responseSigner authn.ResponseSigner
|
||||
clock clock.Clock
|
||||
@@ -32,7 +32,7 @@ type commandRoutingService struct {
|
||||
|
||||
// ExecuteCommand builds a verified downstream command, routes it by exact
|
||||
// message_type, executes it, and signs the resulting unary response.
|
||||
func (s commandRoutingService) ExecuteCommand(ctx context.Context, _ *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
func (s commandRoutingService) ExecuteCommand(ctx context.Context, _ *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
command, err := authenticatedCommandFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -80,7 +80,7 @@ func (s commandRoutingService) ExecuteCommand(ctx context.Context, _ *gatewayv1.
|
||||
return nil, status.Error(codes.Unavailable, "response signer is unavailable")
|
||||
}
|
||||
|
||||
return &gatewayv1.ExecuteCommandResponse{
|
||||
return &edgev1.ExecuteCommandResponse{
|
||||
ProtocolVersion: command.ProtocolVersion,
|
||||
RequestId: command.RequestID,
|
||||
TimestampMs: responseTimestampMS,
|
||||
@@ -93,13 +93,13 @@ func (s commandRoutingService) ExecuteCommand(ctx context.Context, _ *gatewayv1.
|
||||
|
||||
// SubscribeEvents delegates to the authenticated streaming service
|
||||
// implementation selected during server construction.
|
||||
func (s commandRoutingService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s commandRoutingService) SubscribeEvents(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
return s.subscribeDelegate.SubscribeEvents(req, stream)
|
||||
}
|
||||
|
||||
// newCommandRoutingService constructs the final authenticated service that
|
||||
// owns verified unary routing while preserving the delegated streaming path.
|
||||
func newCommandRoutingService(subscribeDelegate gatewayv1.EdgeGatewayServer, router downstream.Router, responseSigner authn.ResponseSigner, clk clock.Clock, downstreamTimeout time.Duration) gatewayv1.EdgeGatewayServer {
|
||||
func newCommandRoutingService(subscribeDelegate edgev1.GatewayServer, router downstream.Router, responseSigner authn.ResponseSigner, clk clock.Clock, downstreamTimeout time.Duration) edgev1.GatewayServer {
|
||||
return commandRoutingService{
|
||||
subscribeDelegate: subscribeDelegate,
|
||||
router: router,
|
||||
@@ -142,4 +142,4 @@ func (unavailableResponseSigner) SignEvent(authn.EventSigningFields) ([]byte, er
|
||||
return nil, errors.New("response signer is unavailable")
|
||||
}
|
||||
|
||||
var _ gatewayv1.EdgeGatewayServer = commandRoutingService{}
|
||||
var _ edgev1.GatewayServer = commandRoutingService{}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
"galaxy/gateway/proto/galaxy/gateway/v1/gatewayv1connect"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
"galaxy/gateway/proto/edge/v1/edgev1connect"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -17,15 +17,15 @@ import (
|
||||
// connectEdgeAdapter exposes the existing gRPC-shaped authenticated edge
|
||||
// service decorator stack (envelope → session → payload-hash → signature →
|
||||
// freshness/replay → rate-limit → routing/push) through the
|
||||
// gatewayv1connect.EdgeGatewayHandler interface. It owns no logic of its
|
||||
// edgev1connect.GatewayHandler interface. It owns no logic of its
|
||||
// own; the underlying decorator stack carries the full ingress contract
|
||||
// unchanged.
|
||||
type connectEdgeAdapter struct {
|
||||
impl gatewayv1.EdgeGatewayServer
|
||||
impl edgev1.GatewayServer
|
||||
}
|
||||
|
||||
// newConnectEdgeAdapter wraps impl as a Connect handler.
|
||||
func newConnectEdgeAdapter(impl gatewayv1.EdgeGatewayServer) gatewayv1connect.EdgeGatewayHandler {
|
||||
func newConnectEdgeAdapter(impl edgev1.GatewayServer) edgev1connect.GatewayHandler {
|
||||
return &connectEdgeAdapter{impl: impl}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func newConnectEdgeAdapter(impl gatewayv1.EdgeGatewayServer) gatewayv1connect.Ed
|
||||
// service, and wraps the typed response. gRPC `status.Error` values
|
||||
// returned by the decorator stack are translated to *connect.Error so
|
||||
// the Connect client receives the matching code and message.
|
||||
func (a *connectEdgeAdapter) ExecuteCommand(ctx context.Context, req *connect.Request[gatewayv1.ExecuteCommandRequest]) (*connect.Response[gatewayv1.ExecuteCommandResponse], error) {
|
||||
func (a *connectEdgeAdapter) ExecuteCommand(ctx context.Context, req *connect.Request[edgev1.ExecuteCommandRequest]) (*connect.Response[edgev1.ExecuteCommandResponse], error) {
|
||||
resp, err := a.impl.ExecuteCommand(ctx, req.Msg)
|
||||
if err != nil {
|
||||
return nil, translateGRPCStatusError(err)
|
||||
@@ -48,7 +48,7 @@ func (a *connectEdgeAdapter) ExecuteCommand(ctx context.Context, req *connect.Re
|
||||
// stream; the remaining grpc.ServerStream surface is satisfied by no-op
|
||||
// shims so the interface contract is met without panicking. Errors
|
||||
// returned by the decorator stack are translated to *connect.Error.
|
||||
func (a *connectEdgeAdapter) SubscribeEvents(ctx context.Context, req *connect.Request[gatewayv1.SubscribeEventsRequest], stream *connect.ServerStream[gatewayv1.GatewayEvent]) error {
|
||||
func (a *connectEdgeAdapter) SubscribeEvents(ctx context.Context, req *connect.Request[edgev1.SubscribeEventsRequest], stream *connect.ServerStream[edgev1.GatewayEvent]) error {
|
||||
wrapped := &connectEdgeStream{ctx: ctx, stream: stream}
|
||||
if err := a.impl.SubscribeEvents(req.Msg, wrapped); err != nil {
|
||||
return translateGRPCStatusError(err)
|
||||
@@ -83,19 +83,19 @@ func translateGRPCStatusError(err error) error {
|
||||
return connect.NewError(connect.Code(grpcStatus.Code()), errors.New(grpcStatus.Message()))
|
||||
}
|
||||
|
||||
// connectEdgeStream satisfies grpc.ServerStreamingServer[gatewayv1.GatewayEvent]
|
||||
// connectEdgeStream satisfies grpc.ServerStreamingServer[edgev1.GatewayEvent]
|
||||
// on top of *connect.ServerStream. The decorator stack reads the request
|
||||
// context and pushes outbound events through Send; the rest of the
|
||||
// grpc.ServerStream surface is not exercised in the gateway, so the no-op
|
||||
// implementations preserve the type contract without surprising behaviour.
|
||||
type connectEdgeStream struct {
|
||||
ctx context.Context
|
||||
stream *connect.ServerStream[gatewayv1.GatewayEvent]
|
||||
stream *connect.ServerStream[edgev1.GatewayEvent]
|
||||
}
|
||||
|
||||
// Send forwards a typed gateway event through the underlying Connect server
|
||||
// stream.
|
||||
func (s *connectEdgeStream) Send(event *gatewayv1.GatewayEvent) error {
|
||||
func (s *connectEdgeStream) Send(event *edgev1.GatewayEvent) error {
|
||||
return s.stream.Send(event)
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ func (s *connectEdgeStream) SetTrailer(metadata.MD) {}
|
||||
// SendMsg directly; if a future caller does, the typed Send path is used
|
||||
// when the message is a GatewayEvent.
|
||||
func (s *connectEdgeStream) SendMsg(m any) error {
|
||||
event, ok := m.(*gatewayv1.GatewayEvent)
|
||||
event, ok := m.(*edgev1.GatewayEvent)
|
||||
if !ok {
|
||||
return fmt.Errorf("connectEdgeStream.SendMsg: unsupported message type %T", m)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"buf.build/go/protovalidate"
|
||||
"google.golang.org/grpc"
|
||||
@@ -47,14 +47,14 @@ func parsedEnvelopeFromContext(ctx context.Context) (parsedEnvelope, bool) {
|
||||
// envelopeValidatingService applies envelope parsing and the protocol gate
|
||||
// before delegating to the configured service implementation.
|
||||
type envelopeValidatingService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
delegate gatewayv1.EdgeGatewayServer
|
||||
delegate edgev1.GatewayServer
|
||||
}
|
||||
|
||||
// ExecuteCommand validates req and only then forwards it to the configured
|
||||
// delegate with the parsed envelope attached to ctx.
|
||||
func (s envelopeValidatingService) ExecuteCommand(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
func (s envelopeValidatingService) ExecuteCommand(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
envelope, err := parseExecuteCommandRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -65,7 +65,7 @@ func (s envelopeValidatingService) ExecuteCommand(ctx context.Context, req *gate
|
||||
|
||||
// SubscribeEvents validates req and only then forwards it to the configured
|
||||
// delegate with the parsed envelope attached to the stream context.
|
||||
func (s envelopeValidatingService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s envelopeValidatingService) SubscribeEvents(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
envelope, err := parseSubscribeEventsRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -79,7 +79,7 @@ func (s envelopeValidatingService) SubscribeEvents(req *gatewayv1.SubscribeEvent
|
||||
|
||||
// parseExecuteCommandRequest validates req according to the request-envelope
|
||||
// rules and returns a cloned parsed envelope suitable for later auth steps.
|
||||
func parseExecuteCommandRequest(req *gatewayv1.ExecuteCommandRequest) (parsedEnvelope, error) {
|
||||
func parseExecuteCommandRequest(req *edgev1.ExecuteCommandRequest) (parsedEnvelope, error) {
|
||||
if req == nil {
|
||||
return parsedEnvelope{}, newMalformedEnvelopeError("request envelope must not be nil")
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func parseExecuteCommandRequest(req *gatewayv1.ExecuteCommandRequest) (parsedEnv
|
||||
|
||||
// parseSubscribeEventsRequest validates req according to the request-envelope
|
||||
// rules and returns a cloned parsed envelope suitable for later auth steps.
|
||||
func parseSubscribeEventsRequest(req *gatewayv1.SubscribeEventsRequest) (parsedEnvelope, error) {
|
||||
func parseSubscribeEventsRequest(req *edgev1.SubscribeEventsRequest) (parsedEnvelope, error) {
|
||||
if req == nil {
|
||||
return parsedEnvelope{}, newMalformedEnvelopeError("request envelope must not be nil")
|
||||
}
|
||||
@@ -131,13 +131,13 @@ func parseSubscribeEventsRequest(req *gatewayv1.SubscribeEventsRequest) (parsedE
|
||||
|
||||
// newEnvelopeValidatingService wraps delegate with the envelope-validation
|
||||
// gate.
|
||||
func newEnvelopeValidatingService(delegate gatewayv1.EdgeGatewayServer) gatewayv1.EdgeGatewayServer {
|
||||
func newEnvelopeValidatingService(delegate edgev1.GatewayServer) edgev1.GatewayServer {
|
||||
return envelopeValidatingService{delegate: delegate}
|
||||
}
|
||||
|
||||
// canonicalExecuteCommandValidationError maps any ExecuteCommand validation
|
||||
// failure into the stable canonical error chosen by field order.
|
||||
func canonicalExecuteCommandValidationError(req *gatewayv1.ExecuteCommandRequest) error {
|
||||
func canonicalExecuteCommandValidationError(req *edgev1.ExecuteCommandRequest) error {
|
||||
switch {
|
||||
case req.GetProtocolVersion() == "":
|
||||
return newMalformedEnvelopeError("protocol_version must not be empty")
|
||||
@@ -162,7 +162,7 @@ func canonicalExecuteCommandValidationError(req *gatewayv1.ExecuteCommandRequest
|
||||
|
||||
// canonicalSubscribeEventsValidationError maps any SubscribeEvents validation
|
||||
// failure into the stable canonical error chosen by field order.
|
||||
func canonicalSubscribeEventsValidationError(req *gatewayv1.SubscribeEventsRequest) error {
|
||||
func canonicalSubscribeEventsValidationError(req *edgev1.SubscribeEventsRequest) error {
|
||||
switch {
|
||||
case req.GetProtocolVersion() == "":
|
||||
return newMalformedEnvelopeError("protocol_version must not be empty")
|
||||
@@ -198,7 +198,7 @@ func newUnsupportedProtocolVersionError(version string) error {
|
||||
type parsedEnvelopeContextKey struct{}
|
||||
|
||||
type envelopeContextStream struct {
|
||||
grpc.ServerStreamingServer[gatewayv1.GatewayEvent]
|
||||
grpc.ServerStreamingServer[edgev1.GatewayEvent]
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
@@ -210,4 +210,4 @@ func (s envelopeContextStream) Context() context.Context {
|
||||
return s.ctx
|
||||
}
|
||||
|
||||
var _ gatewayv1.EdgeGatewayServer = envelopeValidatingService{}
|
||||
var _ edgev1.GatewayServer = envelopeValidatingService{}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -19,10 +19,10 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mutate func(*gatewayv1.ExecuteCommandRequest)
|
||||
mutate func(*edgev1.ExecuteCommandRequest)
|
||||
wantCode codes.Code
|
||||
wantMessage string
|
||||
assertValid func(*testing.T, *gatewayv1.ExecuteCommandRequest, parsedEnvelope)
|
||||
assertValid func(*testing.T, *edgev1.ExecuteCommandRequest, parsedEnvelope)
|
||||
}{
|
||||
{
|
||||
name: "nil request",
|
||||
@@ -31,7 +31,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty protocol version",
|
||||
mutate: func(req *gatewayv1.ExecuteCommandRequest) {
|
||||
mutate: func(req *edgev1.ExecuteCommandRequest) {
|
||||
req.ProtocolVersion = ""
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -39,7 +39,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty device session id",
|
||||
mutate: func(req *gatewayv1.ExecuteCommandRequest) {
|
||||
mutate: func(req *edgev1.ExecuteCommandRequest) {
|
||||
req.DeviceSessionId = ""
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -47,7 +47,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty message type",
|
||||
mutate: func(req *gatewayv1.ExecuteCommandRequest) {
|
||||
mutate: func(req *edgev1.ExecuteCommandRequest) {
|
||||
req.MessageType = ""
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -55,7 +55,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "zero timestamp",
|
||||
mutate: func(req *gatewayv1.ExecuteCommandRequest) {
|
||||
mutate: func(req *edgev1.ExecuteCommandRequest) {
|
||||
req.TimestampMs = 0
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -63,7 +63,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty request id",
|
||||
mutate: func(req *gatewayv1.ExecuteCommandRequest) {
|
||||
mutate: func(req *edgev1.ExecuteCommandRequest) {
|
||||
req.RequestId = ""
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -71,7 +71,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty payload bytes",
|
||||
mutate: func(req *gatewayv1.ExecuteCommandRequest) {
|
||||
mutate: func(req *edgev1.ExecuteCommandRequest) {
|
||||
req.PayloadBytes = nil
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -79,7 +79,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty payload hash",
|
||||
mutate: func(req *gatewayv1.ExecuteCommandRequest) {
|
||||
mutate: func(req *edgev1.ExecuteCommandRequest) {
|
||||
req.PayloadHash = nil
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -87,7 +87,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty signature",
|
||||
mutate: func(req *gatewayv1.ExecuteCommandRequest) {
|
||||
mutate: func(req *edgev1.ExecuteCommandRequest) {
|
||||
req.Signature = nil
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -95,7 +95,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "unsupported protocol version",
|
||||
mutate: func(req *gatewayv1.ExecuteCommandRequest) {
|
||||
mutate: func(req *edgev1.ExecuteCommandRequest) {
|
||||
req.ProtocolVersion = "v2"
|
||||
},
|
||||
wantCode: codes.FailedPrecondition,
|
||||
@@ -104,7 +104,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
{
|
||||
name: "valid request",
|
||||
wantCode: codes.OK,
|
||||
assertValid: func(t *testing.T, req *gatewayv1.ExecuteCommandRequest, envelope parsedEnvelope) {
|
||||
assertValid: func(t *testing.T, req *edgev1.ExecuteCommandRequest, envelope parsedEnvelope) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, supportedProtocolVersion, envelope.ProtocolVersion)
|
||||
@@ -138,7 +138,7 @@ func TestParseExecuteCommandRequest(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var req *gatewayv1.ExecuteCommandRequest
|
||||
var req *edgev1.ExecuteCommandRequest
|
||||
if tt.name != "nil request" {
|
||||
req = newValidExecuteCommandRequest()
|
||||
if tt.mutate != nil {
|
||||
@@ -166,10 +166,10 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mutate func(*gatewayv1.SubscribeEventsRequest)
|
||||
mutate func(*edgev1.SubscribeEventsRequest)
|
||||
wantCode codes.Code
|
||||
wantMessage string
|
||||
assertValid func(*testing.T, *gatewayv1.SubscribeEventsRequest, parsedEnvelope)
|
||||
assertValid func(*testing.T, *edgev1.SubscribeEventsRequest, parsedEnvelope)
|
||||
}{
|
||||
{
|
||||
name: "nil request",
|
||||
@@ -178,7 +178,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty protocol version",
|
||||
mutate: func(req *gatewayv1.SubscribeEventsRequest) {
|
||||
mutate: func(req *edgev1.SubscribeEventsRequest) {
|
||||
req.ProtocolVersion = ""
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -186,7 +186,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty device session id",
|
||||
mutate: func(req *gatewayv1.SubscribeEventsRequest) {
|
||||
mutate: func(req *edgev1.SubscribeEventsRequest) {
|
||||
req.DeviceSessionId = ""
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -194,7 +194,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty message type",
|
||||
mutate: func(req *gatewayv1.SubscribeEventsRequest) {
|
||||
mutate: func(req *edgev1.SubscribeEventsRequest) {
|
||||
req.MessageType = ""
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -202,7 +202,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "zero timestamp",
|
||||
mutate: func(req *gatewayv1.SubscribeEventsRequest) {
|
||||
mutate: func(req *edgev1.SubscribeEventsRequest) {
|
||||
req.TimestampMs = 0
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -210,7 +210,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty request id",
|
||||
mutate: func(req *gatewayv1.SubscribeEventsRequest) {
|
||||
mutate: func(req *edgev1.SubscribeEventsRequest) {
|
||||
req.RequestId = ""
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -218,7 +218,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty payload hash",
|
||||
mutate: func(req *gatewayv1.SubscribeEventsRequest) {
|
||||
mutate: func(req *edgev1.SubscribeEventsRequest) {
|
||||
req.PayloadHash = nil
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -226,7 +226,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "empty signature",
|
||||
mutate: func(req *gatewayv1.SubscribeEventsRequest) {
|
||||
mutate: func(req *edgev1.SubscribeEventsRequest) {
|
||||
req.Signature = nil
|
||||
},
|
||||
wantCode: codes.InvalidArgument,
|
||||
@@ -234,7 +234,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "unsupported protocol version",
|
||||
mutate: func(req *gatewayv1.SubscribeEventsRequest) {
|
||||
mutate: func(req *edgev1.SubscribeEventsRequest) {
|
||||
req.ProtocolVersion = "v2"
|
||||
},
|
||||
wantCode: codes.FailedPrecondition,
|
||||
@@ -243,7 +243,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
{
|
||||
name: "valid request with empty payload bytes",
|
||||
wantCode: codes.OK,
|
||||
assertValid: func(t *testing.T, req *gatewayv1.SubscribeEventsRequest, envelope parsedEnvelope) {
|
||||
assertValid: func(t *testing.T, req *edgev1.SubscribeEventsRequest, envelope parsedEnvelope) {
|
||||
t.Helper()
|
||||
|
||||
assert.Empty(t, req.GetPayloadBytes())
|
||||
@@ -260,7 +260,7 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var req *gatewayv1.SubscribeEventsRequest
|
||||
var req *edgev1.SubscribeEventsRequest
|
||||
if tt.name != "nil request" {
|
||||
req = newValidSubscribeEventsRequest()
|
||||
if tt.mutate != nil {
|
||||
@@ -286,10 +286,10 @@ func TestParseSubscribeEventsRequest(t *testing.T) {
|
||||
func TestEnvelopeValidatingServiceExecuteCommandRejectsInvalidRequestBeforeDelegate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
service := newEnvelopeValidatingService(delegate)
|
||||
|
||||
_, err := service.ExecuteCommand(context.Background(), &gatewayv1.ExecuteCommandRequest{})
|
||||
_, err := service.ExecuteCommand(context.Background(), &edgev1.ExecuteCommandRequest{})
|
||||
require.Error(t, err)
|
||||
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
@@ -299,10 +299,10 @@ func TestEnvelopeValidatingServiceExecuteCommandRejectsInvalidRequestBeforeDeleg
|
||||
func TestEnvelopeValidatingServiceSubscribeEventsRejectsInvalidRequestBeforeDelegate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
service := newEnvelopeValidatingService(delegate)
|
||||
|
||||
err := service.SubscribeEvents(&gatewayv1.SubscribeEventsRequest{}, stubGatewayEventStream{})
|
||||
err := service.SubscribeEvents(&edgev1.SubscribeEventsRequest{}, stubGatewayEventStream{})
|
||||
require.Error(t, err)
|
||||
|
||||
assert.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
@@ -313,15 +313,15 @@ func TestEnvelopeValidatingServiceExecuteCommandAttachesParsedEnvelope(t *testin
|
||||
t.Parallel()
|
||||
|
||||
want := newValidExecuteCommandRequest()
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
delegate := &recordingGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
envelope, ok := parsedEnvelopeFromContext(ctx)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, want.GetRequestId(), envelope.RequestID)
|
||||
assert.Equal(t, want.GetDeviceSessionId(), envelope.DeviceSessionID)
|
||||
assert.Equal(t, want.GetMessageType(), envelope.MessageType)
|
||||
assert.Equal(t, want.GetPayloadBytes(), envelope.PayloadBytes)
|
||||
return &gatewayv1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
return &edgev1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
},
|
||||
}
|
||||
service := newEnvelopeValidatingService(delegate)
|
||||
@@ -337,8 +337,8 @@ func TestEnvelopeValidatingServiceSubscribeEventsAttachesParsedEnvelope(t *testi
|
||||
t.Parallel()
|
||||
|
||||
want := newValidSubscribeEventsRequest()
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
subscribeEventsFunc: func(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
delegate := &recordingGatewayService{
|
||||
subscribeEventsFunc: func(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
envelope, ok := parsedEnvelopeFromContext(stream.Context())
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, want.GetRequestId(), envelope.RequestID)
|
||||
@@ -357,25 +357,25 @@ func TestEnvelopeValidatingServiceSubscribeEventsAttachesParsedEnvelope(t *testi
|
||||
assert.Equal(t, 1, delegate.subscribeCalls)
|
||||
}
|
||||
|
||||
type recordingEdgeGatewayService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
type recordingGatewayService struct {
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
executeCalls int
|
||||
subscribeCalls int
|
||||
executeCommandFunc func(context.Context, *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error)
|
||||
subscribeEventsFunc func(*gatewayv1.SubscribeEventsRequest, grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error
|
||||
executeCommandFunc func(context.Context, *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error)
|
||||
subscribeEventsFunc func(*edgev1.SubscribeEventsRequest, grpc.ServerStreamingServer[edgev1.GatewayEvent]) error
|
||||
}
|
||||
|
||||
func (s *recordingEdgeGatewayService) ExecuteCommand(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
func (s *recordingGatewayService) ExecuteCommand(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
s.executeCalls++
|
||||
if s.executeCommandFunc != nil {
|
||||
return s.executeCommandFunc(ctx, req)
|
||||
}
|
||||
|
||||
return &gatewayv1.ExecuteCommandResponse{}, nil
|
||||
return &edgev1.ExecuteCommandResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *recordingEdgeGatewayService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s *recordingGatewayService) SubscribeEvents(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
s.subscribeCalls++
|
||||
if s.subscribeEventsFunc != nil {
|
||||
return s.subscribeEventsFunc(req, stream)
|
||||
@@ -389,7 +389,7 @@ type stubGatewayEventStream struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (s stubGatewayEventStream) Send(*gatewayv1.GatewayEvent) error {
|
||||
func (s stubGatewayEventStream) Send(*edgev1.GatewayEvent) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"galaxy/gateway/internal/clock"
|
||||
"galaxy/gateway/internal/replay"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -19,9 +19,9 @@ const minimumReplayReservationTTL = time.Millisecond
|
||||
// freshnessAndReplayService applies freshness and anti-replay checks after
|
||||
// client-signature verification and before later policy or routing steps run.
|
||||
type freshnessAndReplayService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
delegate gatewayv1.EdgeGatewayServer
|
||||
delegate edgev1.GatewayServer
|
||||
clock clock.Clock
|
||||
replayStore replay.Store
|
||||
freshnessWindow time.Duration
|
||||
@@ -29,7 +29,7 @@ type freshnessAndReplayService struct {
|
||||
|
||||
// ExecuteCommand verifies request freshness and replay protection before
|
||||
// delegating to the configured service implementation.
|
||||
func (s freshnessAndReplayService) ExecuteCommand(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
func (s freshnessAndReplayService) ExecuteCommand(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
if err := s.verifyFreshnessAndReplay(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func (s freshnessAndReplayService) ExecuteCommand(ctx context.Context, req *gate
|
||||
|
||||
// SubscribeEvents verifies request freshness and replay protection before
|
||||
// delegating to the configured service implementation.
|
||||
func (s freshnessAndReplayService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s freshnessAndReplayService) SubscribeEvents(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
if err := s.verifyFreshnessAndReplay(stream.Context()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func (s freshnessAndReplayService) SubscribeEvents(req *gatewayv1.SubscribeEvent
|
||||
|
||||
// newFreshnessAndReplayService wraps delegate with the freshness and replay
|
||||
// gate.
|
||||
func newFreshnessAndReplayService(delegate gatewayv1.EdgeGatewayServer, clk clock.Clock, replayStore replay.Store, freshnessWindow time.Duration) gatewayv1.EdgeGatewayServer {
|
||||
func newFreshnessAndReplayService(delegate edgev1.GatewayServer, clk clock.Clock, replayStore replay.Store, freshnessWindow time.Duration) edgev1.GatewayServer {
|
||||
return freshnessAndReplayService{
|
||||
delegate: delegate,
|
||||
clock: clk,
|
||||
@@ -92,4 +92,4 @@ func (unavailableReplayStore) Reserve(context.Context, string, string, time.Dura
|
||||
return errors.New("replay store is unavailable")
|
||||
}
|
||||
|
||||
var _ gatewayv1.EdgeGatewayServer = freshnessAndReplayService{}
|
||||
var _ edgev1.GatewayServer = freshnessAndReplayService{}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"galaxy/gateway/internal/replay"
|
||||
"galaxy/gateway/internal/session"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -40,7 +40,7 @@ func TestExecuteCommandRejectsStaleTimestamp(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -82,7 +82,7 @@ func TestSubscribeEventsRejectsStaleTimestamp(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -104,7 +104,7 @@ func TestSubscribeEventsRejectsStaleTimestamp(t *testing.T) {
|
||||
func TestExecuteCommandRejectsReplay(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -131,7 +131,7 @@ func TestExecuteCommandRejectsReplay(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsReplay(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -162,9 +162,9 @@ func TestSubscribeEventsRejectsReplay(t *testing.T) {
|
||||
func TestExecuteCommandAllowsSameRequestIDAcrossDistinctSessions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
return &gatewayv1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
delegate := &recordingGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
return &edgev1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -196,8 +196,8 @@ func TestExecuteCommandAllowsSameRequestIDAcrossDistinctSessions(t *testing.T) {
|
||||
func TestSubscribeEventsAllowsSameRequestIDAcrossDistinctSessions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
subscribeEventsFunc: func(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
delegate := &recordingGatewayService{
|
||||
subscribeEventsFunc: func(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -238,7 +238,7 @@ func TestSubscribeEventsAllowsSameRequestIDAcrossDistinctSessions(t *testing.T)
|
||||
func TestExecuteCommandRejectsReplayStoreUnavailable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -262,7 +262,7 @@ func TestExecuteCommandRejectsReplayStoreUnavailable(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsReplayStoreUnavailable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -286,9 +286,9 @@ func TestSubscribeEventsRejectsReplayStoreUnavailable(t *testing.T) {
|
||||
func TestExecuteCommandFreshRequestReachesDelegateAndUsesDynamicReplayTTL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
return &gatewayv1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
delegate := &recordingGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
return &edgev1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -324,8 +324,8 @@ func TestExecuteCommandFreshRequestReachesDelegateAndUsesDynamicReplayTTL(t *tes
|
||||
func TestSubscribeEventsFreshRequestReachesDelegateAndUsesDynamicReplayTTL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
subscribeEventsFunc: func(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
delegate := &recordingGatewayService{
|
||||
subscribeEventsFunc: func(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -361,9 +361,9 @@ func TestSubscribeEventsFreshRequestReachesDelegateAndUsesDynamicReplayTTL(t *te
|
||||
func TestExecuteCommandFutureSkewUsesExtendedReplayTTL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
return &gatewayv1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
delegate := &recordingGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
return &edgev1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -395,9 +395,9 @@ func TestExecuteCommandFutureSkewUsesExtendedReplayTTL(t *testing.T) {
|
||||
func TestExecuteCommandBoundaryFreshnessUsesMinimumReplayTTL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
return &gatewayv1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
delegate := &recordingGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
return &edgev1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"galaxy/gateway/internal/logging"
|
||||
"galaxy/gateway/internal/telemetry"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.uber.org/zap"
|
||||
@@ -78,9 +78,9 @@ func recordEdgeRequest(logger *zap.Logger, metrics *telemetry.Runtime, ctx conte
|
||||
|
||||
func envelopeFieldsFromRequest(req any) (messageType string, requestID string, traceID string) {
|
||||
switch typed := req.(type) {
|
||||
case *gatewayv1.ExecuteCommandRequest:
|
||||
case *edgev1.ExecuteCommandRequest:
|
||||
return typed.GetMessageType(), typed.GetRequestId(), typed.GetTraceId()
|
||||
case *gatewayv1.SubscribeEventsRequest:
|
||||
case *edgev1.SubscribeEventsRequest:
|
||||
return typed.GetMessageType(), typed.GetRequestId(), typed.GetTraceId()
|
||||
default:
|
||||
return "", "", ""
|
||||
@@ -88,7 +88,7 @@ func envelopeFieldsFromRequest(req any) (messageType string, requestID string, t
|
||||
}
|
||||
|
||||
func resultCodeFromResponse(resp any) string {
|
||||
typed, ok := resp.(*gatewayv1.ExecuteCommandResponse)
|
||||
typed, ok := resp.(*edgev1.ExecuteCommandResponse)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"galaxy/gateway/authn"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -15,14 +15,14 @@ import (
|
||||
// payloadHashVerifyingService applies payload-hash verification after session
|
||||
// lookup and before any later auth or routing step runs.
|
||||
type payloadHashVerifyingService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
delegate gatewayv1.EdgeGatewayServer
|
||||
delegate edgev1.GatewayServer
|
||||
}
|
||||
|
||||
// ExecuteCommand verifies req payload integrity before delegating to the
|
||||
// configured service implementation.
|
||||
func (s payloadHashVerifyingService) ExecuteCommand(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
func (s payloadHashVerifyingService) ExecuteCommand(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
if err := verifyPayloadHash(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func (s payloadHashVerifyingService) ExecuteCommand(ctx context.Context, req *ga
|
||||
|
||||
// SubscribeEvents verifies req payload integrity before delegating to the
|
||||
// configured service implementation.
|
||||
func (s payloadHashVerifyingService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s payloadHashVerifyingService) SubscribeEvents(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
if err := verifyPayloadHash(stream.Context()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func (s payloadHashVerifyingService) SubscribeEvents(req *gatewayv1.SubscribeEve
|
||||
|
||||
// newPayloadHashVerifyingService wraps delegate with the payload-hash
|
||||
// verification gate.
|
||||
func newPayloadHashVerifyingService(delegate gatewayv1.EdgeGatewayServer) gatewayv1.EdgeGatewayServer {
|
||||
func newPayloadHashVerifyingService(delegate edgev1.GatewayServer) edgev1.GatewayServer {
|
||||
return payloadHashVerifyingService{delegate: delegate}
|
||||
}
|
||||
|
||||
@@ -63,4 +63,4 @@ func verifyPayloadHash(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
var _ gatewayv1.EdgeGatewayServer = payloadHashVerifyingService{}
|
||||
var _ edgev1.GatewayServer = payloadHashVerifyingService{}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
func TestExecuteCommandRejectsPayloadHashWithInvalidLength(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -38,7 +38,7 @@ func TestExecuteCommandRejectsPayloadHashWithInvalidLength(t *testing.T) {
|
||||
func TestExecuteCommandRejectsPayloadHashMismatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -62,7 +62,7 @@ func TestExecuteCommandRejectsPayloadHashMismatch(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsPayloadHashWithInvalidLength(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -85,7 +85,7 @@ func TestSubscribeEventsRejectsPayloadHashWithInvalidLength(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsPayloadHashMismatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"galaxy/gateway/internal/logging"
|
||||
"galaxy/gateway/internal/push"
|
||||
"galaxy/gateway/internal/telemetry"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// NewFanOutPushStreamService constructs the authenticated SubscribeEvents tail
|
||||
// service that registers active streams in hub and forwards client-facing
|
||||
// events after the bootstrap event has been sent.
|
||||
func NewFanOutPushStreamService(hub *push.Hub, responseSigner authn.ResponseSigner, clk clock.Clock, logger *zap.Logger) gatewayv1.EdgeGatewayServer {
|
||||
func NewFanOutPushStreamService(hub *push.Hub, responseSigner authn.ResponseSigner, clk clock.Clock, logger *zap.Logger) edgev1.GatewayServer {
|
||||
if responseSigner == nil {
|
||||
responseSigner = unavailableResponseSigner{}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func NewFanOutPushStreamService(hub *push.Hub, responseSigner authn.ResponseSign
|
||||
// fanOutPushStreamService owns the post-bootstrap authenticated push-stream
|
||||
// lifecycle backed by the in-memory push hub.
|
||||
type fanOutPushStreamService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
hub *push.Hub
|
||||
responseSigner authn.ResponseSigner
|
||||
@@ -54,7 +54,7 @@ type fanOutPushStreamService struct {
|
||||
|
||||
// SubscribeEvents registers the verified stream in the push hub and forwards
|
||||
// matching client-facing events until the stream ends.
|
||||
func (s fanOutPushStreamService) SubscribeEvents(_ *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s fanOutPushStreamService) SubscribeEvents(_ *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
binding, ok := authenticatedStreamBindingFromContext(stream.Context())
|
||||
if !ok {
|
||||
return status.Error(codes.Internal, "authenticated request context is incomplete")
|
||||
@@ -109,7 +109,7 @@ func (s fanOutPushStreamService) SubscribeEvents(_ *gatewayv1.SubscribeEventsReq
|
||||
}
|
||||
}
|
||||
|
||||
func (s fanOutPushStreamService) buildGatewayEvent(event push.Event) (*gatewayv1.GatewayEvent, error) {
|
||||
func (s fanOutPushStreamService) buildGatewayEvent(event push.Event) (*edgev1.GatewayEvent, error) {
|
||||
timestampMS := s.clock.Now().UTC().UnixMilli()
|
||||
payloadHash := sha256.Sum256(event.PayloadBytes)
|
||||
|
||||
@@ -125,7 +125,7 @@ func (s fanOutPushStreamService) buildGatewayEvent(event push.Event) (*gatewayv1
|
||||
return nil, status.Error(codes.Unavailable, "response signer is unavailable")
|
||||
}
|
||||
|
||||
return &gatewayv1.GatewayEvent{
|
||||
return &edgev1.GatewayEvent{
|
||||
EventType: event.EventType,
|
||||
EventId: event.EventID,
|
||||
TimestampMs: timestampMS,
|
||||
@@ -169,4 +169,4 @@ func mapSubscriptionOutcome(err error) telemetry.EdgeOutcome {
|
||||
}
|
||||
}
|
||||
|
||||
var _ gatewayv1.EdgeGatewayServer = fanOutPushStreamService{}
|
||||
var _ edgev1.GatewayServer = fanOutPushStreamService{}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"galaxy/gateway/internal/telemetry"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"google.golang.org/grpc"
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
// 15-second default a fully-idle stream costs ~840 KB/day per client;
|
||||
// see `docs/ARCHITECTURE.md` for the per-scale projection.
|
||||
type heartbeatingStream struct {
|
||||
grpc.ServerStreamingServer[gatewayv1.GatewayEvent]
|
||||
grpc.ServerStreamingServer[edgev1.GatewayEvent]
|
||||
|
||||
interval time.Duration
|
||||
metrics *telemetry.Runtime
|
||||
@@ -43,7 +43,7 @@ type heartbeatingStream struct {
|
||||
// the wrapping entirely; non-nil returns must have `Stop()` called once
|
||||
// the stream lifecycle ends.
|
||||
func newHeartbeatingStream(
|
||||
inner grpc.ServerStreamingServer[gatewayv1.GatewayEvent],
|
||||
inner grpc.ServerStreamingServer[edgev1.GatewayEvent],
|
||||
interval time.Duration,
|
||||
metrics *telemetry.Runtime,
|
||||
) *heartbeatingStream {
|
||||
@@ -64,7 +64,7 @@ func newHeartbeatingStream(
|
||||
// so the heartbeat goroutine waits a fresh interval before firing
|
||||
// again. A Send that succeeds means the transport just delivered real
|
||||
// bytes; the silence window restarts from "now".
|
||||
func (s *heartbeatingStream) Send(event *gatewayv1.GatewayEvent) error {
|
||||
func (s *heartbeatingStream) Send(event *edgev1.GatewayEvent) error {
|
||||
s.sendMu.Lock()
|
||||
defer s.sendMu.Unlock()
|
||||
if err := s.ServerStreamingServer.Send(event); err != nil {
|
||||
@@ -158,6 +158,6 @@ func (s *heartbeatingStream) resetTimerLocked() {
|
||||
// EventType is left at its proto3 default so the wire frame stays as
|
||||
// small as Connect framing allows. See `gatewayHeartbeatEventType` for
|
||||
// the security rationale of leaving the event unsigned.
|
||||
func buildHeartbeatEvent() *gatewayv1.GatewayEvent {
|
||||
return &gatewayv1.GatewayEvent{EventType: gatewayHeartbeatEventType}
|
||||
func buildHeartbeatEvent() *edgev1.GatewayEvent {
|
||||
return &edgev1.GatewayEvent{EventType: gatewayHeartbeatEventType}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -66,7 +66,7 @@ func TestHeartbeatingStreamRealSendResetsSilenceTimer(t *testing.T) {
|
||||
defer ticker.Stop()
|
||||
for range 6 {
|
||||
<-ticker.C
|
||||
if err := hb.Send(&gatewayv1.GatewayEvent{EventType: "real.event"}); err != nil {
|
||||
if err := hb.Send(&edgev1.GatewayEvent{EventType: "real.event"}); err != nil {
|
||||
t.Errorf("real Send failed: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -134,16 +134,16 @@ func TestHeartbeatingStreamSendErrorPropagates(t *testing.T) {
|
||||
require.NotNil(t, hb)
|
||||
defer hb.Stop()
|
||||
|
||||
err := hb.Send(&gatewayv1.GatewayEvent{EventType: "real.event"})
|
||||
err := hb.Send(&edgev1.GatewayEvent{EventType: "real.event"})
|
||||
require.ErrorIs(t, err, wantErr)
|
||||
}
|
||||
|
||||
// capturingStream is a minimal grpc.ServerStreamingServer that pushes
|
||||
// every Send into a channel so tests can assert on the wire frame.
|
||||
type capturingStream struct {
|
||||
grpc.ServerStreamingServer[gatewayv1.GatewayEvent]
|
||||
grpc.ServerStreamingServer[edgev1.GatewayEvent]
|
||||
|
||||
events chan *gatewayv1.GatewayEvent
|
||||
events chan *edgev1.GatewayEvent
|
||||
sendErr atomic.Pointer[errorBox]
|
||||
}
|
||||
|
||||
@@ -152,10 +152,10 @@ type errorBox struct{ err error }
|
||||
func newCapturingStream(t *testing.T) *capturingStream {
|
||||
t.Helper()
|
||||
|
||||
return &capturingStream{events: make(chan *gatewayv1.GatewayEvent, 16)}
|
||||
return &capturingStream{events: make(chan *edgev1.GatewayEvent, 16)}
|
||||
}
|
||||
|
||||
func (s *capturingStream) Send(event *gatewayv1.GatewayEvent) error {
|
||||
func (s *capturingStream) Send(event *edgev1.GatewayEvent) error {
|
||||
if box := s.sendErr.Load(); box != nil {
|
||||
return box.err
|
||||
}
|
||||
@@ -172,7 +172,7 @@ func (s *capturingStream) SetTrailer(metadata.MD) {}
|
||||
func (s *capturingStream) SendMsg(any) error { return errors.New("capturingStream.SendMsg: unused") }
|
||||
func (s *capturingStream) RecvMsg(any) error { return errors.New("capturingStream.RecvMsg: unused") }
|
||||
|
||||
func (s *capturingStream) recv(t *testing.T, timeout time.Duration) *gatewayv1.GatewayEvent {
|
||||
func (s *capturingStream) recv(t *testing.T, timeout time.Duration) *edgev1.GatewayEvent {
|
||||
t.Helper()
|
||||
|
||||
select {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"galaxy/gateway/authn"
|
||||
"galaxy/gateway/internal/clock"
|
||||
"galaxy/gateway/internal/telemetry"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
gatewayfbs "galaxy/schema/fbs/gateway"
|
||||
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
@@ -81,9 +81,9 @@ func authenticatedStreamBindingFromContext(ctx context.Context) (authenticatedSt
|
||||
// the tail performs and only emits a heartbeat when the silence window
|
||||
// elapses; tails remain heartbeat-unaware.
|
||||
type authenticatedPushStreamService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
tailDelegate gatewayv1.EdgeGatewayServer
|
||||
tailDelegate edgev1.GatewayServer
|
||||
responseSigner authn.ResponseSigner
|
||||
clock clock.Clock
|
||||
heartbeatInterval time.Duration
|
||||
@@ -92,7 +92,7 @@ type authenticatedPushStreamService struct {
|
||||
|
||||
// SubscribeEvents binds the verified stream identity, sends the initial signed
|
||||
// server-time event, and then delegates the remaining lifecycle.
|
||||
func (s authenticatedPushStreamService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s authenticatedPushStreamService) SubscribeEvents(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
envelope, ok := parsedEnvelopeFromContext(stream.Context())
|
||||
if !ok {
|
||||
return status.Error(codes.Internal, "authenticated request context is incomplete")
|
||||
@@ -134,7 +134,7 @@ func (s authenticatedPushStreamService) SubscribeEvents(req *gatewayv1.Subscribe
|
||||
return status.Error(codes.Unavailable, "response signer is unavailable")
|
||||
}
|
||||
|
||||
if err := boundStream.Send(&gatewayv1.GatewayEvent{
|
||||
if err := boundStream.Send(&edgev1.GatewayEvent{
|
||||
EventType: serverTimeEventType,
|
||||
EventId: envelope.RequestID,
|
||||
TimestampMs: serverTimeMS,
|
||||
@@ -147,7 +147,7 @@ func (s authenticatedPushStreamService) SubscribeEvents(req *gatewayv1.Subscribe
|
||||
return err
|
||||
}
|
||||
|
||||
var streamForTail grpc.ServerStreamingServer[gatewayv1.GatewayEvent] = boundStream
|
||||
var streamForTail grpc.ServerStreamingServer[edgev1.GatewayEvent] = boundStream
|
||||
if hbStream := newHeartbeatingStream(boundStream, s.heartbeatInterval, s.metrics); hbStream != nil {
|
||||
defer hbStream.Stop()
|
||||
go func() {
|
||||
@@ -165,12 +165,12 @@ func (s authenticatedPushStreamService) SubscribeEvents(req *gatewayv1.Subscribe
|
||||
}
|
||||
|
||||
func newAuthenticatedPushStreamService(
|
||||
tailDelegate gatewayv1.EdgeGatewayServer,
|
||||
tailDelegate edgev1.GatewayServer,
|
||||
responseSigner authn.ResponseSigner,
|
||||
clk clock.Clock,
|
||||
heartbeatInterval time.Duration,
|
||||
metrics *telemetry.Runtime,
|
||||
) gatewayv1.EdgeGatewayServer {
|
||||
) edgev1.GatewayServer {
|
||||
if tailDelegate == nil {
|
||||
tailDelegate = holdOpenSubscribeEventsService{}
|
||||
}
|
||||
@@ -197,7 +197,7 @@ func buildServerTimeEventPayload(serverTimeMS int64) []byte {
|
||||
type authenticatedStreamBindingContextKey struct{}
|
||||
|
||||
type authenticatedStreamContextStream struct {
|
||||
grpc.ServerStreamingServer[gatewayv1.GatewayEvent]
|
||||
grpc.ServerStreamingServer[edgev1.GatewayEvent]
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
@@ -210,12 +210,12 @@ func (s authenticatedStreamContextStream) Context() context.Context {
|
||||
}
|
||||
|
||||
type holdOpenSubscribeEventsService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
}
|
||||
|
||||
func (holdOpenSubscribeEventsService) SubscribeEvents(_ *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (holdOpenSubscribeEventsService) SubscribeEvents(_ *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
<-stream.Context().Done()
|
||||
return stream.Context().Err()
|
||||
}
|
||||
|
||||
var _ gatewayv1.EdgeGatewayServer = authenticatedPushStreamService{}
|
||||
var _ edgev1.GatewayServer = authenticatedPushStreamService{}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"galaxy/gateway/internal/config"
|
||||
"galaxy/gateway/internal/ratelimit"
|
||||
"galaxy/gateway/internal/session"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -102,9 +102,9 @@ type AuthenticatedRequestPolicy interface {
|
||||
}
|
||||
|
||||
type authenticatedRateLimitService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
delegate gatewayv1.EdgeGatewayServer
|
||||
delegate edgev1.GatewayServer
|
||||
limiter AuthenticatedRequestLimiter
|
||||
policy AuthenticatedRequestPolicy
|
||||
cfg config.AuthenticatedGRPCAntiAbuseConfig
|
||||
@@ -112,7 +112,7 @@ type authenticatedRateLimitService struct {
|
||||
|
||||
// ExecuteCommand applies authenticated rate limits and edge policy before
|
||||
// delegating to the configured service implementation.
|
||||
func (s authenticatedRateLimitService) ExecuteCommand(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
func (s authenticatedRateLimitService) ExecuteCommand(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
if err := s.applyRateLimitsAndPolicy(ctx, authenticatedRPCExecuteCommand); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func (s authenticatedRateLimitService) ExecuteCommand(ctx context.Context, req *
|
||||
|
||||
// SubscribeEvents applies authenticated rate limits and edge policy before
|
||||
// delegating to the configured service implementation.
|
||||
func (s authenticatedRateLimitService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s authenticatedRateLimitService) SubscribeEvents(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
if err := s.applyRateLimitsAndPolicy(stream.Context(), authenticatedRPCSubscribeEvents); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func (s authenticatedRateLimitService) SubscribeEvents(req *gatewayv1.SubscribeE
|
||||
|
||||
// newAuthenticatedRateLimitService wraps delegate with the authenticated
|
||||
// rate-limit and edge-policy gate.
|
||||
func newAuthenticatedRateLimitService(delegate gatewayv1.EdgeGatewayServer, limiter AuthenticatedRequestLimiter, policy AuthenticatedRequestPolicy, cfg config.AuthenticatedGRPCAntiAbuseConfig) gatewayv1.EdgeGatewayServer {
|
||||
func newAuthenticatedRateLimitService(delegate edgev1.GatewayServer, limiter AuthenticatedRequestLimiter, policy AuthenticatedRequestPolicy, cfg config.AuthenticatedGRPCAntiAbuseConfig) edgev1.GatewayServer {
|
||||
return authenticatedRateLimitService{
|
||||
delegate: delegate,
|
||||
limiter: limiter,
|
||||
@@ -279,4 +279,4 @@ func (noopAuthenticatedRequestPolicy) Evaluate(context.Context, AuthenticatedReq
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ gatewayv1.EdgeGatewayServer = authenticatedRateLimitService{}
|
||||
var _ edgev1.GatewayServer = authenticatedRateLimitService{}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"galaxy/gateway/internal/ratelimit"
|
||||
"galaxy/gateway/internal/restapi"
|
||||
"galaxy/gateway/internal/session"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
func TestExecuteCommandRateLimitsByIP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGatewayWithGRPCConfig(t, newAuthenticatedGRPCConfigForTest(func(cfg *config.AuthenticatedGRPCConfig) {
|
||||
cfg.AntiAbuse.IP = config.AuthenticatedRateLimitConfig{
|
||||
Requests: 1,
|
||||
@@ -54,7 +54,7 @@ func TestExecuteCommandRateLimitsByIP(t *testing.T) {
|
||||
func TestExecuteCommandRateLimitsBySession(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGatewayWithGRPCConfig(t, newAuthenticatedGRPCConfigForTest(func(cfg *config.AuthenticatedGRPCConfig) {
|
||||
cfg.AntiAbuse.Session = config.AuthenticatedRateLimitConfig{
|
||||
Requests: 1,
|
||||
@@ -87,7 +87,7 @@ func TestExecuteCommandRateLimitsBySession(t *testing.T) {
|
||||
func TestExecuteCommandRateLimitsByUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGatewayWithGRPCConfig(t, newAuthenticatedGRPCConfigForTest(func(cfg *config.AuthenticatedGRPCConfig) {
|
||||
cfg.AntiAbuse.User = config.AuthenticatedRateLimitConfig{
|
||||
Requests: 1,
|
||||
@@ -124,7 +124,7 @@ func TestExecuteCommandRateLimitsByUser(t *testing.T) {
|
||||
func TestExecuteCommandRateLimitsByMessageClass(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGatewayWithGRPCConfig(t, newAuthenticatedGRPCConfigForTest(func(cfg *config.AuthenticatedGRPCConfig) {
|
||||
cfg.AntiAbuse.MessageClass = config.AuthenticatedRateLimitConfig{
|
||||
Requests: 1,
|
||||
@@ -161,7 +161,7 @@ func TestAuthenticatedPolicyHookReceivesVerifiedRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
policy := &recordingAuthenticatedRequestPolicy{}
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: userMappedSessionCache(map[string]string{"device-session-123": "user-123"}),
|
||||
@@ -189,7 +189,7 @@ func TestAuthenticatedPolicyHookReceivesVerifiedRequest(t *testing.T) {
|
||||
func TestExecuteCommandPolicyRejectMapsToPermissionDenied(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: userMappedSessionCache(map[string]string{"device-session-123": "user-123"}),
|
||||
@@ -212,7 +212,7 @@ func TestExecuteCommandPolicyRejectMapsToPermissionDenied(t *testing.T) {
|
||||
func TestSubscribeEventsRateLimitRejectsStream(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGatewayWithGRPCConfig(t, newAuthenticatedGRPCConfigForTest(func(cfg *config.AuthenticatedGRPCConfig) {
|
||||
cfg.AntiAbuse.IP = config.AuthenticatedRateLimitConfig{
|
||||
Requests: 1,
|
||||
@@ -274,7 +274,7 @@ func TestAuthenticatedRateLimitsStayIsolatedFromPublicREST(t *testing.T) {
|
||||
AuthService: staticAuthServiceClient{},
|
||||
Limiter: publicLimiterAdapter{limiter: sharedLimiter},
|
||||
})
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
grpcServer := NewServer(grpcCfg, ServerDependencies{
|
||||
Service: delegate,
|
||||
Router: executeCommandAdapterRouter{service: delegate},
|
||||
@@ -342,7 +342,7 @@ func newAuthenticatedGRPCConfigForTest(mutate func(*config.AuthenticatedGRPCConf
|
||||
return cfg
|
||||
}
|
||||
|
||||
func newValidExecuteCommandRequestWithMessageType(deviceSessionID string, requestID string, messageType string) *gatewayv1.ExecuteCommandRequest {
|
||||
func newValidExecuteCommandRequestWithMessageType(deviceSessionID string, requestID string, messageType string) *edgev1.ExecuteCommandRequest {
|
||||
req := newValidExecuteCommandRequestWithSessionAndRequestID(deviceSessionID, requestID)
|
||||
req.MessageType = messageType
|
||||
req.Signature = signRequest(
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
"galaxy/gateway/internal/replay"
|
||||
"galaxy/gateway/internal/session"
|
||||
"galaxy/gateway/internal/telemetry"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
"galaxy/gateway/proto/galaxy/gateway/v1/gatewayv1connect"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
"galaxy/gateway/proto/edge/v1/edgev1connect"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
@@ -42,7 +42,7 @@ type ServerDependencies struct {
|
||||
// after the initial authenticated service event has been sent. When nil, the
|
||||
// gateway keeps authenticated SubscribeEvents streams open until the client
|
||||
// cancels them, the server shuts down, or a later stream send fails.
|
||||
Service gatewayv1.EdgeGatewayServer
|
||||
Service edgev1.GatewayServer
|
||||
|
||||
// Router resolves the exact downstream unary client for the verified
|
||||
// message_type value. When nil, the authenticated unary surface uses an
|
||||
@@ -93,7 +93,7 @@ type ServerDependencies struct {
|
||||
// single net/http listener.
|
||||
type Server struct {
|
||||
cfg config.AuthenticatedGRPCConfig
|
||||
service gatewayv1.EdgeGatewayServer
|
||||
service edgev1.GatewayServer
|
||||
logger *zap.Logger
|
||||
pushHub *push.Hub
|
||||
metrics *telemetry.Runtime
|
||||
@@ -169,7 +169,7 @@ func (s *Server) Run(ctx context.Context) error {
|
||||
|
||||
mux := http.NewServeMux()
|
||||
connectHandler := newConnectEdgeAdapter(s.service)
|
||||
path, handler := gatewayv1connect.NewEdgeGatewayHandler(
|
||||
path, handler := edgev1connect.NewGatewayHandler(
|
||||
connectHandler,
|
||||
connect.WithInterceptors(observabilityConnectInterceptor(s.logger, s.metrics)),
|
||||
)
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"galaxy/gateway/internal/app"
|
||||
"galaxy/gateway/internal/config"
|
||||
"galaxy/gateway/internal/session"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
"galaxy/gateway/proto/galaxy/gateway/v1/gatewayv1connect"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
"galaxy/gateway/proto/edge/v1/edgev1connect"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -30,7 +30,7 @@ func TestExecuteCommandRejectsMalformedEnvelope(t *testing.T) {
|
||||
addr := waitForListenAddr(t, server)
|
||||
client := newEdgeClient(t, addr)
|
||||
|
||||
_, err := client.ExecuteCommand(context.Background(), connect.NewRequest(&gatewayv1.ExecuteCommandRequest{}))
|
||||
_, err := client.ExecuteCommand(context.Background(), connect.NewRequest(&edgev1.ExecuteCommandRequest{}))
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func TestSubscribeEventsRejectsMalformedEnvelope(t *testing.T) {
|
||||
addr := waitForListenAddr(t, server)
|
||||
client := newEdgeClient(t, addr)
|
||||
|
||||
err := subscribeEventsError(t, context.Background(), client, &gatewayv1.SubscribeEventsRequest{})
|
||||
err := subscribeEventsError(t, context.Background(), client, &edgev1.SubscribeEventsRequest{})
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func TestExecuteCommandRejectsUnsupportedProtocolVersion(t *testing.T) {
|
||||
addr := waitForListenAddr(t, server)
|
||||
client := newEdgeClient(t, addr)
|
||||
|
||||
_, err := client.ExecuteCommand(context.Background(), connect.NewRequest(&gatewayv1.ExecuteCommandRequest{
|
||||
_, err := client.ExecuteCommand(context.Background(), connect.NewRequest(&edgev1.ExecuteCommandRequest{
|
||||
ProtocolVersion: "v2",
|
||||
DeviceSessionId: "device-session-123",
|
||||
MessageType: "fleet.move",
|
||||
@@ -418,7 +418,7 @@ func waitForListenAddr(t *testing.T, server *Server) string {
|
||||
// authenticated edge listener. AllowHTTP forces the client to issue plain
|
||||
// HTTP/2 requests (h2c) instead of attempting TLS, which the gateway's
|
||||
// in-process test bootstrap does not configure.
|
||||
func newEdgeClient(t *testing.T, addr string) gatewayv1connect.EdgeGatewayClient {
|
||||
func newEdgeClient(t *testing.T, addr string) edgev1connect.GatewayClient {
|
||||
t.Helper()
|
||||
|
||||
httpClient := &http.Client{
|
||||
@@ -429,7 +429,7 @@ func newEdgeClient(t *testing.T, addr string) gatewayv1connect.EdgeGatewayClient
|
||||
},
|
||||
},
|
||||
}
|
||||
return gatewayv1connect.NewEdgeGatewayClient(httpClient, "http://"+addr)
|
||||
return edgev1connect.NewGatewayClient(httpClient, "http://"+addr)
|
||||
}
|
||||
|
||||
// connectErrorMessage extracts the *connect.Error message from err. It
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"galaxy/gateway/internal/session"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -30,15 +30,15 @@ func resolvedSessionFromContext(ctx context.Context) (session.Record, bool) {
|
||||
// sessionLookupService resolves the authenticated session from SessionCache
|
||||
// after envelope parsing succeeds and before later auth steps run.
|
||||
type sessionLookupService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
delegate gatewayv1.EdgeGatewayServer
|
||||
delegate edgev1.GatewayServer
|
||||
cache session.Cache
|
||||
}
|
||||
|
||||
// ExecuteCommand resolves the cached session for req and only then forwards it
|
||||
// to the configured delegate with the resolved session attached to ctx.
|
||||
func (s sessionLookupService) ExecuteCommand(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
func (s sessionLookupService) ExecuteCommand(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
record, err := s.lookupSession(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -50,7 +50,7 @@ func (s sessionLookupService) ExecuteCommand(ctx context.Context, req *gatewayv1
|
||||
// SubscribeEvents resolves the cached session for req and only then forwards it
|
||||
// to the configured delegate with the resolved session attached to the stream
|
||||
// context.
|
||||
func (s sessionLookupService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s sessionLookupService) SubscribeEvents(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
record, err := s.lookupSession(stream.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -63,7 +63,7 @@ func (s sessionLookupService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequ
|
||||
}
|
||||
|
||||
// newSessionLookupService wraps delegate with the session-cache lookup gate.
|
||||
func newSessionLookupService(delegate gatewayv1.EdgeGatewayServer, cache session.Cache) gatewayv1.EdgeGatewayServer {
|
||||
func newSessionLookupService(delegate edgev1.GatewayServer, cache session.Cache) edgev1.GatewayServer {
|
||||
return sessionLookupService{
|
||||
delegate: delegate,
|
||||
cache: cache,
|
||||
@@ -105,7 +105,7 @@ func cloneSessionRecord(record session.Record) session.Record {
|
||||
type resolvedSessionContextKey struct{}
|
||||
|
||||
type resolvedSessionContextStream struct {
|
||||
grpc.ServerStreamingServer[gatewayv1.GatewayEvent]
|
||||
grpc.ServerStreamingServer[edgev1.GatewayEvent]
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
@@ -126,4 +126,4 @@ func (unavailableSessionCache) Lookup(context.Context, string) (session.Record,
|
||||
func (unavailableSessionCache) MarkRevoked(string) {}
|
||||
func (unavailableSessionCache) MarkAllRevokedForUser(string) {}
|
||||
|
||||
var _ gatewayv1.EdgeGatewayServer = sessionLookupService{}
|
||||
var _ edgev1.GatewayServer = sessionLookupService{}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"galaxy/gateway/internal/session"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
func TestExecuteCommandRejectsUnknownSession(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{
|
||||
@@ -40,7 +40,7 @@ func TestExecuteCommandRejectsUnknownSession(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsUnknownSession(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{
|
||||
@@ -63,7 +63,7 @@ func TestSubscribeEventsRejectsUnknownSession(t *testing.T) {
|
||||
func TestExecuteCommandRejectsRevokedSession(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newRevokedSessionRecord(), nil }},
|
||||
@@ -82,7 +82,7 @@ func TestExecuteCommandRejectsRevokedSession(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsRevokedSession(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newRevokedSessionRecord(), nil }},
|
||||
@@ -101,7 +101,7 @@ func TestSubscribeEventsRejectsRevokedSession(t *testing.T) {
|
||||
func TestExecuteCommandRejectsSessionCacheUnavailable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{
|
||||
@@ -124,7 +124,7 @@ func TestExecuteCommandRejectsSessionCacheUnavailable(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsSessionCacheUnavailable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{
|
||||
@@ -147,12 +147,12 @@ func TestSubscribeEventsRejectsSessionCacheUnavailable(t *testing.T) {
|
||||
func TestExecuteCommandAttachesResolvedSession(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
delegate := &recordingGatewayService{
|
||||
executeCommandFunc: func(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
record, ok := resolvedSessionFromContext(ctx)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, newActiveSessionRecord(), record)
|
||||
return &gatewayv1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
return &edgev1.ExecuteCommandResponse{RequestId: req.GetRequestId()}, nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -173,8 +173,8 @@ func TestExecuteCommandAttachesResolvedSession(t *testing.T) {
|
||||
func TestSubscribeEventsAttachesResolvedSession(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
subscribeEventsFunc: func(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
delegate := &recordingGatewayService{
|
||||
subscribeEventsFunc: func(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
record, ok := resolvedSessionFromContext(stream.Context())
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, newActiveSessionRecord(), record)
|
||||
@@ -204,8 +204,8 @@ func TestSubscribeEventsAttachesResolvedSession(t *testing.T) {
|
||||
func TestSubscribeEventsAttachesAuthenticatedStreamBinding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{
|
||||
subscribeEventsFunc: func(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
delegate := &recordingGatewayService{
|
||||
subscribeEventsFunc: func(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
binding, ok := authenticatedStreamBindingFromContext(stream.Context())
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, authenticatedStreamBinding{
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"galaxy/gateway/authn"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -15,14 +15,14 @@ import (
|
||||
// signatureVerifyingService applies client-signature verification after
|
||||
// payload integrity checks and before later auth or routing steps run.
|
||||
type signatureVerifyingService struct {
|
||||
gatewayv1.UnimplementedEdgeGatewayServer
|
||||
edgev1.UnimplementedGatewayServer
|
||||
|
||||
delegate gatewayv1.EdgeGatewayServer
|
||||
delegate edgev1.GatewayServer
|
||||
}
|
||||
|
||||
// ExecuteCommand verifies req client signature before delegating to the
|
||||
// configured service implementation.
|
||||
func (s signatureVerifyingService) ExecuteCommand(ctx context.Context, req *gatewayv1.ExecuteCommandRequest) (*gatewayv1.ExecuteCommandResponse, error) {
|
||||
func (s signatureVerifyingService) ExecuteCommand(ctx context.Context, req *edgev1.ExecuteCommandRequest) (*edgev1.ExecuteCommandResponse, error) {
|
||||
if err := verifyRequestSignature(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func (s signatureVerifyingService) ExecuteCommand(ctx context.Context, req *gate
|
||||
|
||||
// SubscribeEvents verifies req client signature before delegating to the
|
||||
// configured service implementation.
|
||||
func (s signatureVerifyingService) SubscribeEvents(req *gatewayv1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[gatewayv1.GatewayEvent]) error {
|
||||
func (s signatureVerifyingService) SubscribeEvents(req *edgev1.SubscribeEventsRequest, stream grpc.ServerStreamingServer[edgev1.GatewayEvent]) error {
|
||||
if err := verifyRequestSignature(stream.Context()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func (s signatureVerifyingService) SubscribeEvents(req *gatewayv1.SubscribeEvent
|
||||
|
||||
// newSignatureVerifyingService wraps delegate with the client-signature
|
||||
// verification gate.
|
||||
func newSignatureVerifyingService(delegate gatewayv1.EdgeGatewayServer) gatewayv1.EdgeGatewayServer {
|
||||
func newSignatureVerifyingService(delegate edgev1.GatewayServer) edgev1.GatewayServer {
|
||||
return signatureVerifyingService{delegate: delegate}
|
||||
}
|
||||
|
||||
@@ -77,4 +77,4 @@ func verifyRequestSignature(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
var _ gatewayv1.EdgeGatewayServer = signatureVerifyingService{}
|
||||
var _ edgev1.GatewayServer = signatureVerifyingService{}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
func TestExecuteCommandRejectsInvalidSignature(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -37,7 +37,7 @@ func TestExecuteCommandRejectsInvalidSignature(t *testing.T) {
|
||||
func TestExecuteCommandRejectsWrongKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{
|
||||
@@ -62,7 +62,7 @@ func TestExecuteCommandRejectsWrongKey(t *testing.T) {
|
||||
func TestExecuteCommandRejectsInvalidCachedPublicKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{
|
||||
@@ -87,7 +87,7 @@ func TestExecuteCommandRejectsInvalidCachedPublicKey(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsInvalidSignature(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{lookupFunc: func(context.Context, string) (session.Record, error) { return newActiveSessionRecord(), nil }},
|
||||
@@ -110,7 +110,7 @@ func TestSubscribeEventsRejectsInvalidSignature(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsWrongKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{
|
||||
@@ -135,7 +135,7 @@ func TestSubscribeEventsRejectsWrongKey(t *testing.T) {
|
||||
func TestSubscribeEventsRejectsInvalidCachedPublicKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
delegate := &recordingEdgeGatewayService{}
|
||||
delegate := &recordingGatewayService{}
|
||||
server, runGateway := newTestGateway(t, ServerDependencies{
|
||||
Service: delegate,
|
||||
SessionCache: staticSessionCache{
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
"galaxy/gateway/authn"
|
||||
"galaxy/gateway/internal/downstream"
|
||||
"galaxy/gateway/internal/session"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
"galaxy/gateway/proto/galaxy/gateway/v1/gatewayv1connect"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
"galaxy/gateway/proto/edge/v1/edgev1connect"
|
||||
|
||||
gatewayfbs "galaxy/schema/fbs/gateway"
|
||||
|
||||
@@ -29,19 +29,19 @@ var (
|
||||
testFreshnessWindow = 5 * time.Minute
|
||||
)
|
||||
|
||||
func newValidExecuteCommandRequest() *gatewayv1.ExecuteCommandRequest {
|
||||
func newValidExecuteCommandRequest() *edgev1.ExecuteCommandRequest {
|
||||
return newValidExecuteCommandRequestWithSessionAndRequestID("device-session-123", "request-123")
|
||||
}
|
||||
|
||||
func newValidExecuteCommandRequestWithSessionAndRequestID(deviceSessionID string, requestID string) *gatewayv1.ExecuteCommandRequest {
|
||||
func newValidExecuteCommandRequestWithSessionAndRequestID(deviceSessionID string, requestID string) *edgev1.ExecuteCommandRequest {
|
||||
return newValidExecuteCommandRequestWithTimestamp(deviceSessionID, requestID, testCurrentTime.UnixMilli())
|
||||
}
|
||||
|
||||
func newValidExecuteCommandRequestWithTimestamp(deviceSessionID string, requestID string, timestampMS int64) *gatewayv1.ExecuteCommandRequest {
|
||||
func newValidExecuteCommandRequestWithTimestamp(deviceSessionID string, requestID string, timestampMS int64) *edgev1.ExecuteCommandRequest {
|
||||
payloadBytes := []byte("payload")
|
||||
payloadHash := sha256.Sum256(payloadBytes)
|
||||
|
||||
req := &gatewayv1.ExecuteCommandRequest{
|
||||
req := &edgev1.ExecuteCommandRequest{
|
||||
ProtocolVersion: supportedProtocolVersion,
|
||||
DeviceSessionId: deviceSessionID,
|
||||
MessageType: "fleet.move",
|
||||
@@ -56,18 +56,18 @@ func newValidExecuteCommandRequestWithTimestamp(deviceSessionID string, requestI
|
||||
return req
|
||||
}
|
||||
|
||||
func newValidSubscribeEventsRequest() *gatewayv1.SubscribeEventsRequest {
|
||||
func newValidSubscribeEventsRequest() *edgev1.SubscribeEventsRequest {
|
||||
return newValidSubscribeEventsRequestWithSessionAndRequestID("device-session-123", "request-123")
|
||||
}
|
||||
|
||||
func newValidSubscribeEventsRequestWithSessionAndRequestID(deviceSessionID string, requestID string) *gatewayv1.SubscribeEventsRequest {
|
||||
func newValidSubscribeEventsRequestWithSessionAndRequestID(deviceSessionID string, requestID string) *edgev1.SubscribeEventsRequest {
|
||||
return newValidSubscribeEventsRequestWithTimestamp(deviceSessionID, requestID, testCurrentTime.UnixMilli())
|
||||
}
|
||||
|
||||
func newValidSubscribeEventsRequestWithTimestamp(deviceSessionID string, requestID string, timestampMS int64) *gatewayv1.SubscribeEventsRequest {
|
||||
func newValidSubscribeEventsRequestWithTimestamp(deviceSessionID string, requestID string, timestampMS int64) *edgev1.SubscribeEventsRequest {
|
||||
payloadHash := sha256.Sum256(nil)
|
||||
|
||||
req := &gatewayv1.SubscribeEventsRequest{
|
||||
req := &edgev1.SubscribeEventsRequest{
|
||||
ProtocolVersion: supportedProtocolVersion,
|
||||
DeviceSessionId: deviceSessionID,
|
||||
MessageType: "gateway.subscribe",
|
||||
@@ -172,7 +172,7 @@ func (c fixedClock) Now() time.Time {
|
||||
func recvBootstrapEvent(t interface {
|
||||
require.TestingT
|
||||
Helper()
|
||||
}, stream *connect.ServerStreamForClient[gatewayv1.GatewayEvent]) *gatewayv1.GatewayEvent {
|
||||
}, stream *connect.ServerStreamForClient[edgev1.GatewayEvent]) *edgev1.GatewayEvent {
|
||||
t.Helper()
|
||||
|
||||
if !stream.Receive() {
|
||||
@@ -189,7 +189,7 @@ func recvBootstrapEvent(t interface {
|
||||
func subscribeEventsError(t interface {
|
||||
require.TestingT
|
||||
Helper()
|
||||
}, ctx context.Context, client gatewayv1connect.EdgeGatewayClient, req *gatewayv1.SubscribeEventsRequest) error {
|
||||
}, ctx context.Context, client edgev1connect.GatewayClient, req *edgev1.SubscribeEventsRequest) error {
|
||||
t.Helper()
|
||||
|
||||
stream, err := client.SubscribeEvents(ctx, connect.NewRequest(req))
|
||||
@@ -208,7 +208,7 @@ func subscribeEventsError(t interface {
|
||||
func assertServerTimeBootstrapEvent(t interface {
|
||||
require.TestingT
|
||||
Helper()
|
||||
}, event *gatewayv1.GatewayEvent, publicKey ed25519.PublicKey, wantRequestID string, wantTraceID string, wantTimestampMS int64) {
|
||||
}, event *edgev1.GatewayEvent, publicKey ed25519.PublicKey, wantRequestID string, wantTraceID string, wantTimestampMS int64) {
|
||||
t.Helper()
|
||||
|
||||
require.NotNil(t, event)
|
||||
@@ -244,7 +244,7 @@ func (s staticReplayStore) Reserve(ctx context.Context, deviceSessionID string,
|
||||
}
|
||||
|
||||
type executeCommandAdapterRouter struct {
|
||||
service gatewayv1.EdgeGatewayServer
|
||||
service edgev1.GatewayServer
|
||||
}
|
||||
|
||||
func (r executeCommandAdapterRouter) Route(string) (downstream.Client, error) {
|
||||
@@ -252,11 +252,11 @@ func (r executeCommandAdapterRouter) Route(string) (downstream.Client, error) {
|
||||
}
|
||||
|
||||
type executeCommandAdapterClient struct {
|
||||
service gatewayv1.EdgeGatewayServer
|
||||
service edgev1.GatewayServer
|
||||
}
|
||||
|
||||
func (c executeCommandAdapterClient) ExecuteCommand(ctx context.Context, command downstream.AuthenticatedCommand) (downstream.UnaryResult, error) {
|
||||
response, err := c.service.ExecuteCommand(ctx, &gatewayv1.ExecuteCommandRequest{
|
||||
response, err := c.service.ExecuteCommand(ctx, &edgev1.ExecuteCommandRequest{
|
||||
ProtocolVersion: command.ProtocolVersion,
|
||||
DeviceSessionId: command.DeviceSessionID,
|
||||
MessageType: command.MessageType,
|
||||
|
||||
@@ -6,6 +6,14 @@ info:
|
||||
This specification documents the implemented `galaxy/gateway` v1 public
|
||||
REST surface.
|
||||
|
||||
At the edge this surface is served same-origin under one host: the edge
|
||||
Caddy routes `/api/*` and `/healthz` to the public REST listener, so those
|
||||
paths are reached at `https://<host>/healthz` and
|
||||
`https://<host>/api/v1/public/auth/*`. The authenticated Connect/gRPC-Web
|
||||
surface lives behind the same host at `/rpc/*` and is not part of this
|
||||
REST contract. `/readyz` is an in-process readiness probe served on the
|
||||
listener itself and is not exposed through the edge.
|
||||
|
||||
Implemented endpoints:
|
||||
- `GET /healthz`
|
||||
- `GET /readyz`
|
||||
|
||||
+48
-48
@@ -2,9 +2,9 @@
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc (unknown)
|
||||
// source: galaxy/gateway/v1/edge_gateway.proto
|
||||
// source: edge/v1/edge_gateway.proto
|
||||
|
||||
package gatewayv1
|
||||
package edgev1
|
||||
|
||||
import (
|
||||
_ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
|
||||
@@ -42,7 +42,7 @@ type ExecuteCommandRequest struct {
|
||||
|
||||
func (x *ExecuteCommandRequest) Reset() {
|
||||
*x = ExecuteCommandRequest{}
|
||||
mi := &file_galaxy_gateway_v1_edge_gateway_proto_msgTypes[0]
|
||||
mi := &file_edge_v1_edge_gateway_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func (x *ExecuteCommandRequest) String() string {
|
||||
func (*ExecuteCommandRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ExecuteCommandRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_galaxy_gateway_v1_edge_gateway_proto_msgTypes[0]
|
||||
mi := &file_edge_v1_edge_gateway_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -67,7 +67,7 @@ func (x *ExecuteCommandRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ExecuteCommandRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ExecuteCommandRequest) Descriptor() ([]byte, []int) {
|
||||
return file_galaxy_gateway_v1_edge_gateway_proto_rawDescGZIP(), []int{0}
|
||||
return file_edge_v1_edge_gateway_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ExecuteCommandRequest) GetProtocolVersion() string {
|
||||
@@ -148,7 +148,7 @@ type ExecuteCommandResponse struct {
|
||||
|
||||
func (x *ExecuteCommandResponse) Reset() {
|
||||
*x = ExecuteCommandResponse{}
|
||||
mi := &file_galaxy_gateway_v1_edge_gateway_proto_msgTypes[1]
|
||||
mi := &file_edge_v1_edge_gateway_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func (x *ExecuteCommandResponse) String() string {
|
||||
func (*ExecuteCommandResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ExecuteCommandResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_galaxy_gateway_v1_edge_gateway_proto_msgTypes[1]
|
||||
mi := &file_edge_v1_edge_gateway_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -173,7 +173,7 @@ func (x *ExecuteCommandResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ExecuteCommandResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ExecuteCommandResponse) Descriptor() ([]byte, []int) {
|
||||
return file_galaxy_gateway_v1_edge_gateway_proto_rawDescGZIP(), []int{1}
|
||||
return file_edge_v1_edge_gateway_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ExecuteCommandResponse) GetProtocolVersion() string {
|
||||
@@ -246,7 +246,7 @@ type SubscribeEventsRequest struct {
|
||||
|
||||
func (x *SubscribeEventsRequest) Reset() {
|
||||
*x = SubscribeEventsRequest{}
|
||||
mi := &file_galaxy_gateway_v1_edge_gateway_proto_msgTypes[2]
|
||||
mi := &file_edge_v1_edge_gateway_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -258,7 +258,7 @@ func (x *SubscribeEventsRequest) String() string {
|
||||
func (*SubscribeEventsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SubscribeEventsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_galaxy_gateway_v1_edge_gateway_proto_msgTypes[2]
|
||||
mi := &file_edge_v1_edge_gateway_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -271,7 +271,7 @@ func (x *SubscribeEventsRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use SubscribeEventsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SubscribeEventsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_galaxy_gateway_v1_edge_gateway_proto_rawDescGZIP(), []int{2}
|
||||
return file_edge_v1_edge_gateway_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *SubscribeEventsRequest) GetProtocolVersion() string {
|
||||
@@ -353,7 +353,7 @@ type GatewayEvent struct {
|
||||
|
||||
func (x *GatewayEvent) Reset() {
|
||||
*x = GatewayEvent{}
|
||||
mi := &file_galaxy_gateway_v1_edge_gateway_proto_msgTypes[3]
|
||||
mi := &file_edge_v1_edge_gateway_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -365,7 +365,7 @@ func (x *GatewayEvent) String() string {
|
||||
func (*GatewayEvent) ProtoMessage() {}
|
||||
|
||||
func (x *GatewayEvent) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_galaxy_gateway_v1_edge_gateway_proto_msgTypes[3]
|
||||
mi := &file_edge_v1_edge_gateway_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -378,7 +378,7 @@ func (x *GatewayEvent) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GatewayEvent.ProtoReflect.Descriptor instead.
|
||||
func (*GatewayEvent) Descriptor() ([]byte, []int) {
|
||||
return file_galaxy_gateway_v1_edge_gateway_proto_rawDescGZIP(), []int{3}
|
||||
return file_edge_v1_edge_gateway_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *GatewayEvent) GetEventType() string {
|
||||
@@ -437,11 +437,11 @@ func (x *GatewayEvent) GetTraceId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_galaxy_gateway_v1_edge_gateway_proto protoreflect.FileDescriptor
|
||||
var File_edge_v1_edge_gateway_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_galaxy_gateway_v1_edge_gateway_proto_rawDesc = "" +
|
||||
const file_edge_v1_edge_gateway_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"$galaxy/gateway/v1/edge_gateway.proto\x12\x11galaxy.gateway.v1\x1a\x1bbuf/validate/validate.proto\"\x9c\x03\n" +
|
||||
"\x1aedge/v1/edge_gateway.proto\x12\aedge.v1\x1a\x1bbuf/validate/validate.proto\"\x9c\x03\n" +
|
||||
"\x15ExecuteCommandRequest\x122\n" +
|
||||
"\x10protocol_version\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x0fprotocolVersion\x123\n" +
|
||||
"\x11device_session_id\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x10\x01R\x0fdeviceSessionId\x12*\n" +
|
||||
@@ -484,35 +484,35 @@ const file_galaxy_gateway_v1_edge_gateway_proto_rawDesc = "" +
|
||||
"\tsignature\x18\x06 \x01(\fR\tsignature\x12\x1d\n" +
|
||||
"\n" +
|
||||
"request_id\x18\a \x01(\tR\trequestId\x12\x19\n" +
|
||||
"\btrace_id\x18\b \x01(\tR\atraceId2\xd5\x01\n" +
|
||||
"\vEdgeGateway\x12e\n" +
|
||||
"\x0eExecuteCommand\x12(.galaxy.gateway.v1.ExecuteCommandRequest\x1a).galaxy.gateway.v1.ExecuteCommandResponse\x12_\n" +
|
||||
"\x0fSubscribeEvents\x12).galaxy.gateway.v1.SubscribeEventsRequest\x1a\x1f.galaxy.gateway.v1.GatewayEvent0\x01B2Z0galaxy/gateway/proto/galaxy/gateway/v1;gatewayv1b\x06proto3"
|
||||
"\btrace_id\x18\b \x01(\tR\atraceId2\xa9\x01\n" +
|
||||
"\aGateway\x12Q\n" +
|
||||
"\x0eExecuteCommand\x12\x1e.edge.v1.ExecuteCommandRequest\x1a\x1f.edge.v1.ExecuteCommandResponse\x12K\n" +
|
||||
"\x0fSubscribeEvents\x12\x1f.edge.v1.SubscribeEventsRequest\x1a\x15.edge.v1.GatewayEvent0\x01B%Z#galaxy/gateway/proto/edge/v1;edgev1b\x06proto3"
|
||||
|
||||
var (
|
||||
file_galaxy_gateway_v1_edge_gateway_proto_rawDescOnce sync.Once
|
||||
file_galaxy_gateway_v1_edge_gateway_proto_rawDescData []byte
|
||||
file_edge_v1_edge_gateway_proto_rawDescOnce sync.Once
|
||||
file_edge_v1_edge_gateway_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_galaxy_gateway_v1_edge_gateway_proto_rawDescGZIP() []byte {
|
||||
file_galaxy_gateway_v1_edge_gateway_proto_rawDescOnce.Do(func() {
|
||||
file_galaxy_gateway_v1_edge_gateway_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_galaxy_gateway_v1_edge_gateway_proto_rawDesc), len(file_galaxy_gateway_v1_edge_gateway_proto_rawDesc)))
|
||||
func file_edge_v1_edge_gateway_proto_rawDescGZIP() []byte {
|
||||
file_edge_v1_edge_gateway_proto_rawDescOnce.Do(func() {
|
||||
file_edge_v1_edge_gateway_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_edge_v1_edge_gateway_proto_rawDesc), len(file_edge_v1_edge_gateway_proto_rawDesc)))
|
||||
})
|
||||
return file_galaxy_gateway_v1_edge_gateway_proto_rawDescData
|
||||
return file_edge_v1_edge_gateway_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_galaxy_gateway_v1_edge_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_galaxy_gateway_v1_edge_gateway_proto_goTypes = []any{
|
||||
(*ExecuteCommandRequest)(nil), // 0: galaxy.gateway.v1.ExecuteCommandRequest
|
||||
(*ExecuteCommandResponse)(nil), // 1: galaxy.gateway.v1.ExecuteCommandResponse
|
||||
(*SubscribeEventsRequest)(nil), // 2: galaxy.gateway.v1.SubscribeEventsRequest
|
||||
(*GatewayEvent)(nil), // 3: galaxy.gateway.v1.GatewayEvent
|
||||
var file_edge_v1_edge_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_edge_v1_edge_gateway_proto_goTypes = []any{
|
||||
(*ExecuteCommandRequest)(nil), // 0: edge.v1.ExecuteCommandRequest
|
||||
(*ExecuteCommandResponse)(nil), // 1: edge.v1.ExecuteCommandResponse
|
||||
(*SubscribeEventsRequest)(nil), // 2: edge.v1.SubscribeEventsRequest
|
||||
(*GatewayEvent)(nil), // 3: edge.v1.GatewayEvent
|
||||
}
|
||||
var file_galaxy_gateway_v1_edge_gateway_proto_depIdxs = []int32{
|
||||
0, // 0: galaxy.gateway.v1.EdgeGateway.ExecuteCommand:input_type -> galaxy.gateway.v1.ExecuteCommandRequest
|
||||
2, // 1: galaxy.gateway.v1.EdgeGateway.SubscribeEvents:input_type -> galaxy.gateway.v1.SubscribeEventsRequest
|
||||
1, // 2: galaxy.gateway.v1.EdgeGateway.ExecuteCommand:output_type -> galaxy.gateway.v1.ExecuteCommandResponse
|
||||
3, // 3: galaxy.gateway.v1.EdgeGateway.SubscribeEvents:output_type -> galaxy.gateway.v1.GatewayEvent
|
||||
var file_edge_v1_edge_gateway_proto_depIdxs = []int32{
|
||||
0, // 0: edge.v1.Gateway.ExecuteCommand:input_type -> edge.v1.ExecuteCommandRequest
|
||||
2, // 1: edge.v1.Gateway.SubscribeEvents:input_type -> edge.v1.SubscribeEventsRequest
|
||||
1, // 2: edge.v1.Gateway.ExecuteCommand:output_type -> edge.v1.ExecuteCommandResponse
|
||||
3, // 3: edge.v1.Gateway.SubscribeEvents:output_type -> edge.v1.GatewayEvent
|
||||
2, // [2:4] is the sub-list for method output_type
|
||||
0, // [0:2] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
@@ -520,26 +520,26 @@ var file_galaxy_gateway_v1_edge_gateway_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_galaxy_gateway_v1_edge_gateway_proto_init() }
|
||||
func file_galaxy_gateway_v1_edge_gateway_proto_init() {
|
||||
if File_galaxy_gateway_v1_edge_gateway_proto != nil {
|
||||
func init() { file_edge_v1_edge_gateway_proto_init() }
|
||||
func file_edge_v1_edge_gateway_proto_init() {
|
||||
if File_edge_v1_edge_gateway_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_galaxy_gateway_v1_edge_gateway_proto_rawDesc), len(file_galaxy_gateway_v1_edge_gateway_proto_rawDesc)),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_edge_v1_edge_gateway_proto_rawDesc), len(file_edge_v1_edge_gateway_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_galaxy_gateway_v1_edge_gateway_proto_goTypes,
|
||||
DependencyIndexes: file_galaxy_gateway_v1_edge_gateway_proto_depIdxs,
|
||||
MessageInfos: file_galaxy_gateway_v1_edge_gateway_proto_msgTypes,
|
||||
GoTypes: file_edge_v1_edge_gateway_proto_goTypes,
|
||||
DependencyIndexes: file_edge_v1_edge_gateway_proto_depIdxs,
|
||||
MessageInfos: file_edge_v1_edge_gateway_proto_msgTypes,
|
||||
}.Build()
|
||||
File_galaxy_gateway_v1_edge_gateway_proto = out.File
|
||||
file_galaxy_gateway_v1_edge_gateway_proto_goTypes = nil
|
||||
file_galaxy_gateway_v1_edge_gateway_proto_depIdxs = nil
|
||||
File_edge_v1_edge_gateway_proto = out.File
|
||||
file_edge_v1_edge_gateway_proto_goTypes = nil
|
||||
file_edge_v1_edge_gateway_proto_depIdxs = nil
|
||||
}
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package galaxy.gateway.v1;
|
||||
package edge.v1;
|
||||
|
||||
option go_package = "galaxy/gateway/proto/galaxy/gateway/v1;gatewayv1";
|
||||
option go_package = "galaxy/gateway/proto/edge/v1;edgev1";
|
||||
|
||||
import "buf/validate/validate.proto";
|
||||
|
||||
service EdgeGateway {
|
||||
service Gateway {
|
||||
rpc ExecuteCommand(ExecuteCommandRequest) returns (ExecuteCommandResponse);
|
||||
rpc SubscribeEvents(SubscribeEventsRequest) returns (stream GatewayEvent);
|
||||
}
|
||||
+45
-45
@@ -2,9 +2,9 @@
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.1
|
||||
// - protoc (unknown)
|
||||
// source: galaxy/gateway/v1/edge_gateway.proto
|
||||
// source: edge/v1/edge_gateway.proto
|
||||
|
||||
package gatewayv1
|
||||
package edgev1
|
||||
|
||||
import (
|
||||
context "context"
|
||||
@@ -19,39 +19,39 @@ import (
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
EdgeGateway_ExecuteCommand_FullMethodName = "/galaxy.gateway.v1.EdgeGateway/ExecuteCommand"
|
||||
EdgeGateway_SubscribeEvents_FullMethodName = "/galaxy.gateway.v1.EdgeGateway/SubscribeEvents"
|
||||
Gateway_ExecuteCommand_FullMethodName = "/edge.v1.Gateway/ExecuteCommand"
|
||||
Gateway_SubscribeEvents_FullMethodName = "/edge.v1.Gateway/SubscribeEvents"
|
||||
)
|
||||
|
||||
// EdgeGatewayClient is the client API for EdgeGateway service.
|
||||
// GatewayClient is the client API for Gateway service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type EdgeGatewayClient interface {
|
||||
type GatewayClient interface {
|
||||
ExecuteCommand(ctx context.Context, in *ExecuteCommandRequest, opts ...grpc.CallOption) (*ExecuteCommandResponse, error)
|
||||
SubscribeEvents(ctx context.Context, in *SubscribeEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GatewayEvent], error)
|
||||
}
|
||||
|
||||
type edgeGatewayClient struct {
|
||||
type gatewayClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewEdgeGatewayClient(cc grpc.ClientConnInterface) EdgeGatewayClient {
|
||||
return &edgeGatewayClient{cc}
|
||||
func NewGatewayClient(cc grpc.ClientConnInterface) GatewayClient {
|
||||
return &gatewayClient{cc}
|
||||
}
|
||||
|
||||
func (c *edgeGatewayClient) ExecuteCommand(ctx context.Context, in *ExecuteCommandRequest, opts ...grpc.CallOption) (*ExecuteCommandResponse, error) {
|
||||
func (c *gatewayClient) ExecuteCommand(ctx context.Context, in *ExecuteCommandRequest, opts ...grpc.CallOption) (*ExecuteCommandResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ExecuteCommandResponse)
|
||||
err := c.cc.Invoke(ctx, EdgeGateway_ExecuteCommand_FullMethodName, in, out, cOpts...)
|
||||
err := c.cc.Invoke(ctx, Gateway_ExecuteCommand_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *edgeGatewayClient) SubscribeEvents(ctx context.Context, in *SubscribeEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GatewayEvent], error) {
|
||||
func (c *gatewayClient) SubscribeEvents(ctx context.Context, in *SubscribeEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GatewayEvent], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &EdgeGateway_ServiceDesc.Streams[0], EdgeGateway_SubscribeEvents_FullMethodName, cOpts...)
|
||||
stream, err := c.cc.NewStream(ctx, &Gateway_ServiceDesc.Streams[0], Gateway_SubscribeEvents_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,98 +66,98 @@ func (c *edgeGatewayClient) SubscribeEvents(ctx context.Context, in *SubscribeEv
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type EdgeGateway_SubscribeEventsClient = grpc.ServerStreamingClient[GatewayEvent]
|
||||
type Gateway_SubscribeEventsClient = grpc.ServerStreamingClient[GatewayEvent]
|
||||
|
||||
// EdgeGatewayServer is the server API for EdgeGateway service.
|
||||
// All implementations must embed UnimplementedEdgeGatewayServer
|
||||
// GatewayServer is the server API for Gateway service.
|
||||
// All implementations must embed UnimplementedGatewayServer
|
||||
// for forward compatibility.
|
||||
type EdgeGatewayServer interface {
|
||||
type GatewayServer interface {
|
||||
ExecuteCommand(context.Context, *ExecuteCommandRequest) (*ExecuteCommandResponse, error)
|
||||
SubscribeEvents(*SubscribeEventsRequest, grpc.ServerStreamingServer[GatewayEvent]) error
|
||||
mustEmbedUnimplementedEdgeGatewayServer()
|
||||
mustEmbedUnimplementedGatewayServer()
|
||||
}
|
||||
|
||||
// UnimplementedEdgeGatewayServer must be embedded to have
|
||||
// UnimplementedGatewayServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedEdgeGatewayServer struct{}
|
||||
type UnimplementedGatewayServer struct{}
|
||||
|
||||
func (UnimplementedEdgeGatewayServer) ExecuteCommand(context.Context, *ExecuteCommandRequest) (*ExecuteCommandResponse, error) {
|
||||
func (UnimplementedGatewayServer) ExecuteCommand(context.Context, *ExecuteCommandRequest) (*ExecuteCommandResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ExecuteCommand not implemented")
|
||||
}
|
||||
func (UnimplementedEdgeGatewayServer) SubscribeEvents(*SubscribeEventsRequest, grpc.ServerStreamingServer[GatewayEvent]) error {
|
||||
func (UnimplementedGatewayServer) SubscribeEvents(*SubscribeEventsRequest, grpc.ServerStreamingServer[GatewayEvent]) error {
|
||||
return status.Error(codes.Unimplemented, "method SubscribeEvents not implemented")
|
||||
}
|
||||
func (UnimplementedEdgeGatewayServer) mustEmbedUnimplementedEdgeGatewayServer() {}
|
||||
func (UnimplementedEdgeGatewayServer) testEmbeddedByValue() {}
|
||||
func (UnimplementedGatewayServer) mustEmbedUnimplementedGatewayServer() {}
|
||||
func (UnimplementedGatewayServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeEdgeGatewayServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to EdgeGatewayServer will
|
||||
// UnsafeGatewayServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to GatewayServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeEdgeGatewayServer interface {
|
||||
mustEmbedUnimplementedEdgeGatewayServer()
|
||||
type UnsafeGatewayServer interface {
|
||||
mustEmbedUnimplementedGatewayServer()
|
||||
}
|
||||
|
||||
func RegisterEdgeGatewayServer(s grpc.ServiceRegistrar, srv EdgeGatewayServer) {
|
||||
// If the following call panics, it indicates UnimplementedEdgeGatewayServer was
|
||||
func RegisterGatewayServer(s grpc.ServiceRegistrar, srv GatewayServer) {
|
||||
// If the following call panics, it indicates UnimplementedGatewayServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&EdgeGateway_ServiceDesc, srv)
|
||||
s.RegisterService(&Gateway_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _EdgeGateway_ExecuteCommand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
func _Gateway_ExecuteCommand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ExecuteCommandRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EdgeGatewayServer).ExecuteCommand(ctx, in)
|
||||
return srv.(GatewayServer).ExecuteCommand(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EdgeGateway_ExecuteCommand_FullMethodName,
|
||||
FullMethod: Gateway_ExecuteCommand_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EdgeGatewayServer).ExecuteCommand(ctx, req.(*ExecuteCommandRequest))
|
||||
return srv.(GatewayServer).ExecuteCommand(ctx, req.(*ExecuteCommandRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EdgeGateway_SubscribeEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
func _Gateway_SubscribeEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(SubscribeEventsRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(EdgeGatewayServer).SubscribeEvents(m, &grpc.GenericServerStream[SubscribeEventsRequest, GatewayEvent]{ServerStream: stream})
|
||||
return srv.(GatewayServer).SubscribeEvents(m, &grpc.GenericServerStream[SubscribeEventsRequest, GatewayEvent]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type EdgeGateway_SubscribeEventsServer = grpc.ServerStreamingServer[GatewayEvent]
|
||||
type Gateway_SubscribeEventsServer = grpc.ServerStreamingServer[GatewayEvent]
|
||||
|
||||
// EdgeGateway_ServiceDesc is the grpc.ServiceDesc for EdgeGateway service.
|
||||
// Gateway_ServiceDesc is the grpc.ServiceDesc for Gateway service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var EdgeGateway_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "galaxy.gateway.v1.EdgeGateway",
|
||||
HandlerType: (*EdgeGatewayServer)(nil),
|
||||
var Gateway_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "edge.v1.Gateway",
|
||||
HandlerType: (*GatewayServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "ExecuteCommand",
|
||||
Handler: _EdgeGateway_ExecuteCommand_Handler,
|
||||
Handler: _Gateway_ExecuteCommand_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "SubscribeEvents",
|
||||
Handler: _EdgeGateway_SubscribeEvents_Handler,
|
||||
Handler: _Gateway_SubscribeEvents_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "galaxy/gateway/v1/edge_gateway.proto",
|
||||
Metadata: "edge/v1/edge_gateway.proto",
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
|
||||
//
|
||||
// Source: edge/v1/edge_gateway.proto
|
||||
|
||||
package edgev1connect
|
||||
|
||||
import (
|
||||
connect "connectrpc.com/connect"
|
||||
context "context"
|
||||
errors "errors"
|
||||
v1 "galaxy/gateway/proto/edge/v1"
|
||||
http "net/http"
|
||||
strings "strings"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the connect package are
|
||||
// compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of connect newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of connect or updating the connect
|
||||
// version compiled into your binary.
|
||||
const _ = connect.IsAtLeastVersion1_13_0
|
||||
|
||||
const (
|
||||
// GatewayName is the fully-qualified name of the Gateway service.
|
||||
GatewayName = "edge.v1.Gateway"
|
||||
)
|
||||
|
||||
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
|
||||
//
|
||||
// Note that these are different from the fully-qualified method names used by
|
||||
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||
// period.
|
||||
const (
|
||||
// GatewayExecuteCommandProcedure is the fully-qualified name of the Gateway's ExecuteCommand RPC.
|
||||
GatewayExecuteCommandProcedure = "/edge.v1.Gateway/ExecuteCommand"
|
||||
// GatewaySubscribeEventsProcedure is the fully-qualified name of the Gateway's SubscribeEvents RPC.
|
||||
GatewaySubscribeEventsProcedure = "/edge.v1.Gateway/SubscribeEvents"
|
||||
)
|
||||
|
||||
// GatewayClient is a client for the edge.v1.Gateway service.
|
||||
type GatewayClient interface {
|
||||
ExecuteCommand(context.Context, *connect.Request[v1.ExecuteCommandRequest]) (*connect.Response[v1.ExecuteCommandResponse], error)
|
||||
SubscribeEvents(context.Context, *connect.Request[v1.SubscribeEventsRequest]) (*connect.ServerStreamForClient[v1.GatewayEvent], error)
|
||||
}
|
||||
|
||||
// NewGatewayClient constructs a client for the edge.v1.Gateway service. By default, it uses the
|
||||
// Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
|
||||
// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
|
||||
// connect.WithGRPCWeb() options.
|
||||
//
|
||||
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
|
||||
// http://api.acme.com or https://acme.com/grpc).
|
||||
func NewGatewayClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) GatewayClient {
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
gatewayMethods := v1.File_edge_v1_edge_gateway_proto.Services().ByName("Gateway").Methods()
|
||||
return &gatewayClient{
|
||||
executeCommand: connect.NewClient[v1.ExecuteCommandRequest, v1.ExecuteCommandResponse](
|
||||
httpClient,
|
||||
baseURL+GatewayExecuteCommandProcedure,
|
||||
connect.WithSchema(gatewayMethods.ByName("ExecuteCommand")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
subscribeEvents: connect.NewClient[v1.SubscribeEventsRequest, v1.GatewayEvent](
|
||||
httpClient,
|
||||
baseURL+GatewaySubscribeEventsProcedure,
|
||||
connect.WithSchema(gatewayMethods.ByName("SubscribeEvents")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// gatewayClient implements GatewayClient.
|
||||
type gatewayClient struct {
|
||||
executeCommand *connect.Client[v1.ExecuteCommandRequest, v1.ExecuteCommandResponse]
|
||||
subscribeEvents *connect.Client[v1.SubscribeEventsRequest, v1.GatewayEvent]
|
||||
}
|
||||
|
||||
// ExecuteCommand calls edge.v1.Gateway.ExecuteCommand.
|
||||
func (c *gatewayClient) ExecuteCommand(ctx context.Context, req *connect.Request[v1.ExecuteCommandRequest]) (*connect.Response[v1.ExecuteCommandResponse], error) {
|
||||
return c.executeCommand.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// SubscribeEvents calls edge.v1.Gateway.SubscribeEvents.
|
||||
func (c *gatewayClient) SubscribeEvents(ctx context.Context, req *connect.Request[v1.SubscribeEventsRequest]) (*connect.ServerStreamForClient[v1.GatewayEvent], error) {
|
||||
return c.subscribeEvents.CallServerStream(ctx, req)
|
||||
}
|
||||
|
||||
// GatewayHandler is an implementation of the edge.v1.Gateway service.
|
||||
type GatewayHandler interface {
|
||||
ExecuteCommand(context.Context, *connect.Request[v1.ExecuteCommandRequest]) (*connect.Response[v1.ExecuteCommandResponse], error)
|
||||
SubscribeEvents(context.Context, *connect.Request[v1.SubscribeEventsRequest], *connect.ServerStream[v1.GatewayEvent]) error
|
||||
}
|
||||
|
||||
// NewGatewayHandler builds an HTTP handler from the service implementation. It returns the path on
|
||||
// which to mount the handler and the handler itself.
|
||||
//
|
||||
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
|
||||
// and JSON codecs. They also support gzip compression.
|
||||
func NewGatewayHandler(svc GatewayHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
||||
gatewayMethods := v1.File_edge_v1_edge_gateway_proto.Services().ByName("Gateway").Methods()
|
||||
gatewayExecuteCommandHandler := connect.NewUnaryHandler(
|
||||
GatewayExecuteCommandProcedure,
|
||||
svc.ExecuteCommand,
|
||||
connect.WithSchema(gatewayMethods.ByName("ExecuteCommand")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
gatewaySubscribeEventsHandler := connect.NewServerStreamHandler(
|
||||
GatewaySubscribeEventsProcedure,
|
||||
svc.SubscribeEvents,
|
||||
connect.WithSchema(gatewayMethods.ByName("SubscribeEvents")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
return "/edge.v1.Gateway/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case GatewayExecuteCommandProcedure:
|
||||
gatewayExecuteCommandHandler.ServeHTTP(w, r)
|
||||
case GatewaySubscribeEventsProcedure:
|
||||
gatewaySubscribeEventsHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// UnimplementedGatewayHandler returns CodeUnimplemented from all methods.
|
||||
type UnimplementedGatewayHandler struct{}
|
||||
|
||||
func (UnimplementedGatewayHandler) ExecuteCommand(context.Context, *connect.Request[v1.ExecuteCommandRequest]) (*connect.Response[v1.ExecuteCommandResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("edge.v1.Gateway.ExecuteCommand is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedGatewayHandler) SubscribeEvents(context.Context, *connect.Request[v1.SubscribeEventsRequest], *connect.ServerStream[v1.GatewayEvent]) error {
|
||||
return connect.NewError(connect.CodeUnimplemented, errors.New("edge.v1.Gateway.SubscribeEvents is not implemented"))
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
|
||||
//
|
||||
// Source: galaxy/gateway/v1/edge_gateway.proto
|
||||
|
||||
package gatewayv1connect
|
||||
|
||||
import (
|
||||
connect "connectrpc.com/connect"
|
||||
context "context"
|
||||
errors "errors"
|
||||
v1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
http "net/http"
|
||||
strings "strings"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the connect package are
|
||||
// compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of connect newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of connect or updating the connect
|
||||
// version compiled into your binary.
|
||||
const _ = connect.IsAtLeastVersion1_13_0
|
||||
|
||||
const (
|
||||
// EdgeGatewayName is the fully-qualified name of the EdgeGateway service.
|
||||
EdgeGatewayName = "galaxy.gateway.v1.EdgeGateway"
|
||||
)
|
||||
|
||||
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
|
||||
//
|
||||
// Note that these are different from the fully-qualified method names used by
|
||||
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||
// period.
|
||||
const (
|
||||
// EdgeGatewayExecuteCommandProcedure is the fully-qualified name of the EdgeGateway's
|
||||
// ExecuteCommand RPC.
|
||||
EdgeGatewayExecuteCommandProcedure = "/galaxy.gateway.v1.EdgeGateway/ExecuteCommand"
|
||||
// EdgeGatewaySubscribeEventsProcedure is the fully-qualified name of the EdgeGateway's
|
||||
// SubscribeEvents RPC.
|
||||
EdgeGatewaySubscribeEventsProcedure = "/galaxy.gateway.v1.EdgeGateway/SubscribeEvents"
|
||||
)
|
||||
|
||||
// EdgeGatewayClient is a client for the galaxy.gateway.v1.EdgeGateway service.
|
||||
type EdgeGatewayClient interface {
|
||||
ExecuteCommand(context.Context, *connect.Request[v1.ExecuteCommandRequest]) (*connect.Response[v1.ExecuteCommandResponse], error)
|
||||
SubscribeEvents(context.Context, *connect.Request[v1.SubscribeEventsRequest]) (*connect.ServerStreamForClient[v1.GatewayEvent], error)
|
||||
}
|
||||
|
||||
// NewEdgeGatewayClient constructs a client for the galaxy.gateway.v1.EdgeGateway service. By
|
||||
// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
|
||||
// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
|
||||
// connect.WithGRPC() or connect.WithGRPCWeb() options.
|
||||
//
|
||||
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
|
||||
// http://api.acme.com or https://acme.com/grpc).
|
||||
func NewEdgeGatewayClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) EdgeGatewayClient {
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
edgeGatewayMethods := v1.File_galaxy_gateway_v1_edge_gateway_proto.Services().ByName("EdgeGateway").Methods()
|
||||
return &edgeGatewayClient{
|
||||
executeCommand: connect.NewClient[v1.ExecuteCommandRequest, v1.ExecuteCommandResponse](
|
||||
httpClient,
|
||||
baseURL+EdgeGatewayExecuteCommandProcedure,
|
||||
connect.WithSchema(edgeGatewayMethods.ByName("ExecuteCommand")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
subscribeEvents: connect.NewClient[v1.SubscribeEventsRequest, v1.GatewayEvent](
|
||||
httpClient,
|
||||
baseURL+EdgeGatewaySubscribeEventsProcedure,
|
||||
connect.WithSchema(edgeGatewayMethods.ByName("SubscribeEvents")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// edgeGatewayClient implements EdgeGatewayClient.
|
||||
type edgeGatewayClient struct {
|
||||
executeCommand *connect.Client[v1.ExecuteCommandRequest, v1.ExecuteCommandResponse]
|
||||
subscribeEvents *connect.Client[v1.SubscribeEventsRequest, v1.GatewayEvent]
|
||||
}
|
||||
|
||||
// ExecuteCommand calls galaxy.gateway.v1.EdgeGateway.ExecuteCommand.
|
||||
func (c *edgeGatewayClient) ExecuteCommand(ctx context.Context, req *connect.Request[v1.ExecuteCommandRequest]) (*connect.Response[v1.ExecuteCommandResponse], error) {
|
||||
return c.executeCommand.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// SubscribeEvents calls galaxy.gateway.v1.EdgeGateway.SubscribeEvents.
|
||||
func (c *edgeGatewayClient) SubscribeEvents(ctx context.Context, req *connect.Request[v1.SubscribeEventsRequest]) (*connect.ServerStreamForClient[v1.GatewayEvent], error) {
|
||||
return c.subscribeEvents.CallServerStream(ctx, req)
|
||||
}
|
||||
|
||||
// EdgeGatewayHandler is an implementation of the galaxy.gateway.v1.EdgeGateway service.
|
||||
type EdgeGatewayHandler interface {
|
||||
ExecuteCommand(context.Context, *connect.Request[v1.ExecuteCommandRequest]) (*connect.Response[v1.ExecuteCommandResponse], error)
|
||||
SubscribeEvents(context.Context, *connect.Request[v1.SubscribeEventsRequest], *connect.ServerStream[v1.GatewayEvent]) error
|
||||
}
|
||||
|
||||
// NewEdgeGatewayHandler builds an HTTP handler from the service implementation. It returns the path
|
||||
// on which to mount the handler and the handler itself.
|
||||
//
|
||||
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
|
||||
// and JSON codecs. They also support gzip compression.
|
||||
func NewEdgeGatewayHandler(svc EdgeGatewayHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
||||
edgeGatewayMethods := v1.File_galaxy_gateway_v1_edge_gateway_proto.Services().ByName("EdgeGateway").Methods()
|
||||
edgeGatewayExecuteCommandHandler := connect.NewUnaryHandler(
|
||||
EdgeGatewayExecuteCommandProcedure,
|
||||
svc.ExecuteCommand,
|
||||
connect.WithSchema(edgeGatewayMethods.ByName("ExecuteCommand")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
edgeGatewaySubscribeEventsHandler := connect.NewServerStreamHandler(
|
||||
EdgeGatewaySubscribeEventsProcedure,
|
||||
svc.SubscribeEvents,
|
||||
connect.WithSchema(edgeGatewayMethods.ByName("SubscribeEvents")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
return "/galaxy.gateway.v1.EdgeGateway/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case EdgeGatewayExecuteCommandProcedure:
|
||||
edgeGatewayExecuteCommandHandler.ServeHTTP(w, r)
|
||||
case EdgeGatewaySubscribeEventsProcedure:
|
||||
edgeGatewaySubscribeEventsHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// UnimplementedEdgeGatewayHandler returns CodeUnimplemented from all methods.
|
||||
type UnimplementedEdgeGatewayHandler struct{}
|
||||
|
||||
func (UnimplementedEdgeGatewayHandler) ExecuteCommand(context.Context, *connect.Request[v1.ExecuteCommandRequest]) (*connect.Response[v1.ExecuteCommandResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("galaxy.gateway.v1.EdgeGateway.ExecuteCommand is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedEdgeGatewayHandler) SubscribeEvents(context.Context, *connect.Request[v1.SubscribeEventsRequest], *connect.ServerStream[v1.GatewayEvent]) error {
|
||||
return connect.NewError(connect.CodeUnimplemented, errors.New("galaxy.gateway.v1.EdgeGateway.SubscribeEvents is not implemented"))
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"time"
|
||||
|
||||
gatewayauthn "galaxy/gateway/authn"
|
||||
gatewayv1 "galaxy/gateway/proto/galaxy/gateway/v1"
|
||||
"galaxy/gateway/proto/galaxy/gateway/v1/gatewayv1connect"
|
||||
edgev1 "galaxy/gateway/proto/edge/v1"
|
||||
"galaxy/gateway/proto/edge/v1/edgev1connect"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/google/uuid"
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
// alongside gRPC and gRPC-Web on the same port.
|
||||
type SignedGatewayClient struct {
|
||||
httpClient *http.Client
|
||||
edge gatewayv1connect.EdgeGatewayClient
|
||||
edge edgev1connect.GatewayClient
|
||||
deviceSID string
|
||||
privateKey ed25519.PrivateKey
|
||||
respPub ed25519.PublicKey
|
||||
@@ -75,7 +75,7 @@ func DialGateway(_ context.Context, addr string, deviceSID string, privateKey ed
|
||||
},
|
||||
},
|
||||
}
|
||||
edge := gatewayv1connect.NewEdgeGatewayClient(httpClient, "http://"+addr)
|
||||
edge := edgev1connect.NewGatewayClient(httpClient, "http://"+addr)
|
||||
|
||||
return &SignedGatewayClient{
|
||||
httpClient: httpClient,
|
||||
@@ -164,7 +164,7 @@ func (c *SignedGatewayClient) Execute(ctx context.Context, messageType string, p
|
||||
signature = ed25519.Sign(c.privateKey, input)
|
||||
}
|
||||
|
||||
req := &gatewayv1.ExecuteCommandRequest{
|
||||
req := &edgev1.ExecuteCommandRequest{
|
||||
ProtocolVersion: protocolVersion,
|
||||
DeviceSessionId: deviceSID,
|
||||
MessageType: messageType,
|
||||
@@ -209,7 +209,7 @@ func (c *SignedGatewayClient) Execute(ctx context.Context, messageType string, p
|
||||
// authenticated event the gateway delivers; the channel closes when
|
||||
// the stream ends or when ctx is done. Errors land on the err
|
||||
// channel.
|
||||
func (c *SignedGatewayClient) SubscribeEvents(ctx context.Context, messageType string) (<-chan *gatewayv1.GatewayEvent, <-chan error, error) {
|
||||
func (c *SignedGatewayClient) SubscribeEvents(ctx context.Context, messageType string) (<-chan *edgev1.GatewayEvent, <-chan error, error) {
|
||||
requestID := uuid.NewString()
|
||||
timestampMS := time.Now().UnixMilli()
|
||||
protocolVersion := "v1"
|
||||
@@ -224,7 +224,7 @@ func (c *SignedGatewayClient) SubscribeEvents(ctx context.Context, messageType s
|
||||
PayloadHash: emptyHash[:],
|
||||
}))
|
||||
|
||||
stream, err := c.edge.SubscribeEvents(ctx, connect.NewRequest(&gatewayv1.SubscribeEventsRequest{
|
||||
stream, err := c.edge.SubscribeEvents(ctx, connect.NewRequest(&edgev1.SubscribeEventsRequest{
|
||||
ProtocolVersion: protocolVersion,
|
||||
DeviceSessionId: c.deviceSID,
|
||||
MessageType: messageType,
|
||||
@@ -237,7 +237,7 @@ func (c *SignedGatewayClient) SubscribeEvents(ctx context.Context, messageType s
|
||||
return nil, nil, fmt.Errorf("open subscribe events: %w", err)
|
||||
}
|
||||
|
||||
events := make(chan *gatewayv1.GatewayEvent, 16)
|
||||
events := make(chan *edgev1.GatewayEvent, 16)
|
||||
errs := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(events)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
.vitepress/dist/
|
||||
.vitepress/cache/
|
||||
@@ -0,0 +1,59 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
|
||||
// Galaxy project site. The single-origin deployment serves this static
|
||||
// build at the root (`/`); the game UI lives under `/game/`. The site is
|
||||
// Markdown-first with the default theme's two-column navigation (left:
|
||||
// chapters; right: on-this-page), LaTeX math, and a minimal monospace
|
||||
// theme. Internationalisation uses VitePress `locales`, which renders the
|
||||
// built-in language switcher in the nav bar.
|
||||
export default defineConfig({
|
||||
title: "Galaxy",
|
||||
description: "Galaxy — a turn-based space strategy game.",
|
||||
cleanUrls: true,
|
||||
// READMEs (this dir's and any future per-section ones) are developer
|
||||
// docs, not site pages.
|
||||
srcExclude: ["**/README.md"],
|
||||
// The game UI is served from `/game/` by the edge Caddy, not by
|
||||
// VitePress, so its links must skip the dead-link checker.
|
||||
ignoreDeadLinks: [/^\/game\//],
|
||||
markdown: {
|
||||
// LaTeX via markdown-it-mathjax3: inline `$…$`, block `$$…$$`.
|
||||
math: true,
|
||||
},
|
||||
themeConfig: {
|
||||
outline: { level: [2, 3], label: "On this page" },
|
||||
},
|
||||
locales: {
|
||||
root: {
|
||||
label: "English",
|
||||
lang: "en",
|
||||
themeConfig: {
|
||||
// The game is a separate app under /game/, not a VitePress page.
|
||||
// target "_self" makes the link a real navigation (full load),
|
||||
// not a client-side SPA route — otherwise VitePress shows its
|
||||
// own 404 for the unknown route.
|
||||
nav: [{ text: "Play", link: "/game/", target: "_self" }],
|
||||
sidebar: [
|
||||
{
|
||||
text: "Galaxy",
|
||||
items: [{ text: "Overview", link: "/" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
ru: {
|
||||
label: "Русский",
|
||||
lang: "ru",
|
||||
link: "/ru/",
|
||||
themeConfig: {
|
||||
nav: [{ text: "Играть", link: "/game/", target: "_self" }],
|
||||
sidebar: [
|
||||
{
|
||||
text: "Galaxy",
|
||||
items: [{ text: "Обзор", link: "/ru/" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Minimal, restrained theme. No flashy hero or bright accents — the
|
||||
* project site leans "nerdy" with a fixed-width type stack. System
|
||||
* fonts first so nothing is downloaded.
|
||||
*/
|
||||
:root {
|
||||
--vp-font-family-base: ui-monospace, "JetBrains Mono", "SF Mono",
|
||||
"Fira Code", "DejaVu Sans Mono", Menlo, Consolas, monospace;
|
||||
--vp-font-family-mono: var(--vp-font-family-base);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Minimal theme: the default VitePress theme without its bundled Inter
|
||||
// font, plus a monospace type stack for the project's "nerdy" look.
|
||||
import DefaultTheme from "vitepress/theme-without-fonts";
|
||||
import "./custom.css";
|
||||
|
||||
export default DefaultTheme;
|
||||
@@ -0,0 +1,36 @@
|
||||
# Galaxy project site
|
||||
|
||||
The public project site — an overview today, documentation as it grows.
|
||||
Built with [VitePress](https://vitepress.dev) and served as static files
|
||||
at the **root** (`/`) of the single-origin deployment; the game UI lives
|
||||
under `/game/` (see `tools/dev-deploy/Caddyfile.dev`).
|
||||
|
||||
## Layout
|
||||
|
||||
- `index.md`, `ru/index.md` — per-locale home pages.
|
||||
- `.vitepress/config.ts` — site config: locales (English + Russian, with
|
||||
the built-in language switcher), LaTeX math (`math: true`), and the
|
||||
two-column navigation (left sidebar = chapters, right outline =
|
||||
on-this-page).
|
||||
- `.vitepress/theme/` — the default theme without bundled fonts, plus a
|
||||
minimal monospace type stack in `custom.css`.
|
||||
|
||||
## Authoring
|
||||
|
||||
- Add a page as Markdown and register it in the `sidebar` of each locale
|
||||
in `.vitepress/config.ts`.
|
||||
- Localised content mirrors the English tree under `ru/`.
|
||||
- Math uses LaTeX: inline `$E = mc^2$`, block `$$ … $$`.
|
||||
- Link to the game with the root-relative `/game/` path so the build
|
||||
stays domain-agnostic (no hard-coded host).
|
||||
|
||||
## Commands
|
||||
|
||||
pnpm install
|
||||
pnpm dev # local dev server
|
||||
pnpm build # static build into .vitepress/dist
|
||||
pnpm preview # preview the build
|
||||
|
||||
The dev and prod deploys build the site via
|
||||
`make -C tools/dev-deploy seed-site` and the `dev-deploy` /
|
||||
`prod-build` workflows.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Galaxy
|
||||
|
||||
A turn-based space strategy game.
|
||||
|
||||
[Play the game →](/game/){target="_self"}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "galaxy-site",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Galaxy project site — overview and documentation (VitePress).",
|
||||
"scripts": {
|
||||
"dev": "vitepress dev",
|
||||
"build": "vitepress build",
|
||||
"preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"markdown-it-mathjax3": "^5.2.0",
|
||||
"vitepress": "^1.6.4"
|
||||
}
|
||||
}
|
||||
Generated
+1736
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
@@ -0,0 +1,24 @@
|
||||
// Tombstone service worker for the site origin's root scope (`/`).
|
||||
//
|
||||
// The game UI used to be served at this origin's root with a
|
||||
// root-scoped service worker. It now lives under `/game/` (its own
|
||||
// scoped worker), and the project site served at `/` ships no service
|
||||
// worker of its own. This file exists only so any lingering old
|
||||
// root-scoped worker, on its next update check, replaces itself with
|
||||
// this one — which unregisters itself and reloads its controlled pages
|
||||
// so they fall through to the live network (the site) instead of a
|
||||
// stale cache. New visitors never register it; nothing here calls
|
||||
// `register`.
|
||||
self.addEventListener("install", () => self.skipWaiting());
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
await self.registration.unregister();
|
||||
const clients = await self.clients.matchAll({ type: "window" });
|
||||
for (const client of clients) {
|
||||
client.navigate(client.url);
|
||||
}
|
||||
})(),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
# Galaxy
|
||||
|
||||
Пошаговая космическая стратегия.
|
||||
|
||||
[Играть →](/game/){target="_self"}
|
||||
@@ -1,51 +1,68 @@
|
||||
# Application-routing Caddy for the long-lived dev environment.
|
||||
# Listens only on the `edge` Docker network; TLS termination and the
|
||||
# real `:80`/`:443` listeners belong to the host Caddy in front of us.
|
||||
# Single-origin, path-based: the project site, the game UI, and both
|
||||
# gateway surfaces live behind one host. TLS termination and the real
|
||||
# `:80`/`:443` listeners belong to the host Caddy in front of us.
|
||||
#
|
||||
# `/srv/galaxy-ui` is mounted from the `galaxy-dev-ui-dist` named volume,
|
||||
# refreshed on every dev-deploy run.
|
||||
{
|
||||
# / -> project site (galaxy-dev-site-dist -> /srv/galaxy-site)
|
||||
# /game/* -> game UI (galaxy-dev-ui-dist -> /srv/galaxy-ui)
|
||||
# /api/*, /healthz -> gateway public REST (galaxy-api:8080)
|
||||
# /rpc/* -> gateway Connect/gRPC-web (galaxy-api:9090)
|
||||
#
|
||||
# The same artifact is domain-agnostic: nothing here names a host, so an
|
||||
# identical bundle serves galaxy.lan, galaxy.iliadenisov.ru, or any
|
||||
# other domain the host Caddy terminates.
|
||||
{
|
||||
}
|
||||
auto_https off
|
||||
}
|
||||
|
||||
:80 {
|
||||
handle @frontend {
|
||||
root * /srv/galaxy-ui
|
||||
|
||||
# Authenticated Connect-Web edge. The browser calls
|
||||
# `/rpc/edge.v1.Gateway/<Method>`; strip the `/rpc` prefix so the
|
||||
# gateway sees the proto-derived service path on its :9090 listener.
|
||||
handle_path /rpc/* {
|
||||
reverse_proxy galaxy-api:9090
|
||||
}
|
||||
|
||||
# `_app/immutable/`; the file name changes whenever the
|
||||
# content changes, so the browser can cache them forever.
|
||||
# Without an explicit Cache-Control, Caddy falls back to
|
||||
# heuristic caching that revalidates on every reload —
|
||||
# measurably slow on Safari + the long-lived dev stack
|
||||
# when the cache is warm. Everything else (index.html
|
||||
# fallback, env.js, version.json, core.wasm,
|
||||
# wasm_exec.js, favicon.svg) must revalidate so a fresh
|
||||
# deploy lands without the user having to clear the
|
||||
# cache by hand.
|
||||
@immutable path /_app/immutable/*
|
||||
header @immutable Cache-Control "public, max-age=31536000, immutable"
|
||||
@dynamic not path /_app/immutable/*
|
||||
header @dynamic Cache-Control "no-cache, must-revalidate"
|
||||
|
||||
# Gateway public REST (auth) and the health probe on :8080.
|
||||
@api path /api/* /healthz
|
||||
handle @api {
|
||||
reverse_proxy galaxy-api:8080
|
||||
}
|
||||
|
||||
file_server
|
||||
encode zstd gzip
|
||||
}
|
||||
|
||||
# Bare `/game` (no trailing slash) -> `/game/` so the SPA root
|
||||
# resolves before the site catch-all can claim it.
|
||||
handle /game {
|
||||
redir * /game/ 308
|
||||
}
|
||||
|
||||
handle @api {
|
||||
# Connect-Web (authenticated) lives on a separate listener
|
||||
# (`GATEWAY_AUTHENTICATED_GRPC_ADDR=:9090`). Anything else —
|
||||
# public auth, healthz — is the public REST listener on
|
||||
# `:8080`. The split mirrors the Vite dev-server proxy in
|
||||
# `ui/frontend/vite.config.ts`.
|
||||
@connect path /galaxy.gateway.v1.EdgeGateway/*
|
||||
handle @connect {
|
||||
reverse_proxy galaxy-api:9090
|
||||
}
|
||||
reverse_proxy galaxy-api:8080
|
||||
}
|
||||
}
|
||||
# Game UI under `/game/`. The bundle is built with base=/game, so it
|
||||
# references `/game/_app/...`; strip the prefix to serve the build
|
||||
# whose files sit at the volume root. SPA fallback to index.html.
|
||||
handle_path /game/* {
|
||||
root * /srv/galaxy-ui
|
||||
# Hash-named, content-addressed chunks: cache forever.
|
||||
@immutable path /_app/immutable/*
|
||||
header @immutable Cache-Control "public, max-age=31536000, immutable"
|
||||
# index.html, env.js, version.json, core.wasm, wasm_exec.js,
|
||||
# favicon, manifest, service-worker.js must revalidate so a
|
||||
# fresh deploy lands without a manual cache clear.
|
||||
@dynamic not path /_app/immutable/*
|
||||
header @dynamic Cache-Control "no-cache, must-revalidate"
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
encode zstd gzip
|
||||
}
|
||||
|
||||
# Project site at the root (VitePress static output).
|
||||
handle {
|
||||
root * /srv/galaxy-site
|
||||
# VitePress emits hash-named assets under `/assets/`.
|
||||
@immutable path /assets/*
|
||||
header @immutable Cache-Control "public, max-age=31536000, immutable"
|
||||
@dynamic not path /assets/*
|
||||
header @dynamic Cache-Control "no-cache, must-revalidate"
|
||||
try_files {path} {path}.html {path}/index.html /404.html
|
||||
file_server
|
||||
encode zstd gzip
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
# Production placeholder. Mirrors `Caddyfile.dev` but uses real
|
||||
# hostnames and lets Caddy auto-provision TLS certificates. Not used
|
||||
# until prod-deploy plumbing exists; kept under version control so the
|
||||
# dev/prod surface stays symmetric.
|
||||
# Production placeholder. Single-origin, path-based — mirrors
|
||||
# `Caddyfile.dev` but binds the real public host and lets Caddy
|
||||
# auto-provision TLS. Not used until prod-deploy plumbing exists; kept
|
||||
# in version control so the dev/prod surface stays identical.
|
||||
#
|
||||
# The host is supplied at deploy time via `GALAXY_PUBLIC_HOST` so the
|
||||
# same image is domain-agnostic (the fallback is only a placeholder).
|
||||
|
||||
www.galaxy.com {
|
||||
root * /srv/galaxy-ui
|
||||
{$GALAXY_PUBLIC_HOST:galaxy.example} {
|
||||
handle_path /rpc/* {
|
||||
reverse_proxy galaxy-api:9090
|
||||
}
|
||||
|
||||
# Mirrors the cache policy `Caddyfile.dev` documents in detail:
|
||||
# SvelteKit's hash-named `_app/immutable/*` is safe to cache
|
||||
# forever; everything else must revalidate so a deploy reaches
|
||||
# the browser without a manual cache clear.
|
||||
@immutable path /_app/immutable/*
|
||||
header @immutable Cache-Control "public, max-age=31536000, immutable"
|
||||
@dynamic not path /_app/immutable/*
|
||||
header @dynamic Cache-Control "no-cache, must-revalidate"
|
||||
@api path /api/* /healthz
|
||||
handle @api {
|
||||
reverse_proxy galaxy-api:8080
|
||||
}
|
||||
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
encode zstd gzip
|
||||
}
|
||||
|
||||
api.galaxy.com {
|
||||
reverse_proxy galaxy-api:8080
|
||||
handle /game {
|
||||
redir * /game/ 308
|
||||
}
|
||||
|
||||
handle_path /game/* {
|
||||
root * /srv/galaxy-ui
|
||||
@immutable path /_app/immutable/*
|
||||
header @immutable Cache-Control "public, max-age=31536000, immutable"
|
||||
@dynamic not path /_app/immutable/*
|
||||
header @dynamic Cache-Control "no-cache, must-revalidate"
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
encode zstd gzip
|
||||
}
|
||||
|
||||
handle {
|
||||
root * /srv/galaxy-site
|
||||
@immutable path /assets/*
|
||||
header @immutable Cache-Control "public, max-age=31536000, immutable"
|
||||
@dynamic not path /assets/*
|
||||
header @dynamic Cache-Control "no-cache, must-revalidate"
|
||||
try_files {path} {path}.html {path}/index.html /404.html
|
||||
file_server
|
||||
encode zstd gzip
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: help up down rebuild logs status clean-data health psql build-engine seed-ui seed-geoip
|
||||
.PHONY: help up down rebuild logs status clean-data health psql build-engine seed-ui seed-site seed-geoip
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
@@ -6,6 +6,9 @@ REPO_ROOT := $(realpath $(CURDIR)/../..)
|
||||
ENGINE_IMAGE := galaxy-engine:dev
|
||||
STACK_LABEL := galaxy.stack=dev-deploy
|
||||
ENGINE_LABEL := org.opencontainers.image.title=galaxy-game-engine
|
||||
# Public host the in-front host Caddy serves the single-origin stack on.
|
||||
# Used only by `make health` probes; override for a different domain.
|
||||
GALAXY_DEV_HOST ?= galaxy.lan
|
||||
# Game-state root lives under the invoking user's home by default so
|
||||
# `make up` works without sudo. Override `GALAXY_DEV_GAME_STATE_DIR`
|
||||
# in the environment or `.env` to relocate (e.g. /var/lib/galaxy-dev/
|
||||
@@ -17,11 +20,12 @@ export GALAXY_DEV_GAME_STATE_DIR ?= $(HOME)/.galaxy-dev/game-state
|
||||
COMPOSE := docker compose
|
||||
|
||||
help:
|
||||
@echo "Long-lived Galaxy dev environment (https://*.galaxy.lan):"
|
||||
@echo "Long-lived Galaxy dev environment (single-origin, e.g. https://galaxy.lan):"
|
||||
@echo " make up Build images, ensure engine image, seed geoip, bring stack up"
|
||||
@echo " make rebuild Force rebuild of backend / gateway images and bring up"
|
||||
@echo " make build-engine Build $(ENGINE_IMAGE) from game/Dockerfile (no-op if present)"
|
||||
@echo " make seed-ui Build ui/frontend and load into galaxy-dev-ui-dist volume"
|
||||
@echo " make seed-site Build site/ (VitePress) and load into galaxy-dev-site-dist volume"
|
||||
@echo " make seed-geoip Copy GeoIP fixture into galaxy-dev-geoip-data volume"
|
||||
@echo " make down Stop containers, keep named volumes"
|
||||
@echo " make logs Tail all logs"
|
||||
@@ -33,7 +37,7 @@ help:
|
||||
@echo "Requires:"
|
||||
@echo " - external Docker network '$${GALAXY_EDGE_NETWORK:-edge}'"
|
||||
@echo " (docker network create edge)"
|
||||
@echo " - host Caddy proxying *.galaxy.lan into that network"
|
||||
@echo " - host Caddy proxying the public host into that network"
|
||||
@echo " - game-state dir: $(GALAXY_DEV_GAME_STATE_DIR) (auto-created)"
|
||||
|
||||
up: build-engine seed-geoip
|
||||
@@ -76,7 +80,8 @@ seed-ui:
|
||||
fi
|
||||
@echo "building UI (vite build)…"
|
||||
(cd $(REPO_ROOT)/ui/frontend && \
|
||||
VITE_GATEWAY_BASE_URL=https://api.galaxy.lan \
|
||||
VITE_GATEWAY_BASE_URL= \
|
||||
BASE_PATH=/game \
|
||||
VITE_GALAXY_DEV_AFFORDANCES=true \
|
||||
VITE_GATEWAY_RESPONSE_PUBLIC_KEY=$$(cat $(REPO_ROOT)/ui/frontend/.env.development \
|
||||
| sed -n 's/^VITE_GATEWAY_RESPONSE_PUBLIC_KEY=//p') \
|
||||
@@ -88,6 +93,23 @@ seed-ui:
|
||||
-v $(REPO_ROOT)/ui/frontend/build:/src:ro \
|
||||
alpine sh -c 'rm -rf /dst/* /dst/.??* 2>/dev/null; cp -a /src/. /dst/'
|
||||
|
||||
# Build the project site (VitePress) and load the static output into the
|
||||
# named volume Caddy serves at the root. Used by the dev-deploy workflow
|
||||
# and by anyone bringing the stack up by hand.
|
||||
seed-site:
|
||||
@if [ ! -d $(REPO_ROOT)/site/node_modules ]; then \
|
||||
echo "installing site dependencies…"; \
|
||||
(cd $(REPO_ROOT)/site && pnpm install --frozen-lockfile); \
|
||||
fi
|
||||
@echo "building project site (vitepress build)…"
|
||||
(cd $(REPO_ROOT)/site && pnpm build)
|
||||
@echo "loading site dist into galaxy-dev-site-dist volume…"
|
||||
docker volume create galaxy-dev-site-dist >/dev/null
|
||||
docker run --rm \
|
||||
-v galaxy-dev-site-dist:/dst \
|
||||
-v $(REPO_ROOT)/site/.vitepress/dist:/src:ro \
|
||||
alpine sh -c 'rm -rf /dst/* /dst/.??* 2>/dev/null; cp -a /src/. /dst/'
|
||||
|
||||
down:
|
||||
$(COMPOSE) down
|
||||
|
||||
@@ -98,10 +120,12 @@ status:
|
||||
$(COMPOSE) ps
|
||||
|
||||
health:
|
||||
@echo "Frontend (https://www.galaxy.lan):"
|
||||
@curl -sS -o /dev/null -w " HTTP %{http_code}\n" https://www.galaxy.lan/ || echo " unreachable"
|
||||
@echo "API healthz (https://api.galaxy.lan/healthz):"
|
||||
@curl -sS -o /dev/null -w " HTTP %{http_code}\n" https://api.galaxy.lan/healthz || echo " unreachable"
|
||||
@echo "Site (https://$(GALAXY_DEV_HOST)/):"
|
||||
@curl -sS -o /dev/null -w " HTTP %{http_code}\n" https://$(GALAXY_DEV_HOST)/ || echo " unreachable"
|
||||
@echo "Game (https://$(GALAXY_DEV_HOST)/game/):"
|
||||
@curl -sS -o /dev/null -w " HTTP %{http_code}\n" https://$(GALAXY_DEV_HOST)/game/ || echo " unreachable"
|
||||
@echo "API healthz (https://$(GALAXY_DEV_HOST)/healthz):"
|
||||
@curl -sS -o /dev/null -w " HTTP %{http_code}\n" https://$(GALAXY_DEV_HOST)/healthz || echo " unreachable"
|
||||
|
||||
psql:
|
||||
$(COMPOSE) exec galaxy-postgres psql -U galaxy -d galaxy_backend
|
||||
|
||||
+57
-29
@@ -2,11 +2,26 @@
|
||||
|
||||
A docker-compose stack that runs the Galaxy backend, gateway, supporting
|
||||
services, and a small Caddy in front of them, reachable through the host
|
||||
Caddy at `https://www.galaxy.lan` and `https://api.galaxy.lan`. Used by
|
||||
the `dev-deploy.yaml` Gitea Actions workflow as the canonical dev target
|
||||
on every merge into the `development` branch, and runnable by hand
|
||||
through this Makefile for local debugging of the deploy plumbing
|
||||
itself.
|
||||
Caddy at a single origin (`https://galaxy.lan` in dev). The stack is
|
||||
single-origin and path-based: the project site, the game UI, and both
|
||||
gateway surfaces live behind one host with no host name baked into the
|
||||
artifacts. Used by the `dev-deploy.yaml` Gitea Actions workflow as the
|
||||
canonical dev target on every merge into the `development` branch, and
|
||||
runnable by hand through this Makefile for local debugging of the deploy
|
||||
plumbing itself.
|
||||
|
||||
The application Caddy (`Caddyfile.dev`) is the authoritative routing
|
||||
source; its header comment documents the exact topology:
|
||||
|
||||
```text
|
||||
/ -> project site (galaxy-dev-site-dist -> /srv/galaxy-site)
|
||||
/game/* -> game UI (galaxy-dev-ui-dist -> /srv/galaxy-ui)
|
||||
/api/*, /healthz -> gateway public REST (galaxy-api:8080)
|
||||
/rpc/* -> gateway Connect/gRPC-web (galaxy-api:9090)
|
||||
```
|
||||
|
||||
The `/rpc` prefix is stripped before the gateway, and the game UI bundle
|
||||
is built with base path `/game`.
|
||||
|
||||
This stack is **not** the developer's primary playground for UI work —
|
||||
that role still belongs to [`tools/local-dev/`](../local-dev/README.md),
|
||||
@@ -38,11 +53,13 @@ The host must already provide:
|
||||
```
|
||||
|
||||
- A host Caddy listening on `:80`/`:443`, attached to the `edge`
|
||||
network, and proxying `www.galaxy.lan` and `api.galaxy.lan` to
|
||||
`galaxy-caddy:80`. Example fragment for the host Caddyfile:
|
||||
network, and proxying the single dev host `galaxy.lan` to
|
||||
`galaxy-caddy:80`. The host Caddy only needs that one host;
|
||||
`Caddyfile.dev` does the path-based fan-out behind it. Example
|
||||
fragment for the host Caddyfile:
|
||||
|
||||
```caddy
|
||||
www.galaxy.lan, api.galaxy.lan {
|
||||
galaxy.lan {
|
||||
tls internal
|
||||
reverse_proxy galaxy-caddy:80
|
||||
}
|
||||
@@ -62,25 +79,28 @@ make -C tools/dev-deploy up
|
||||
|
||||
`up` (re)builds the local-dev backend and gateway images, makes sure the
|
||||
engine image `galaxy-engine:dev` exists, and waits for healthchecks. It
|
||||
does **not** seed the UI volume — that is normally done by CI. The first
|
||||
time you run by hand:
|
||||
does **not** seed the UI or site volumes — that is normally done by CI.
|
||||
The first time you run by hand:
|
||||
|
||||
```sh
|
||||
make -C tools/dev-deploy seed-site
|
||||
make -C tools/dev-deploy seed-ui
|
||||
make -C tools/dev-deploy up
|
||||
make -C tools/dev-deploy health
|
||||
```
|
||||
|
||||
`seed-ui` runs `pnpm build` in `ui/frontend/`, then copies the resulting
|
||||
`build/` tree into the `galaxy-dev-ui-dist` volume. Subsequent CI deploys
|
||||
overwrite this volume automatically.
|
||||
`seed-ui` runs `pnpm build` in `ui/frontend/` (base path `/game`), then
|
||||
copies the resulting `build/` tree into the `galaxy-dev-ui-dist` volume.
|
||||
`seed-site` builds the VitePress project site in `site/` and copies its
|
||||
`.vitepress/dist/` output into the `galaxy-dev-site-dist` volume.
|
||||
Subsequent CI deploys overwrite both volumes automatically.
|
||||
|
||||
## Daily flow
|
||||
|
||||
```sh
|
||||
make -C tools/dev-deploy rebuild # rebuild backend/gateway images + up
|
||||
make -C tools/dev-deploy logs # tail compose logs
|
||||
make -C tools/dev-deploy health # probe https://*.galaxy.lan
|
||||
make -C tools/dev-deploy health # probe https://galaxy.lan/ , /game/ , /healthz
|
||||
make -C tools/dev-deploy down # stop, keep state
|
||||
```
|
||||
|
||||
@@ -109,14 +129,16 @@ cannot leak into the prod environment.
|
||||
|
||||
```
|
||||
Browser
|
||||
│ https://www.galaxy.lan, https://api.galaxy.lan
|
||||
│ https://galaxy.lan/ (one origin, path-based)
|
||||
▼
|
||||
host-Caddy (:80, :443, TLS, attached to `edge` network)
|
||||
│ reverse_proxy *.galaxy.lan → galaxy-caddy:80
|
||||
│ reverse_proxy galaxy.lan → galaxy-caddy:80
|
||||
▼
|
||||
galaxy-caddy (networks: edge + galaxy-dev-internal)
|
||||
│ www.galaxy.lan → file_server /srv/galaxy-ui (volume galaxy-dev-ui-dist)
|
||||
│ api.galaxy.lan → reverse_proxy galaxy-api:8080
|
||||
│ / -> file_server /srv/galaxy-site (volume galaxy-dev-site-dist)
|
||||
│ /game/* -> file_server /srv/galaxy-ui (volume galaxy-dev-ui-dist)
|
||||
│ /api/*, /healthz -> reverse_proxy galaxy-api:8080
|
||||
│ /rpc/* -> reverse_proxy galaxy-api:9090 (strips /rpc)
|
||||
▼
|
||||
galaxy-dev-internal
|
||||
├─ galaxy-api (gateway: :8080 REST, :9090 gRPC)
|
||||
@@ -155,13 +177,14 @@ The same volume-persistence model applies to `tools/local-dev/`.
|
||||
```text
|
||||
make up Build images, ensure engine image, seed geoip, bring stack up
|
||||
make rebuild Rebuild backend / gateway images (ignores cache), then up
|
||||
make seed-ui pnpm build + load build/ into galaxy-dev-ui-dist volume
|
||||
make seed-ui pnpm build (base /game) + load build/ into galaxy-dev-ui-dist volume
|
||||
make seed-site vitepress build + load site dist into galaxy-dev-site-dist volume
|
||||
make seed-geoip Copy pkg/geoip fixture into galaxy-dev-geoip-data volume
|
||||
make build-engine Build galaxy-engine:dev (no-op if image already present)
|
||||
make down Stop containers, keep named volumes
|
||||
make logs Tail compose logs
|
||||
make status docker compose ps
|
||||
make health curl https://www.galaxy.lan + https://api.galaxy.lan/healthz
|
||||
make health curl https://galaxy.lan/ , /game/ , and /healthz
|
||||
make psql psql as galaxy@galaxy_backend
|
||||
make clean-data Stop everything and wipe volumes + game-state dir
|
||||
```
|
||||
@@ -169,15 +192,20 @@ make clean-data Stop everything and wipe volumes + game-state dir
|
||||
## Files
|
||||
|
||||
- `docker-compose.yml` — six services: postgres, redis, mailpit,
|
||||
galaxy-backend, galaxy-api, galaxy-caddy. Reuses the alpine-runtime
|
||||
Dockerfiles from `../local-dev/` so the backend healthcheck can run
|
||||
`wget`. Reuses the dev keypair from `../local-dev/keys/`.
|
||||
- `Caddyfile.dev` — the application-routing Caddy config, mounted into
|
||||
`galaxy-caddy` at `/etc/caddy/Caddyfile`.
|
||||
galaxy-backend, galaxy-api, galaxy-caddy. `galaxy-caddy` mounts both
|
||||
the `galaxy-dev-site-dist` (`/srv/galaxy-site`) and
|
||||
`galaxy-dev-ui-dist` (`/srv/galaxy-ui`) volumes and reverse-proxies
|
||||
both gateway tiers (REST/health on `:8080`, Connect/gRPC-web on
|
||||
`:9090`). Reuses the alpine-runtime Dockerfiles from `../local-dev/`
|
||||
so the backend healthcheck can run `wget`. Reuses the dev keypair
|
||||
from `../local-dev/keys/`.
|
||||
- `Caddyfile.dev` — the application-routing Caddy config and the
|
||||
authoritative single-origin path topology, mounted into `galaxy-caddy`
|
||||
at `/etc/caddy/Caddyfile`.
|
||||
- `Caddyfile.prod` — placeholder for a future prod deployment; not used
|
||||
by this compose.
|
||||
- `Makefile` — wrapper over `docker compose` with helpers for engine,
|
||||
UI seeding, health probes, and full wipe.
|
||||
site/UI seeding, health probes, and full wipe.
|
||||
- `.env.example` — non-secret defaults for the compose `${VAR:-}`
|
||||
expansions. Copy to `.env` if you want host-local overrides.
|
||||
|
||||
@@ -212,6 +240,6 @@ behind. There is no separate state to clean up between the two paths.
|
||||
- `tools/local-dev/` — single-developer playground, host-port mapped,
|
||||
Vite dev server on the side. Recommended for active UI work.
|
||||
- `.gitea/workflows/dev-deploy.yaml` — the CI side of this stack:
|
||||
builds images, seeds the UI volume, runs `docker compose up -d` on
|
||||
every merge into `development`. The Makefile in this directory is
|
||||
what that workflow ultimately calls into.
|
||||
builds images, seeds the site and UI volumes, runs `docker compose
|
||||
up -d` on every merge into `development`. The Makefile in this
|
||||
directory is what that workflow ultimately calls into.
|
||||
|
||||
@@ -186,11 +186,14 @@ services:
|
||||
GATEWAY_RESPONSE_SIGNER_PRIVATE_KEY_PEM_PATH: /run/secrets/gateway-response.pem
|
||||
GATEWAY_REDIS_MASTER_ADDR: "galaxy-redis:6379"
|
||||
GATEWAY_REDIS_PASSWORD: galaxy-dev
|
||||
# UI lives on https://www.galaxy.lan; the API is on
|
||||
# https://api.galaxy.lan. Browsers therefore issue cross-origin
|
||||
# requests to the gateway and need an explicit allow-list.
|
||||
GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS: "https://www.galaxy.lan"
|
||||
GATEWAY_AUTHENTICATED_GRPC_CORS_ALLOWED_ORIGINS: "https://www.galaxy.lan"
|
||||
# Single-origin deployment: the UI, public REST, and Connect-Web
|
||||
# edge share one host, so browser requests are same-origin and
|
||||
# CORS is not needed. An empty allow-list disables the CORS
|
||||
# middleware (requests pass through without Access-Control-*
|
||||
# headers). Re-populate these only if a future deploy fronts the
|
||||
# gateway on a different host than the UI.
|
||||
GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS: ""
|
||||
GATEWAY_AUTHENTICATED_GRPC_CORS_ALLOWED_ORIGINS: ""
|
||||
# Anti-abuse defaults are looser than production: the dev
|
||||
# environment is shared by a handful of trusted testers who
|
||||
# frequently hammer the same identity to reproduce flows.
|
||||
@@ -237,6 +240,7 @@ services:
|
||||
- ./Caddyfile.dev:/etc/caddy/Caddyfile:ro
|
||||
- galaxy-dev-caddy-data:/data
|
||||
- galaxy-dev-ui-dist:/srv/galaxy-ui:ro
|
||||
- galaxy-dev-site-dist:/srv/galaxy-site:ro
|
||||
networks:
|
||||
- galaxy-internal
|
||||
- edge
|
||||
@@ -266,5 +270,7 @@ volumes:
|
||||
name: galaxy-dev-caddy-data
|
||||
galaxy-dev-ui-dist:
|
||||
name: galaxy-dev-ui-dist
|
||||
galaxy-dev-site-dist:
|
||||
name: galaxy-dev-site-dist
|
||||
galaxy-dev-geoip-data:
|
||||
name: galaxy-dev-geoip-data
|
||||
|
||||
@@ -14,8 +14,8 @@ This stack is **not** a CI gate (the per-stage CI gate now lives on
|
||||
`gitea.lan`; see project-level `CLAUDE.md`). It is also distinct from
|
||||
the **long-lived dev environment** at
|
||||
[`tools/dev-deploy/`](../dev-deploy/README.md), which is redeployed on
|
||||
every merge into `development` and is reachable as
|
||||
`https://www.galaxy.lan` / `https://api.galaxy.lan`. The two stacks
|
||||
every merge into `development` and is reachable at the single origin
|
||||
`https://galaxy.lan` (site at `/`, game UI at `/game/`). The two stacks
|
||||
(`tools/local-dev/` and `tools/dev-deploy/`) coexist on the same host
|
||||
because every name — compose project, container, network, volume — is
|
||||
distinct.
|
||||
@@ -131,7 +131,7 @@ host compose network "galaxy-local-de
|
||||
┌────────────────────────────────┐ ┌──────────────────────────────┐
|
||||
│ browser localhost:5173 │── pnpm dev (Vite, host) ──┐ │
|
||||
│ ↳ /api/* proxied ───┼──────────────────────────▶│ gateway:8080 │
|
||||
│ ↳ /galaxy.gateway... ┼──────────────────────────▶│ │
|
||||
│ ↳ /rpc/* proxied ───┼──────────────────────────▶│ gateway:9090 │
|
||||
│ browser localhost:8025 │─────────────────────────▶│ mailpit:8025 │
|
||||
│ psql localhost:5433 │─────────────────────────▶│ postgres:5432 │
|
||||
│ redis-cli localhost:6380 │─────────────────────────▶│ redis:6379 │
|
||||
@@ -141,8 +141,9 @@ host compose network "galaxy-local-de
|
||||
└────────────────────────────────┘
|
||||
```
|
||||
|
||||
Vite's dev server proxies `/api` and `/galaxy.gateway.v1.EdgeGateway`
|
||||
to the gateway, so every browser request stays same-origin (no CORS
|
||||
Vite's dev server proxies `/api` (to the gateway REST listener) and
|
||||
`/rpc` (to the authenticated Connect/gRPC-Web listener, stripping the
|
||||
`/rpc` prefix), so every browser request stays same-origin (no CORS
|
||||
preflight). The gateway is therefore reachable only through Vite at
|
||||
<http://localhost:5173>, not at <http://localhost:8080> from the
|
||||
browser tab. Direct curl/wget against <http://localhost:8080> still
|
||||
@@ -291,9 +292,9 @@ make status docker compose ps
|
||||
## Relationship to other infrastructure
|
||||
|
||||
- `tools/dev-deploy/` — long-lived dev environment redeployed on every
|
||||
merge into `development`; reachable at `https://www.galaxy.lan` /
|
||||
`https://api.galaxy.lan`. Distinct compose project, container names,
|
||||
network and volumes.
|
||||
merge into `development`; reachable at the single origin
|
||||
`https://galaxy.lan` (site at `/`, game UI at `/game/`). Distinct
|
||||
compose project, container names, network and volumes.
|
||||
- `integration/testenv/` — testcontainers harness used by
|
||||
`make -C integration integration`. Uses the canonical
|
||||
`backend/Dockerfile` / `gateway/Dockerfile` at production defaults;
|
||||
|
||||
@@ -180,7 +180,7 @@ services:
|
||||
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST: "1000"
|
||||
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_REQUESTS: "10000"
|
||||
GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST: "1000"
|
||||
# public_misc class wraps the authenticated EdgeGateway gRPC
|
||||
# public_misc class wraps the authenticated edge.v1.Gateway gRPC
|
||||
# endpoints (ExecuteCommand, SubscribeEvents). The gateway's
|
||||
# default for this class is 0 bytes, which rejects every
|
||||
# non-empty body with HTTP 413; override with a generous limit
|
||||
@@ -200,7 +200,7 @@ services:
|
||||
GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_BURST: "1000"
|
||||
ports:
|
||||
- "${LOCAL_DEV_GATEWAY_REST_PORT:-8080}:8080"
|
||||
# Authenticated EdgeGateway connect-web/gRPC listener. The
|
||||
# Authenticated edge.v1.Gateway connect-web/gRPC listener. The
|
||||
# browser reaches it via the Vite dev proxy in
|
||||
# ui/frontend/vite.config.ts.
|
||||
- "${LOCAL_DEV_GATEWAY_GRPC_PORT:-9090}:9090"
|
||||
|
||||
+6
-6
@@ -474,11 +474,11 @@ documents the historical labelling in its package doc.
|
||||
Artifacts (delivered):
|
||||
|
||||
- `gateway/buf.gen.yaml` extended with `buf.build/connectrpc/go`,
|
||||
generating `gateway/proto/galaxy/gateway/v1/gatewayv1connect/edge_gateway.connect.go`
|
||||
generating `gateway/proto/edge/v1/edgev1connect/edge_gateway.connect.go`
|
||||
- `gateway/internal/grpcapi/server.go` rewritten around `http.Server`
|
||||
+ `h2c.NewHandler` + `gatewayv1connect.NewEdgeGatewayHandler`
|
||||
+ `h2c.NewHandler` + `edgev1connect.NewGatewayHandler`
|
||||
- new `gateway/internal/grpcapi/connect_handler.go` adapting the
|
||||
existing `gatewayv1.EdgeGatewayServer` decorator stack to the
|
||||
existing `edgev1.GatewayServer` decorator stack to the
|
||||
Connect handler interface, including a `grpc.ServerStreamingServer`
|
||||
shim around `*connect.ServerStream[GatewayEvent]` and a gRPC
|
||||
`status.Error` → `*connect.Error` translation helper
|
||||
@@ -492,7 +492,7 @@ Artifacts (delivered):
|
||||
`gateway/docs/runbook.md`, and `docs/ARCHITECTURE.md` §15
|
||||
- migrated tests: `gateway/internal/grpcapi/server_test.go`,
|
||||
`test_fixtures_test.go`, and every `*_integration_test.go` in that
|
||||
package now drive a `gatewayv1connect.EdgeGatewayClient` over
|
||||
package now drive a `edgev1connect.GatewayClient` over
|
||||
HTTP/2 cleartext loopback
|
||||
- migrated harness: `integration/testenv/grpc_client.go` →
|
||||
`connect_client.go`. `SignedGatewayClient` keeps the same public
|
||||
@@ -580,10 +580,10 @@ Artifacts (delivered):
|
||||
browsers; the JSDOM test path lives next to it in
|
||||
`ui/frontend/tests/setup-wasm.ts`.
|
||||
- `ui/frontend/src/api/connect.ts` — typed Connect-Web transport +
|
||||
`EdgeGatewayClient` factory.
|
||||
`GatewayClient` factory.
|
||||
- `ui/frontend/src/api/galaxy-client.ts` — `GalaxyClient` skeleton
|
||||
with injected `Signer` and `Sha256` dependencies.
|
||||
- `ui/frontend/src/proto/galaxy/gateway/v1/edge_gateway_pb.ts`
|
||||
- `ui/frontend/src/proto/edge/v1/edge_gateway_pb.ts`
|
||||
(generated) and `ui/frontend/src/proto/buf/validate/validate_pb.ts`
|
||||
(generated as a transitive import via `--include-imports`).
|
||||
- `ui/frontend/static/core.wasm` (903 KB) + `wasm_exec.js` (TinyGo
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package types defines the v1 transport envelopes carried over the
|
||||
// wire between the Galaxy client and gateway. Envelope shapes mirror
|
||||
// the protobuf messages in `gateway/proto/galaxy/gateway/v1/`, but are
|
||||
// the protobuf messages in `gateway/proto/edge/v1/`, but are
|
||||
// kept as plain Go structs here so the UI client can read and produce
|
||||
// them without depending on the protobuf runtime in WASM and gomobile
|
||||
// builds.
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
|
||||
# Gateway public REST + Connect-Web edge listener. Points at the Vite
|
||||
# dev server's own origin so the browser sees same-origin requests;
|
||||
# Vite then proxies `/api` and `/galaxy.gateway.v1.EdgeGateway` to the
|
||||
# real gateway at `http://localhost:8080`. See `vite.config.ts`. To
|
||||
# work against a non-local gateway, override the proxy target via
|
||||
# `VITE_DEV_PROXY_TARGET=http://gateway.host:8080 pnpm dev` (no UI
|
||||
# Vite then proxies `/api` to the REST listener (`:8080`) and `/rpc` to
|
||||
# the Connect listener (`:9090`, prefix stripped), mirroring the
|
||||
# single-origin edge Caddy. See `vite.config.ts`. To work against a
|
||||
# non-local gateway, override the proxy targets via
|
||||
# `VITE_DEV_PROXY_TARGET` / `VITE_DEV_GRPC_PROXY_TARGET` (no UI
|
||||
# rebuild needed).
|
||||
VITE_GATEWAY_BASE_URL=http://localhost:5173
|
||||
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
// `createEdgeGatewayClient` builds a typed Connect-Web client for the
|
||||
// `createGatewayClient` builds a typed Connect-Web client for the
|
||||
// gateway's authenticated edge surface. It speaks the Connect protocol
|
||||
// over HTTP/1.1 (or HTTP/2 if the host upgrades the connection) — the
|
||||
// gateway listener built in Phase 4 natively serves Connect, gRPC, and
|
||||
// gRPC-Web on the same h2c port.
|
||||
//
|
||||
// The factory is intentionally thin: callers provide the gateway base
|
||||
// URL (e.g. https://api.galaxy.test), and receive a typed
|
||||
// `EdgeGatewayClient`. Authentication, signing, and response
|
||||
// The factory is intentionally thin: callers provide the Connect base
|
||||
// URL (the same-origin `/rpc` prefix from `gatewayRpcBaseUrl`), and
|
||||
// receive a typed
|
||||
// `GatewayClient`. Authentication, signing, and response
|
||||
// verification live one layer up, in `GalaxyClient`.
|
||||
|
||||
import { createClient, type Client } from "@connectrpc/connect";
|
||||
import { createConnectTransport } from "@connectrpc/connect-web";
|
||||
import { EdgeGateway } from "../proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { Gateway } from "../proto/edge/v1/edge_gateway_pb";
|
||||
|
||||
export type EdgeGatewayClient = Client<typeof EdgeGateway>;
|
||||
export type GatewayClient = Client<typeof Gateway>;
|
||||
|
||||
export function createEdgeGatewayClient(baseUrl: string): EdgeGatewayClient {
|
||||
return createClient(EdgeGateway, createConnectTransport({ baseUrl }));
|
||||
export function createGatewayClient(baseUrl: string): GatewayClient {
|
||||
return createClient(Gateway, createConnectTransport({ baseUrl }));
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ import type { DeviceKeypair } from "../platform/store/index";
|
||||
import {
|
||||
SubscribeEventsRequestSchema,
|
||||
type GatewayEvent,
|
||||
} from "../proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { GATEWAY_BASE_URL } from "../lib/env";
|
||||
} from "../proto/edge/v1/edge_gateway_pb";
|
||||
import { gatewayRpcBaseUrl } from "../lib/env";
|
||||
import { session } from "../lib/session-store.svelte";
|
||||
import { createEdgeGatewayClient, type EdgeGatewayClient } from "./connect";
|
||||
import { createGatewayClient, type GatewayClient } from "./connect";
|
||||
|
||||
const PROTOCOL_VERSION = "v1";
|
||||
const SUBSCRIBE_MESSAGE_TYPE = "gateway.subscribe";
|
||||
@@ -83,7 +83,7 @@ export type ConnectionStatus =
|
||||
* consumer cannot resolve by itself. Production code reads `core`,
|
||||
* `keypair`, and `deviceSessionId` from the session store and the
|
||||
* gateway public key from `lib/env`; tests inject a fake
|
||||
* `EdgeGatewayClient` and deterministic `sleep`/`random` to drive
|
||||
* `GatewayClient` and deterministic `sleep`/`random` to drive
|
||||
* backoff in fake-timer mode.
|
||||
*/
|
||||
export interface EventStreamStartOptions {
|
||||
@@ -91,8 +91,8 @@ export interface EventStreamStartOptions {
|
||||
keypair: DeviceKeypair;
|
||||
deviceSessionId: string;
|
||||
gatewayResponsePublicKey: Uint8Array;
|
||||
/** Custom transport client. Defaults to `createEdgeGatewayClient(GATEWAY_BASE_URL)`. */
|
||||
client?: EdgeGatewayClient;
|
||||
/** Custom transport client. Defaults to `createGatewayClient(gatewayRpcBaseUrl())`. */
|
||||
client?: GatewayClient;
|
||||
/** Sleep hook for tests; defaults to a real-time `setTimeout`. */
|
||||
sleep?: (ms: number) => Promise<void>;
|
||||
/** Random source for full-jitter backoff; defaults to `Math.random`. */
|
||||
@@ -189,7 +189,7 @@ export class EventStream {
|
||||
const sleep = opts.sleep ?? defaultSleep;
|
||||
const random = opts.random ?? Math.random;
|
||||
const onlineProbe = opts.onlineProbe ?? defaultOnlineProbe;
|
||||
const client = opts.client ?? createEdgeGatewayClient(GATEWAY_BASE_URL);
|
||||
const client = opts.client ?? createGatewayClient(gatewayRpcBaseUrl());
|
||||
|
||||
let attempt = 0;
|
||||
while (!signal.aborted && this.running) {
|
||||
@@ -311,7 +311,7 @@ export class EventStream {
|
||||
}
|
||||
|
||||
async function openStream(
|
||||
client: EdgeGatewayClient,
|
||||
client: GatewayClient,
|
||||
opts: EventStreamStartOptions,
|
||||
signal: AbortSignal,
|
||||
): Promise<AsyncIterable<GatewayEvent>> {
|
||||
|
||||
@@ -15,8 +15,8 @@ import type { Core } from "../platform/core/index";
|
||||
import {
|
||||
ExecuteCommandRequestSchema,
|
||||
type ExecuteCommandResponse,
|
||||
} from "../proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import type { EdgeGatewayClient } from "./connect";
|
||||
} from "../proto/edge/v1/edge_gateway_pb";
|
||||
import type { GatewayClient } from "./connect";
|
||||
|
||||
/**
|
||||
* Signer produces a raw 64-byte Ed25519 signature over canonicalBytes.
|
||||
@@ -35,7 +35,7 @@ export type Sha256 = (payload: Uint8Array) => Promise<Uint8Array>;
|
||||
|
||||
export interface GalaxyClientOptions {
|
||||
core: Core;
|
||||
edge: EdgeGatewayClient;
|
||||
edge: GatewayClient;
|
||||
signer: Signer;
|
||||
sha256: Sha256;
|
||||
deviceSessionId: string;
|
||||
@@ -53,7 +53,7 @@ export interface ExecuteCommandResult {
|
||||
|
||||
export class GalaxyClient {
|
||||
private readonly core: Core;
|
||||
private readonly edge: EdgeGatewayClient;
|
||||
private readonly edge: GatewayClient;
|
||||
private readonly signer: Signer;
|
||||
private readonly sha256: Sha256;
|
||||
private readonly deviceSessionId: string;
|
||||
|
||||
@@ -12,6 +12,7 @@ header now — we just hand the routes down as callbacks so the
|
||||
viewer keeps its prop-driven contract.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { getContext } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
@@ -126,10 +127,10 @@ viewer keeps its prop-driven contract.
|
||||
});
|
||||
|
||||
function backToReport() {
|
||||
goto(`/games/${gameId}/report`);
|
||||
goto(withBase(`/games/${gameId}/report`));
|
||||
}
|
||||
function backToMap() {
|
||||
goto(`/games/${gameId}/map`);
|
||||
goto(withBase(`/games/${gameId}/map`));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ fractions is a Phase 21 decision documented in
|
||||
`ui/docs/science-designer-ux.md`.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { getContext, tick } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
@@ -125,7 +126,7 @@ fractions is a Phase 21 decision documented in
|
||||
}
|
||||
|
||||
function backToTable(): void {
|
||||
void goto(`/games/${gameId}/table/sciences`);
|
||||
void goto(withBase(`/games/${gameId}/table/sciences`));
|
||||
}
|
||||
|
||||
async function save(): Promise<void> {
|
||||
|
||||
@@ -20,6 +20,7 @@ Phase 29 wires the wrap-mode toggle on top of the per-game `wrapMode`
|
||||
preference the store already manages.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { getContext, onDestroy, onMount, untrack } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
@@ -636,14 +637,14 @@ preference the store already manages.
|
||||
const gameId = page.params.id ?? "";
|
||||
const turn = store?.report?.turn ?? 0;
|
||||
void goto(
|
||||
`/games/${gameId}/battle/${target.battleId}?turn=${turn}`,
|
||||
withBase(`/games/${gameId}/battle/${target.battleId}?turn=${turn}`),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "bombing": {
|
||||
const gameId = page.params.id ?? "";
|
||||
void goto(
|
||||
`/games/${gameId}/report#report-bombings`,
|
||||
withBase(`/games/${gameId}/report#report-bombings`),
|
||||
).then(() => {
|
||||
if (typeof document === "undefined") return;
|
||||
const row = document.querySelector(
|
||||
|
||||
@@ -20,6 +20,7 @@ The active section is computed by the orchestrator
|
||||
`activeSlug` prop. The TOC itself owns no observers.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
||||
@@ -62,7 +63,7 @@ The active section is computed by the orchestrator
|
||||
}
|
||||
|
||||
async function backToMap(): Promise<void> {
|
||||
await goto(`/games/${gameId}/map`);
|
||||
await goto(withBase(`/games/${gameId}/map`));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ monospace `<span>`; the rewire here is the one-liner the Phase 23
|
||||
decision log called out.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { getContext } from "svelte";
|
||||
import { page } from "$app/state";
|
||||
|
||||
@@ -47,7 +48,7 @@ decision log called out.
|
||||
</span>
|
||||
<a
|
||||
class="uuid"
|
||||
href={`/games/${gameId}/battle/${b.id}?turn=${turn}`}
|
||||
href={withBase(`/games/${gameId}/battle/${b.id}?turn=${turn}`)}
|
||||
data-testid="report-battle-row"
|
||||
data-id={b.id}
|
||||
>{b.id}</a>
|
||||
|
||||
@@ -17,6 +17,7 @@ The component sits inside the active-view slot owned by
|
||||
data fetching is performed here — the layout is responsible.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { getContext } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
@@ -117,11 +118,11 @@ data fetching is performed here — the layout is responsible.
|
||||
}
|
||||
|
||||
function openDesigner(name: string): void {
|
||||
void goto(`/games/${gameId}/designer/science/${encodeURIComponent(name)}`);
|
||||
void goto(withBase(`/games/${gameId}/designer/science/${encodeURIComponent(name)}`));
|
||||
}
|
||||
|
||||
function newScience(): void {
|
||||
void goto(`/games/${gameId}/designer/science`);
|
||||
void goto(withBase(`/games/${gameId}/designer/science`));
|
||||
}
|
||||
|
||||
async function deleteScience(name: string): Promise<void> {
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
// at the first import.
|
||||
//
|
||||
// `VITE_GATEWAY_BASE_URL` is the base URL of the gateway public REST
|
||||
// surface and the Connect-Web authenticated edge (same host, same
|
||||
// port; the gateway listener serves both). It defaults to the local
|
||||
// dev address used by `tools/local-ci` and the integration suite.
|
||||
// surface. An empty value means "same origin": the single-origin
|
||||
// deployment serves the UI, the REST surface (`/api/...`), and the
|
||||
// authenticated Connect-Web edge (`/rpc/...`) behind one host, so the
|
||||
// browser issues same-origin requests and needs no absolute base. A
|
||||
// non-empty value (the Vite dev proxy, `tools/local-ci`, the
|
||||
// integration suite) points REST at that absolute host instead. The
|
||||
// Connect base is derived from this value by `gatewayRpcBaseUrl`,
|
||||
// which appends the `/rpc` routing prefix.
|
||||
//
|
||||
// `VITE_GATEWAY_RESPONSE_PUBLIC_KEY` is the gateway's response-signing
|
||||
// Ed25519 public key, encoded as standard (non-URL-safe) base64 of
|
||||
@@ -26,6 +31,21 @@ const RAW_RESPONSE_PUBLIC_KEY: string =
|
||||
|
||||
export const GATEWAY_BASE_URL: string = stripTrailingSlash(RAW_BASE_URL);
|
||||
|
||||
/**
|
||||
* gatewayRpcBaseUrl is the base URL for the authenticated Connect-Web
|
||||
* surface. The edge Caddy and the Vite dev proxy route the `/rpc`
|
||||
* prefix to the gateway's Connect listener, stripping it before the
|
||||
* request reaches the proto-derived `edge.v1.Gateway` service path.
|
||||
* When `GATEWAY_BASE_URL` is empty the gateway shares the document
|
||||
* origin, so the origin is resolved at call time from `window`.
|
||||
*/
|
||||
export function gatewayRpcBaseUrl(): string {
|
||||
const origin =
|
||||
GATEWAY_BASE_URL ||
|
||||
(typeof window !== "undefined" ? window.location.origin : "");
|
||||
return `${origin}/rpc`;
|
||||
}
|
||||
|
||||
export const GATEWAY_RESPONSE_PUBLIC_KEY: Uint8Array = decodeBase64(
|
||||
RAW_RESPONSE_PUBLIC_KEY,
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ navigation. Phase 26 introduces the history-mode entry; Phase 35
|
||||
polishes microcopy.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
||||
@@ -41,7 +42,7 @@ polishes microcopy.
|
||||
|
||||
function go(path: string): void {
|
||||
open = false;
|
||||
void goto(path);
|
||||
void goto(withBase(path));
|
||||
}
|
||||
|
||||
function onKeyDown(event: KeyboardEvent): void {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Base-path helpers for the single-origin deployment. The game UI is
|
||||
// served under `kit.paths.base` — empty at the root for local dev,
|
||||
// vitest, and Playwright, and `/game` in the deployed single-origin
|
||||
// build. SvelteKit does not auto-prefix `goto`, `<a href>`, raw asset
|
||||
// fetches, or the service-worker scope, so every app-internal absolute
|
||||
// path is routed through `withBase`.
|
||||
//
|
||||
// `base` from `$app/paths` is the low-level primitive that `resolve()`
|
||||
// builds on. We use it directly here (rather than `resolve()`) because
|
||||
// the client navigates to and fetches dynamic, runtime-built paths
|
||||
// (command routes, `core.wasm`, the service worker) that `resolve()`'s
|
||||
// statically-typed route-id surface cannot express.
|
||||
import { base } from "$app/paths";
|
||||
|
||||
/** appBase is the configured base path (empty string at the root). */
|
||||
export const appBase = base;
|
||||
|
||||
/**
|
||||
* withBase prefixes an app-internal absolute path (leading slash) with
|
||||
* the configured base path. At the root it returns the path unchanged;
|
||||
* under the single-origin deployment it yields e.g. `/game/lobby`.
|
||||
*/
|
||||
export function withBase(path: string): string {
|
||||
return `${base}${path}`;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ exists; until then the convenience of one source of truth for
|
||||
destinations beats the duplication.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
||||
@@ -47,13 +48,13 @@ destinations beats the duplication.
|
||||
async function selectTool(tool: MobileTool): Promise<void> {
|
||||
moreOpen = false;
|
||||
onSelectTool(tool);
|
||||
await goto(`/games/${gameId}/map`);
|
||||
await goto(withBase(`/games/${gameId}/map`));
|
||||
}
|
||||
|
||||
async function go(path: string): Promise<void> {
|
||||
moreOpen = false;
|
||||
onSelectTool("map");
|
||||
await goto(path);
|
||||
await goto(withBase(path));
|
||||
}
|
||||
|
||||
function toggleMore(): void {
|
||||
|
||||
@@ -34,6 +34,7 @@ import type {
|
||||
WeaponsBlockInput,
|
||||
WeaponsForAttackInput,
|
||||
} from "./index";
|
||||
import { withBase } from "$lib/paths";
|
||||
|
||||
/**
|
||||
* GalaxyCoreBridge is the shape Go installs on `globalThis.galaxyCore`.
|
||||
@@ -143,7 +144,7 @@ async function bootBrowserWasm(): Promise<Core> {
|
||||
throw new Error("loadWasmCore: Go runtime missing after wasm_exec.js load");
|
||||
}
|
||||
const go = new Go();
|
||||
const response = await fetch("/core.wasm");
|
||||
const response = await fetch(withBase("/core.wasm"));
|
||||
const bytes = await response.arrayBuffer();
|
||||
const { instance } = await WebAssembly.instantiate(bytes, go.importObject);
|
||||
void go.run(instance);
|
||||
@@ -156,7 +157,7 @@ async function ensureGoRuntimeLoaded(): Promise<void> {
|
||||
}
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = "/wasm_exec.js";
|
||||
script.src = withBase("/wasm_exec.js");
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error("failed to load /wasm_exec.js"));
|
||||
document.head.appendChild(script);
|
||||
|
||||
+26
-26
@@ -1,22 +1,22 @@
|
||||
// @generated by protoc-gen-es v2.12.0 with parameter "target=ts"
|
||||
// @generated from file galaxy/gateway/v1/edge_gateway.proto (package galaxy.gateway.v1, syntax proto3)
|
||||
// @generated from file edge/v1/edge_gateway.proto (package edge.v1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
|
||||
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
||||
import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
|
||||
import { file_buf_validate_validate } from "../../../buf/validate/validate_pb";
|
||||
import { file_buf_validate_validate } from "../../buf/validate/validate_pb";
|
||||
import type { Message } from "@bufbuild/protobuf";
|
||||
|
||||
/**
|
||||
* Describes the file galaxy/gateway/v1/edge_gateway.proto.
|
||||
* Describes the file edge/v1/edge_gateway.proto.
|
||||
*/
|
||||
export const file_galaxy_gateway_v1_edge_gateway: GenFile = /*@__PURE__*/
|
||||
fileDesc("CiRnYWxheHkvZ2F0ZXdheS92MS9lZGdlX2dhdGV3YXkucHJvdG8SEWdhbGF4eS5nYXRld2F5LnYxIqYCChVFeGVjdXRlQ29tbWFuZFJlcXVlc3QSIQoQcHJvdG9jb2xfdmVyc2lvbhgBIAEoCUIHukgEcgIQARIiChFkZXZpY2Vfc2Vzc2lvbl9pZBgCIAEoCUIHukgEcgIQARIdCgxtZXNzYWdlX3R5cGUYAyABKAlCB7pIBHICEAESHQoMdGltZXN0YW1wX21zGAQgASgDQge6SAQiAiAAEhsKCnJlcXVlc3RfaWQYBSABKAlCB7pIBHICEAESHgoNcGF5bG9hZF9ieXRlcxgGIAEoDEIHukgEegIQARIdCgxwYXlsb2FkX2hhc2gYByABKAxCB7pIBHoCEAESGgoJc2lnbmF0dXJlGAggASgMQge6SAR6AhABEhAKCHRyYWNlX2lkGAkgASgJIrEBChZFeGVjdXRlQ29tbWFuZFJlc3BvbnNlEhgKEHByb3RvY29sX3ZlcnNpb24YASABKAkSEgoKcmVxdWVzdF9pZBgCIAEoCRIUCgx0aW1lc3RhbXBfbXMYAyABKAMSEwoLcmVzdWx0X2NvZGUYBCABKAkSFQoNcGF5bG9hZF9ieXRlcxgFIAEoDBIUCgxwYXlsb2FkX2hhc2gYBiABKAwSEQoJc2lnbmF0dXJlGAcgASgMIp4CChZTdWJzY3JpYmVFdmVudHNSZXF1ZXN0EiEKEHByb3RvY29sX3ZlcnNpb24YASABKAlCB7pIBHICEAESIgoRZGV2aWNlX3Nlc3Npb25faWQYAiABKAlCB7pIBHICEAESHQoMbWVzc2FnZV90eXBlGAMgASgJQge6SARyAhABEh0KDHRpbWVzdGFtcF9tcxgEIAEoA0IHukgEIgIgABIbCgpyZXF1ZXN0X2lkGAUgASgJQge6SARyAhABEh0KDHBheWxvYWRfaGFzaBgGIAEoDEIHukgEegIQARIaCglzaWduYXR1cmUYByABKAxCB7pIBHoCEAESFQoNcGF5bG9hZF9ieXRlcxgIIAEoDBIQCgh0cmFjZV9pZBgJIAEoCSKwAQoMR2F0ZXdheUV2ZW50EhIKCmV2ZW50X3R5cGUYASABKAkSEAoIZXZlbnRfaWQYAiABKAkSFAoMdGltZXN0YW1wX21zGAMgASgDEhUKDXBheWxvYWRfYnl0ZXMYBCABKAwSFAoMcGF5bG9hZF9oYXNoGAUgASgMEhEKCXNpZ25hdHVyZRgGIAEoDBISCgpyZXF1ZXN0X2lkGAcgASgJEhAKCHRyYWNlX2lkGAggASgJMtUBCgtFZGdlR2F0ZXdheRJlCg5FeGVjdXRlQ29tbWFuZBIoLmdhbGF4eS5nYXRld2F5LnYxLkV4ZWN1dGVDb21tYW5kUmVxdWVzdBopLmdhbGF4eS5nYXRld2F5LnYxLkV4ZWN1dGVDb21tYW5kUmVzcG9uc2USXwoPU3Vic2NyaWJlRXZlbnRzEikuZ2FsYXh5LmdhdGV3YXkudjEuU3Vic2NyaWJlRXZlbnRzUmVxdWVzdBofLmdhbGF4eS5nYXRld2F5LnYxLkdhdGV3YXlFdmVudDABQjJaMGdhbGF4eS9nYXRld2F5L3Byb3RvL2dhbGF4eS9nYXRld2F5L3YxO2dhdGV3YXl2MWIGcHJvdG8z", [file_buf_validate_validate]);
|
||||
export const file_edge_v1_edge_gateway: GenFile = /*@__PURE__*/
|
||||
fileDesc("ChplZGdlL3YxL2VkZ2VfZ2F0ZXdheS5wcm90bxIHZWRnZS52MSKmAgoVRXhlY3V0ZUNvbW1hbmRSZXF1ZXN0EiEKEHByb3RvY29sX3ZlcnNpb24YASABKAlCB7pIBHICEAESIgoRZGV2aWNlX3Nlc3Npb25faWQYAiABKAlCB7pIBHICEAESHQoMbWVzc2FnZV90eXBlGAMgASgJQge6SARyAhABEh0KDHRpbWVzdGFtcF9tcxgEIAEoA0IHukgEIgIgABIbCgpyZXF1ZXN0X2lkGAUgASgJQge6SARyAhABEh4KDXBheWxvYWRfYnl0ZXMYBiABKAxCB7pIBHoCEAESHQoMcGF5bG9hZF9oYXNoGAcgASgMQge6SAR6AhABEhoKCXNpZ25hdHVyZRgIIAEoDEIHukgEegIQARIQCgh0cmFjZV9pZBgJIAEoCSKxAQoWRXhlY3V0ZUNvbW1hbmRSZXNwb25zZRIYChBwcm90b2NvbF92ZXJzaW9uGAEgASgJEhIKCnJlcXVlc3RfaWQYAiABKAkSFAoMdGltZXN0YW1wX21zGAMgASgDEhMKC3Jlc3VsdF9jb2RlGAQgASgJEhUKDXBheWxvYWRfYnl0ZXMYBSABKAwSFAoMcGF5bG9hZF9oYXNoGAYgASgMEhEKCXNpZ25hdHVyZRgHIAEoDCKeAgoWU3Vic2NyaWJlRXZlbnRzUmVxdWVzdBIhChBwcm90b2NvbF92ZXJzaW9uGAEgASgJQge6SARyAhABEiIKEWRldmljZV9zZXNzaW9uX2lkGAIgASgJQge6SARyAhABEh0KDG1lc3NhZ2VfdHlwZRgDIAEoCUIHukgEcgIQARIdCgx0aW1lc3RhbXBfbXMYBCABKANCB7pIBCICIAASGwoKcmVxdWVzdF9pZBgFIAEoCUIHukgEcgIQARIdCgxwYXlsb2FkX2hhc2gYBiABKAxCB7pIBHoCEAESGgoJc2lnbmF0dXJlGAcgASgMQge6SAR6AhABEhUKDXBheWxvYWRfYnl0ZXMYCCABKAwSEAoIdHJhY2VfaWQYCSABKAkisAEKDEdhdGV3YXlFdmVudBISCgpldmVudF90eXBlGAEgASgJEhAKCGV2ZW50X2lkGAIgASgJEhQKDHRpbWVzdGFtcF9tcxgDIAEoAxIVCg1wYXlsb2FkX2J5dGVzGAQgASgMEhQKDHBheWxvYWRfaGFzaBgFIAEoDBIRCglzaWduYXR1cmUYBiABKAwSEgoKcmVxdWVzdF9pZBgHIAEoCRIQCgh0cmFjZV9pZBgIIAEoCTKpAQoHR2F0ZXdheRJRCg5FeGVjdXRlQ29tbWFuZBIeLmVkZ2UudjEuRXhlY3V0ZUNvbW1hbmRSZXF1ZXN0Gh8uZWRnZS52MS5FeGVjdXRlQ29tbWFuZFJlc3BvbnNlEksKD1N1YnNjcmliZUV2ZW50cxIfLmVkZ2UudjEuU3Vic2NyaWJlRXZlbnRzUmVxdWVzdBoVLmVkZ2UudjEuR2F0ZXdheUV2ZW50MAFCJVojZ2FsYXh5L2dhdGV3YXkvcHJvdG8vZWRnZS92MTtlZGdldjFiBnByb3RvMw", [file_buf_validate_validate]);
|
||||
|
||||
/**
|
||||
* @generated from message galaxy.gateway.v1.ExecuteCommandRequest
|
||||
* @generated from message edge.v1.ExecuteCommandRequest
|
||||
*/
|
||||
export type ExecuteCommandRequest = Message<"galaxy.gateway.v1.ExecuteCommandRequest"> & {
|
||||
export type ExecuteCommandRequest = Message<"edge.v1.ExecuteCommandRequest"> & {
|
||||
/**
|
||||
* protocol_version identifies the request envelope version. The gateway
|
||||
* accepts only the literal "v1" after required-field validation succeeds.
|
||||
@@ -69,16 +69,16 @@ export type ExecuteCommandRequest = Message<"galaxy.gateway.v1.ExecuteCommandReq
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message galaxy.gateway.v1.ExecuteCommandRequest.
|
||||
* Describes the message edge.v1.ExecuteCommandRequest.
|
||||
* Use `create(ExecuteCommandRequestSchema)` to create a new message.
|
||||
*/
|
||||
export const ExecuteCommandRequestSchema: GenMessage<ExecuteCommandRequest> = /*@__PURE__*/
|
||||
messageDesc(file_galaxy_gateway_v1_edge_gateway, 0);
|
||||
messageDesc(file_edge_v1_edge_gateway, 0);
|
||||
|
||||
/**
|
||||
* @generated from message galaxy.gateway.v1.ExecuteCommandResponse
|
||||
* @generated from message edge.v1.ExecuteCommandResponse
|
||||
*/
|
||||
export type ExecuteCommandResponse = Message<"galaxy.gateway.v1.ExecuteCommandResponse"> & {
|
||||
export type ExecuteCommandResponse = Message<"edge.v1.ExecuteCommandResponse"> & {
|
||||
/**
|
||||
* @generated from field: string protocol_version = 1;
|
||||
*/
|
||||
@@ -116,16 +116,16 @@ export type ExecuteCommandResponse = Message<"galaxy.gateway.v1.ExecuteCommandRe
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message galaxy.gateway.v1.ExecuteCommandResponse.
|
||||
* Describes the message edge.v1.ExecuteCommandResponse.
|
||||
* Use `create(ExecuteCommandResponseSchema)` to create a new message.
|
||||
*/
|
||||
export const ExecuteCommandResponseSchema: GenMessage<ExecuteCommandResponse> = /*@__PURE__*/
|
||||
messageDesc(file_galaxy_gateway_v1_edge_gateway, 1);
|
||||
messageDesc(file_edge_v1_edge_gateway, 1);
|
||||
|
||||
/**
|
||||
* @generated from message galaxy.gateway.v1.SubscribeEventsRequest
|
||||
* @generated from message edge.v1.SubscribeEventsRequest
|
||||
*/
|
||||
export type SubscribeEventsRequest = Message<"galaxy.gateway.v1.SubscribeEventsRequest"> & {
|
||||
export type SubscribeEventsRequest = Message<"edge.v1.SubscribeEventsRequest"> & {
|
||||
/**
|
||||
* protocol_version identifies the request envelope version. The gateway
|
||||
* accepts only the literal "v1" after required-field validation succeeds.
|
||||
@@ -179,16 +179,16 @@ export type SubscribeEventsRequest = Message<"galaxy.gateway.v1.SubscribeEventsR
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message galaxy.gateway.v1.SubscribeEventsRequest.
|
||||
* Describes the message edge.v1.SubscribeEventsRequest.
|
||||
* Use `create(SubscribeEventsRequestSchema)` to create a new message.
|
||||
*/
|
||||
export const SubscribeEventsRequestSchema: GenMessage<SubscribeEventsRequest> = /*@__PURE__*/
|
||||
messageDesc(file_galaxy_gateway_v1_edge_gateway, 2);
|
||||
messageDesc(file_edge_v1_edge_gateway, 2);
|
||||
|
||||
/**
|
||||
* @generated from message galaxy.gateway.v1.GatewayEvent
|
||||
* @generated from message edge.v1.GatewayEvent
|
||||
*/
|
||||
export type GatewayEvent = Message<"galaxy.gateway.v1.GatewayEvent"> & {
|
||||
export type GatewayEvent = Message<"edge.v1.GatewayEvent"> & {
|
||||
/**
|
||||
* @generated from field: string event_type = 1;
|
||||
*/
|
||||
@@ -231,18 +231,18 @@ export type GatewayEvent = Message<"galaxy.gateway.v1.GatewayEvent"> & {
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message galaxy.gateway.v1.GatewayEvent.
|
||||
* Describes the message edge.v1.GatewayEvent.
|
||||
* Use `create(GatewayEventSchema)` to create a new message.
|
||||
*/
|
||||
export const GatewayEventSchema: GenMessage<GatewayEvent> = /*@__PURE__*/
|
||||
messageDesc(file_galaxy_gateway_v1_edge_gateway, 3);
|
||||
messageDesc(file_edge_v1_edge_gateway, 3);
|
||||
|
||||
/**
|
||||
* @generated from service galaxy.gateway.v1.EdgeGateway
|
||||
* @generated from service edge.v1.Gateway
|
||||
*/
|
||||
export const EdgeGateway: GenService<{
|
||||
export const Gateway: GenService<{
|
||||
/**
|
||||
* @generated from rpc galaxy.gateway.v1.EdgeGateway.ExecuteCommand
|
||||
* @generated from rpc edge.v1.Gateway.ExecuteCommand
|
||||
*/
|
||||
executeCommand: {
|
||||
methodKind: "unary";
|
||||
@@ -250,7 +250,7 @@ export const EdgeGateway: GenService<{
|
||||
output: typeof ExecuteCommandResponseSchema;
|
||||
},
|
||||
/**
|
||||
* @generated from rpc galaxy.gateway.v1.EdgeGateway.SubscribeEvents
|
||||
* @generated from rpc edge.v1.Gateway.SubscribeEvents
|
||||
*/
|
||||
subscribeEvents: {
|
||||
methodKind: "server_streaming";
|
||||
@@ -258,5 +258,5 @@ export const EdgeGateway: GenService<{
|
||||
output: typeof GatewayEventSchema;
|
||||
},
|
||||
}> = /*@__PURE__*/
|
||||
serviceDesc(file_galaxy_gateway_v1_edge_gateway, 0);
|
||||
serviceDesc(file_edge_v1_edge_gateway, 0);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import { dev } from "$app/environment";
|
||||
import { appBase, withBase } from "$lib/paths";
|
||||
import { i18n } from "$lib/i18n/index.svelte";
|
||||
import { session } from "$lib/session-store.svelte";
|
||||
import { eventStream } from "../api/events.svelte";
|
||||
@@ -26,7 +27,9 @@
|
||||
// in svelte.config.js) so `vite dev` and the dev-server e2e suite
|
||||
// run without the worker intercepting requests.
|
||||
if (!dev && "serviceWorker" in navigator) {
|
||||
void navigator.serviceWorker.register("/service-worker.js");
|
||||
void navigator.serviceWorker.register(withBase("/service-worker.js"), {
|
||||
scope: withBase("/"),
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
eventStream.stop();
|
||||
@@ -75,7 +78,9 @@
|
||||
streamSessionId = null;
|
||||
}
|
||||
|
||||
const pathname = page.url.pathname;
|
||||
// page.url.pathname includes the configured base path; strip it so
|
||||
// the route comparisons below stay base-agnostic.
|
||||
const pathname = page.url.pathname.slice(appBase.length);
|
||||
// Debug-only routes under /__debug/* run their own bootstrap
|
||||
// path against the storage primitives and must bypass the
|
||||
// auth guard so Phase 6's Playwright spec can drive the
|
||||
@@ -84,9 +89,12 @@
|
||||
return;
|
||||
}
|
||||
if (session.status === "anonymous" && pathname !== "/login") {
|
||||
void goto("/login", { replaceState: true });
|
||||
} else if (session.status === "authenticated" && pathname === "/login") {
|
||||
void goto("/lobby", { replaceState: true });
|
||||
void goto(withBase("/login"), { replaceState: true });
|
||||
} else if (
|
||||
session.status === "authenticated" &&
|
||||
(pathname === "/login" || pathname === "/")
|
||||
) {
|
||||
void goto(withBase("/lobby"), { replaceState: true });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { APP_VERSION } from "$lib/version";
|
||||
// The app root renders no content of its own. The root layout's auth
|
||||
// guard redirects "/" to /lobby (authenticated) or /login
|
||||
// (anonymous); this placeholder only shows for the brief moment
|
||||
// before that client-side redirect resolves.
|
||||
import { i18n } from "$lib/i18n/index.svelte";
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<h1>Galaxy</h1>
|
||||
<p>Cross-platform UI client — workspace skeleton.</p>
|
||||
<main class="status">
|
||||
<p>{i18n.t("common.loading")}</p>
|
||||
</main>
|
||||
|
||||
<footer data-testid="app-version">version {APP_VERSION}</footer>
|
||||
|
||||
<style>
|
||||
main {
|
||||
padding: 2rem;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
footer {
|
||||
padding: 1rem 2rem;
|
||||
opacity: 0.6;
|
||||
font-size: 0.875rem;
|
||||
.status {
|
||||
padding: var(--space-6);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -43,6 +43,7 @@ the next game's snapshot — and the next game's selection — start
|
||||
fresh.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { onDestroy, onMount, setContext, untrack } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
@@ -86,9 +87,9 @@ fresh.
|
||||
import { session } from "$lib/session-store.svelte";
|
||||
import { loadStore } from "../../../platform/store/index";
|
||||
import { loadCore } from "../../../platform/core/index";
|
||||
import { createEdgeGatewayClient } from "../../../api/connect";
|
||||
import { createGatewayClient } from "../../../api/connect";
|
||||
import { GalaxyClient } from "../../../api/galaxy-client";
|
||||
import { GATEWAY_BASE_URL, GATEWAY_RESPONSE_PUBLIC_KEY } from "$lib/env";
|
||||
import { gatewayRpcBaseUrl, GATEWAY_RESPONSE_PUBLIC_KEY } from "$lib/env";
|
||||
import {
|
||||
getSyntheticReport,
|
||||
isSyntheticGameId,
|
||||
@@ -373,7 +374,7 @@ fresh.
|
||||
if (isSyntheticGameId(gameId)) {
|
||||
const report = getSyntheticReport(gameId);
|
||||
if (report === undefined) {
|
||||
await goto("/lobby");
|
||||
await goto(withBase("/lobby"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -420,7 +421,7 @@ fresh.
|
||||
coreHolder.set(core);
|
||||
const client = new GalaxyClient({
|
||||
core,
|
||||
edge: createEdgeGatewayClient(GATEWAY_BASE_URL),
|
||||
edge: createGatewayClient(gatewayRpcBaseUrl()),
|
||||
signer: (canonical) => keypair.sign(canonical),
|
||||
sha256,
|
||||
deviceSessionId,
|
||||
@@ -472,7 +473,7 @@ fresh.
|
||||
messageParams: { from: parsed.from },
|
||||
actionLabelKey: "game.events.mail_new.action",
|
||||
onAction: () => {
|
||||
void goto(`/games/${gameId}/mail`);
|
||||
void goto(withBase(`/games/${gameId}/mail`));
|
||||
},
|
||||
durationMs: 8000,
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { goto } from "$app/navigation";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { createEdgeGatewayClient } from "../../api/connect";
|
||||
import { createGatewayClient } from "../../api/connect";
|
||||
import { GalaxyClient } from "../../api/galaxy-client";
|
||||
import {
|
||||
LobbyError,
|
||||
@@ -19,7 +20,7 @@
|
||||
} from "../../api/lobby";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
import { AccountResponse } from "../../proto/galaxy/fbs/user";
|
||||
import { GATEWAY_BASE_URL, GATEWAY_RESPONSE_PUBLIC_KEY } from "$lib/env";
|
||||
import { gatewayRpcBaseUrl, GATEWAY_RESPONSE_PUBLIC_KEY } from "$lib/env";
|
||||
import {
|
||||
SyntheticReportError,
|
||||
loadSyntheticReportFromJSON,
|
||||
@@ -184,11 +185,11 @@
|
||||
}
|
||||
|
||||
function gotoCreate(): void {
|
||||
goto("/lobby/create");
|
||||
goto(withBase("/lobby/create"));
|
||||
}
|
||||
|
||||
function gotoGame(gameId: string): void {
|
||||
goto(`/games/${gameId}/map`);
|
||||
goto(withBase(`/games/${gameId}/map`));
|
||||
}
|
||||
|
||||
async function onSyntheticFileChange(
|
||||
@@ -207,7 +208,7 @@
|
||||
const text = await file.text();
|
||||
const json: unknown = JSON.parse(text);
|
||||
const { gameId } = loadSyntheticReportFromJSON(json);
|
||||
await goto(`/games/${gameId}/map`);
|
||||
await goto(withBase(`/games/${gameId}/map`));
|
||||
} catch (err) {
|
||||
if (err instanceof SyntheticReportError) {
|
||||
syntheticError = err.message;
|
||||
@@ -250,7 +251,7 @@
|
||||
const core = await loadCore();
|
||||
client = new GalaxyClient({
|
||||
core,
|
||||
edge: createEdgeGatewayClient(GATEWAY_BASE_URL),
|
||||
edge: createGatewayClient(gatewayRpcBaseUrl()),
|
||||
signer: (canonical) => keypair.sign(canonical),
|
||||
sha256,
|
||||
deviceSessionId: session.deviceSessionId,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { goto } from "$app/navigation";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { createEdgeGatewayClient } from "../../../api/connect";
|
||||
import { createGatewayClient } from "../../../api/connect";
|
||||
import { GalaxyClient } from "../../../api/galaxy-client";
|
||||
import { LobbyError, createGame } from "../../../api/lobby";
|
||||
import { GATEWAY_BASE_URL, GATEWAY_RESPONSE_PUBLIC_KEY } from "$lib/env";
|
||||
import { gatewayRpcBaseUrl, GATEWAY_RESPONSE_PUBLIC_KEY } from "$lib/env";
|
||||
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
||||
import { loadCore } from "../../../platform/core/index";
|
||||
import { session } from "$lib/session-store.svelte";
|
||||
@@ -51,7 +52,7 @@
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
goto("/lobby");
|
||||
goto(withBase("/lobby"));
|
||||
}
|
||||
|
||||
async function submit(): Promise<void> {
|
||||
@@ -93,7 +94,7 @@
|
||||
turnSchedule: trimmedSchedule,
|
||||
targetEngineVersion: targetEngineVersion.trim() || DEFAULT_TARGET_ENGINE_VERSION,
|
||||
});
|
||||
goto("/lobby");
|
||||
goto(withBase("/lobby"));
|
||||
} catch (err) {
|
||||
formError = describeLobbyError(err);
|
||||
} finally {
|
||||
@@ -116,7 +117,7 @@
|
||||
const core = await loadCore();
|
||||
client = new GalaxyClient({
|
||||
core,
|
||||
edge: createEdgeGatewayClient(GATEWAY_BASE_URL),
|
||||
edge: createGatewayClient(gatewayRpcBaseUrl()),
|
||||
signer: (canonical) => keypair.sign(canonical),
|
||||
sha256,
|
||||
deviceSessionId: session.deviceSessionId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { withBase } from "$lib/paths";
|
||||
import { goto } from "$app/navigation";
|
||||
import {
|
||||
AuthError,
|
||||
@@ -88,7 +89,7 @@
|
||||
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
});
|
||||
await session.signIn(result.deviceSessionId);
|
||||
void goto("/lobby", { replaceState: true });
|
||||
void goto(withBase("/lobby"), { replaceState: true });
|
||||
} catch (err) {
|
||||
if (err instanceof AuthError && err.code === "invalid_request") {
|
||||
challengeId = null;
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
//
|
||||
// SvelteKit registers this worker automatically in the production build.
|
||||
|
||||
import { build, files, version } from "$service-worker";
|
||||
import { base, build, files, version } from "$service-worker";
|
||||
|
||||
const sw = self as unknown as ServiceWorkerGlobalScope;
|
||||
|
||||
const CACHE = `galaxy-cache-${version}`;
|
||||
// "/" is the SPA shell (adapter-static fallback); precaching it makes the
|
||||
// start_url load offline.
|
||||
const PRECACHE = ["/", ...build, ...files];
|
||||
// `${base}/` is the SPA shell (adapter-static fallback); precaching it
|
||||
// makes the start_url load offline. `base` is empty at the root and
|
||||
// `/game` under the single-origin deployment, and `$service-worker`
|
||||
// derives it from `location.pathname` so it stays correct in a subdir.
|
||||
const PRECACHE = [`${base}/`, ...build, ...files];
|
||||
|
||||
sw.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
@@ -65,7 +67,7 @@ sw.addEventListener("fetch", (event) => {
|
||||
const cached = await cache.match(request);
|
||||
if (cached) return cached;
|
||||
if (request.mode === "navigate") {
|
||||
const shell = await cache.match("/");
|
||||
const shell = await cache.match(`${base}/`);
|
||||
if (shell) return shell;
|
||||
}
|
||||
throw err;
|
||||
|
||||
@@ -2,28 +2,28 @@
|
||||
"name": "Galaxy",
|
||||
"short_name": "Galaxy",
|
||||
"description": "Galaxy — a turn-based space strategy game.",
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"id": "./",
|
||||
"start_url": "./",
|
||||
"scope": "./",
|
||||
"display": "standalone",
|
||||
"orientation": "any",
|
||||
"background_color": "#0a0e1a",
|
||||
"theme_color": "#0a0e1a",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-192.png",
|
||||
"src": "icons/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512.png",
|
||||
"src": "icons/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-maskable-512.png",
|
||||
"src": "icons/icon-maskable-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
|
||||
@@ -11,6 +11,13 @@ export default {
|
||||
fallback: "index.html",
|
||||
strict: true,
|
||||
}),
|
||||
paths: {
|
||||
// Base path the app is served under. Empty by default so local
|
||||
// dev, vitest, and Playwright run at the root unchanged; the
|
||||
// deployed single-origin build sets BASE_PATH=/game and the
|
||||
// edge Caddy serves the SPA under that prefix.
|
||||
base: process.env.BASE_PATH ?? "",
|
||||
},
|
||||
serviceWorker: {
|
||||
// Registered manually in the root layout for production only.
|
||||
// SvelteKit's auto-registration also runs under `vite dev`, where
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
// server picks up via `VITE_GATEWAY_RESPONSE_PUBLIC_KEY`.
|
||||
//
|
||||
// The Connect-Web request URL pattern is
|
||||
// <baseUrl>/galaxy.gateway.v1.EdgeGateway/<MethodName>
|
||||
// <baseUrl>/edge.v1.Gateway/<MethodName>
|
||||
// so the route handlers below match against the trailing path
|
||||
// suffix and ignore the host.
|
||||
|
||||
import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
|
||||
import {
|
||||
buildAccountResponsePayload,
|
||||
@@ -54,7 +54,7 @@ async function mockGatewayHappyPath(
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -105,7 +105,7 @@ async function mockGatewayHappyPath(
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async (route) => {
|
||||
// Hold the stream open until the test releases it via
|
||||
// `pendingSubscribes`. Releasing fulfils with a Connect
|
||||
|
||||
@@ -15,7 +15,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
|
||||
import { GameBattleRequest } from "../../src/proto/galaxy/fbs/battle";
|
||||
@@ -91,7 +91,7 @@ async function mockGatewayAndBattle(page: Page): Promise<void> {
|
||||
};
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import {
|
||||
CommandPlanetRouteRemove,
|
||||
@@ -110,7 +110,7 @@ async function mockGateway(page: Page): Promise<MockHandle> {
|
||||
let submitCount = 0;
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -267,7 +267,7 @@ async function mockGateway(page: Page): Promise<MockHandle> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import { create, toJsonString } from "@bufbuild/protobuf";
|
||||
import { webcrypto } from "node:crypto";
|
||||
import { GatewayEventSchema } from "../../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { GatewayEventSchema } from "../../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { buildEventSigningInput } from "./canon";
|
||||
import {
|
||||
FIXTURE_PRIVATE_KEY_PKCS8_BASE64,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { create, toJson, toJsonString } from "@bufbuild/protobuf";
|
||||
import { webcrypto } from "node:crypto";
|
||||
import { ExecuteCommandResponseSchema } from "../../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandResponseSchema } from "../../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import {
|
||||
FIXTURE_PRIVATE_KEY_PKCS8_BASE64,
|
||||
decodeBase64,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
|
||||
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
|
||||
@@ -46,7 +46,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<void> {
|
||||
};
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -93,7 +93,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<void> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
|
||||
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
|
||||
@@ -53,7 +53,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockState> {
|
||||
};
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -112,7 +112,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockState> {
|
||||
// the watcher's catch path logs the abort and returns without a
|
||||
// sign-out — same convention as `tests/e2e/lobby-flow.spec.ts`.
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
|
||||
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
|
||||
import {
|
||||
@@ -70,7 +70,7 @@ async function mockGateway(page: Page): Promise<MockState> {
|
||||
});
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -147,7 +147,7 @@ async function mockGateway(page: Page): Promise<MockState> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { GameCreateRequest } from "../../src/proto/galaxy/fbs/lobby";
|
||||
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
|
||||
import {
|
||||
@@ -74,7 +74,7 @@ async function mockGateway(page: Page, initial: Partial<LobbyState> = {}): Promi
|
||||
});
|
||||
});
|
||||
|
||||
await page.route("**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand", async (route) => {
|
||||
await page.route("**/edge.v1.Gateway/ExecuteCommand", async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
await route.fulfill({ status: 400 });
|
||||
@@ -208,7 +208,7 @@ async function mockGateway(page: Page, initial: Partial<LobbyState> = {}): Promi
|
||||
});
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async (route) => {
|
||||
const action = await new Promise<"endOfStream" | "abort">((resolve) => {
|
||||
mocks.pendingSubscribes.push(() => resolve("endOfStream"));
|
||||
|
||||
@@ -10,7 +10,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import { UserGamesOrderGet } from "../../src/proto/galaxy/fbs/order";
|
||||
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
|
||||
@@ -44,7 +44,7 @@ async function mockGateway(page: Page): Promise<void> {
|
||||
};
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -116,7 +116,7 @@ async function mockGateway(page: Page): Promise<void> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
|
||||
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
|
||||
@@ -57,7 +57,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<void> {
|
||||
};
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -183,7 +183,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<void> {
|
||||
// sign the session out mid-test (same convention as
|
||||
// `game-shell-map.spec.ts`).
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import {
|
||||
UserGamesOrder,
|
||||
@@ -76,7 +76,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockHandle> {
|
||||
let submitCalls = 0;
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -204,7 +204,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockHandle> {
|
||||
|
||||
let subscribeServed = false;
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async (route) => {
|
||||
if (opts.subscribeFrame !== undefined && !subscribeServed) {
|
||||
subscribeServed = true;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import {
|
||||
CommandPlanetProduce,
|
||||
@@ -74,7 +74,7 @@ async function mockGateway(page: Page): Promise<MockHandle> {
|
||||
let submitCount = 0;
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -187,7 +187,7 @@ async function mockGateway(page: Page): Promise<MockHandle> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import {
|
||||
CommandPayload,
|
||||
@@ -65,7 +65,7 @@ async function mockGateway(page: Page): Promise<MockHandle> {
|
||||
let lastVote: MockHandle["lastVote"] = null;
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -239,7 +239,7 @@ async function mockGateway(page: Page): Promise<MockHandle> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import {
|
||||
UserGamesOrder,
|
||||
@@ -65,7 +65,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockHandle> {
|
||||
let lastReportName = "Earth";
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -181,7 +181,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockHandle> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
|
||||
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
|
||||
@@ -92,7 +92,7 @@ async function mockGateway(page: Page): Promise<void> {
|
||||
const storedOrder: CommandResultFixture[] = [];
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -195,7 +195,7 @@ async function mockGateway(page: Page): Promise<void> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import {
|
||||
CommandPayload,
|
||||
@@ -51,7 +51,7 @@ async function mockGateway(page: Page): Promise<void> {
|
||||
let storedOrder: CommandResultFixture[] = [];
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -155,7 +155,7 @@ async function mockGateway(page: Page): Promise<void> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -22,7 +22,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import {
|
||||
CommandPayload,
|
||||
@@ -94,7 +94,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockHandle> {
|
||||
const reportSciences: ScienceFixture[] = [...(opts.initialSciences ?? [])];
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -242,7 +242,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockHandle> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { ByteBuffer } from "flatbuffers";
|
||||
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
|
||||
import { ExecuteCommandRequestSchema } from "../../src/proto/edge/v1/edge_gateway_pb";
|
||||
import { UUID } from "../../src/proto/galaxy/fbs/common";
|
||||
import {
|
||||
CommandPayload,
|
||||
@@ -88,7 +88,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockHandle> {
|
||||
const reportClasses: ShipClassFixture[] = [...(opts.initialClasses ?? [])];
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
|
||||
"**/edge.v1.Gateway/ExecuteCommand",
|
||||
async (route) => {
|
||||
const reqText = route.request().postData();
|
||||
if (reqText === null) {
|
||||
@@ -220,7 +220,7 @@ async function mockGateway(page: Page, opts: MockOpts): Promise<MockHandle> {
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
|
||||
"**/edge.v1.Gateway/SubscribeEvents",
|
||||
async () => {
|
||||
await new Promise<void>(() => {});
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user