Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cmd/hypergoat/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,9 @@ func setupAdmin(r *chi.Mux, cfg *config.Config, svc *services) *admin.Handler {

// GraphiQL playgrounds
r.Get("/graphiql", server.HandleGraphiQL(server.GraphiQLConfig{
Endpoint: cfg.ExternalBaseURL + "/graphql",
SubscriptionEndpoint: strings.Replace(cfg.ExternalBaseURL, "http", "ws", 1) + "/graphql/ws",
Title: "Hypergoat GraphQL",
EndpointPath: "/graphql",
SubscriptionPath: "/graphql/ws",
Title: "Hypergoat GraphQL",
DefaultQuery: `# Hypergoat GraphQL API
#
# Explore the AT Protocol data indexed by this AppView.
Expand All @@ -451,8 +451,8 @@ func setupAdmin(r *chi.Mux, cfg *config.Config, svc *services) *admin.Handler {
}))

r.Get("/graphiql/admin", server.HandleGraphiQL(server.GraphiQLConfig{
Endpoint: cfg.ExternalBaseURL + "/admin/graphql",
Title: "Hypergoat Admin",
EndpointPath: "/admin/graphql",
Title: "Hypergoat Admin",
DefaultQuery: `# Hypergoat Admin API
#
# Administrative operations for managing the AppView.
Expand Down
39 changes: 18 additions & 21 deletions internal/server/graphiql.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (

// GraphiQLConfig contains configuration for the GraphiQL handler.
type GraphiQLConfig struct {
// Endpoint is the GraphQL endpoint URL.
Endpoint string
// SubscriptionEndpoint is the WebSocket endpoint for subscriptions (optional).
SubscriptionEndpoint string
// EndpointPath is the path to the GraphQL endpoint (e.g. "/graphql").
// The full URL is derived at runtime from the browser's window.location.
EndpointPath string
// SubscriptionPath is the path for WebSocket subscriptions (optional, e.g. "/graphql/ws").
SubscriptionPath string
// Title is the page title.
Title string
// DefaultQuery is the initial query to display.
Expand Down Expand Up @@ -71,22 +72,14 @@ func generateGraphiQLHTML(cfg GraphiQLConfig) string {
`
}

// Build fetcher config
fetcherConfig := `{
url: '` + cfg.Endpoint + `',
headers: {
'Content-Type': 'application/json',
},
}`

if cfg.SubscriptionEndpoint != "" {
fetcherConfig = `{
url: '` + cfg.Endpoint + `',
headers: {
'Content-Type': 'application/json',
},
subscriptionUrl: '` + cfg.SubscriptionEndpoint + `',
}`
// Build subscription URL JavaScript snippet.
// Uses window.location to derive the correct WebSocket URL at runtime,
// so the page works regardless of which domain it's accessed through.
subscriptionJS := ""
if cfg.SubscriptionPath != "" {
subscriptionJS = `
const wsProto = location.protocol === 'https:' ? 'wss:' : 'ws:';
fetcherOpts.subscriptionUrl = wsProto + '//' + location.host + '` + cfg.SubscriptionPath + `';`
}

return `<!DOCTYPE html>
Expand Down Expand Up @@ -114,8 +107,12 @@ func generateGraphiQLHTML(cfg GraphiQLConfig) string {
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script crossorigin src="https://unpkg.com/graphiql@3/graphiql.min.js"></script>
<script>
const fetcherOpts = {
url: location.origin + '` + cfg.EndpointPath + `',
headers: { 'Content-Type': 'application/json' },
};` + subscriptionJS + `
const root = ReactDOM.createRoot(document.getElementById('graphiql'));
const fetcher = GraphiQL.createFetcher(` + fetcherConfig + `);
const fetcher = GraphiQL.createFetcher(fetcherOpts);
root.render(
React.createElement(GraphiQL, {
fetcher: fetcher,
Expand Down
16 changes: 8 additions & 8 deletions internal/server/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ func TestHandleClientMetadata(t *testing.T) {

func TestHandleGraphiQL(t *testing.T) {
baseCfg := GraphiQLConfig{
Endpoint: "/graphql",
Title: "Hypergoat GraphiQL",
EndpointPath: "/graphql",
Title: "Hypergoat GraphiQL",
}

t.Run("GET returns 200 with text/html content type", func(t *testing.T) {
Expand Down Expand Up @@ -363,9 +363,9 @@ func TestHandleGraphiQL(t *testing.T) {

t.Run("subscription endpoint included when configured", func(t *testing.T) {
cfg := GraphiQLConfig{
Endpoint: "/graphql",
SubscriptionEndpoint: "ws://localhost:8080/graphql/ws",
Title: "Test",
EndpointPath: "/graphql",
SubscriptionPath: "/graphql/ws",
Title: "Test",
}
handler := HandleGraphiQL(cfg)
req := httptest.NewRequest(http.MethodGet, "/graphiql", nil)
Expand All @@ -374,8 +374,8 @@ func TestHandleGraphiQL(t *testing.T) {
handler.ServeHTTP(rec, req)

body := rec.Body.String()
if !strings.Contains(body, "ws://localhost:8080/graphql/ws") {
t.Error("response body does not contain subscription endpoint")
if !strings.Contains(body, "/graphql/ws") {
t.Error("response body does not contain subscription path")
}
if !strings.Contains(body, "subscriptionUrl") {
t.Error("response body does not contain subscriptionUrl config key")
Expand Down Expand Up @@ -409,7 +409,7 @@ func TestHandleGraphiQL(t *testing.T) {

t.Run("default title used when not configured", func(t *testing.T) {
cfg := GraphiQLConfig{
Endpoint: "/graphql",
EndpointPath: "/graphql",
}
handler := HandleGraphiQL(cfg)
req := httptest.NewRequest(http.MethodGet, "/graphiql", nil)
Expand Down