diff --git a/graphiql.go b/graphiql.go index b024086..77c03ea 100644 --- a/graphiql.go +++ b/graphiql.go @@ -4,21 +4,27 @@ import ( "encoding/json" "html/template" "net/http" + "strings" "github.com/graphql-go/graphql" ) -// graphiqlData is the page data structure of the rendered GraphiQL page +// data is the page data structure of the rendered GraphiQL page type graphiqlData struct { - GraphiqlVersion string - QueryString string - VariablesString string - OperationName string - ResultString string + GraphiqlVersion string + SubscriptionTransportVersion string + QueryString string + ResultString string + VariablesString string + OperationName string + Endpoint template.URL + SubscriptionEndpoint template.URL + UsingHTTP bool + UsingWS bool } // renderGraphiQL renders the GraphiQL GUI -func renderGraphiQL(w http.ResponseWriter, params graphql.Params) { +func renderGraphiQL(w http.ResponseWriter, params graphql.Params, handler Handler) { t := template.New("GraphiQL") t, err := t.Parse(graphiqlTemplate) if err != nil { @@ -50,12 +56,29 @@ func renderGraphiQL(w http.ResponseWriter, params graphql.Params) { resString = string(result) } + isEndpointUsingWS := strings.HasPrefix(handler.Endpoint, "ws://") + UsingHTTP := !isEndpointUsingWS + UsingWS := isEndpointUsingWS || handler.SubscriptionsEndpoint != "" + SubscriptionEndpoint := "" + if UsingWS { + if isEndpointUsingWS { + SubscriptionEndpoint = handler.Endpoint + } else { + SubscriptionEndpoint = handler.SubscriptionsEndpoint + } + } + d := graphiqlData{ - GraphiqlVersion: graphiqlVersion, - QueryString: params.RequestString, - ResultString: resString, - VariablesString: varsString, - OperationName: params.OperationName, + GraphiqlVersion: graphiqlVersion, + SubscriptionTransportVersion: subscriptionTransportVersion, + QueryString: params.RequestString, + ResultString: resString, + VariablesString: varsString, + OperationName: params.OperationName, + Endpoint: template.URL(handler.Endpoint), + SubscriptionEndpoint: template.URL(SubscriptionEndpoint), + UsingHTTP: UsingHTTP, + UsingWS: UsingWS, } err = t.ExecuteTemplate(w, "index", d) if err != nil { @@ -68,6 +91,9 @@ func renderGraphiQL(w http.ResponseWriter, params graphql.Params) { // graphiqlVersion is the current version of GraphiQL const graphiqlVersion = "0.11.11" +// subscriptionTransportVersion is the current version of the subscription transport of GraphiQL +const subscriptionTransportVersion = "0.8.2" + // tmpl is the page template to render GraphiQL const graphiqlTemplate = ` {{ define "index" }} @@ -103,6 +129,17 @@ add "&raw" to the end of the URL within a browser. + + {{ if .UsingHTTP }} + + {{ end }} + {{ if .UsingWS }} + + {{ end }} + {{ if and .UsingWS .UsingHTTP }} + + {{ end }} +
Loading...
@@ -118,10 +155,8 @@ add "&raw" to the end of the URL within a browser. }); // Produce a Location query string from a parameter object. - function locationQuery(params) { - return '?' + Object.keys(params).filter(function (key) { - return Boolean(params[key]); - }).map(function (key) { + function locationQuery(params, location) { + return (location ? location: '') + '?' + Object.keys(params).map(function (key) { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); }).join('&'); @@ -140,28 +175,49 @@ add "&raw" to the end of the URL within a browser. otherParams[k] = parameters[k]; } } - var fetchURL = locationQuery(otherParams); - - // Defines a GraphQL fetcher using the fetch API. - function graphQLFetcher(graphQLParams) { - return fetch(fetchURL, { - method: 'post', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(graphQLParams), - credentials: 'include', - }).then(function (response) { - return response.text(); - }).then(function (responseBody) { - try { - return JSON.parse(responseBody); - } catch (error) { - return responseBody; - } + + {{ if .UsingWS }} + var subscriptionsClient = new window.SubscriptionsTransportWs.SubscriptionClient({{ .SubscriptionEndpoint }}, { + reconnect: true }); - } + var graphQLWSFetcher = subscriptionsClient.request.bind(subscriptionsClient); + {{ end }} + + {{ if .UsingHTTP }} + var fetchURL = locationQuery(otherParams, {{ .Endpoint }}); + + // Defines a GraphQL fetcher using the fetch API. + function graphQLHttpFetcher(graphQLParams) { + return fetch(fetchURL, { + method: 'post', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(graphQLParams), + credentials: 'include', + }).then(function (response) { + return response.text(); + }).then(function (responseBody) { + try { + return JSON.parse(responseBody); + } catch (error) { + return responseBody; + } + }); + } + {{ end }} + + {{ if and .UsingWS .UsingHTTP }} + var fetcher = window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLHttpFetcher); + {{ else }} + {{ if .UsingWS }} + var fetcher = graphQLWSFetcher; + {{ end }} + {{ if .UsingHTTP }} + var fetcher = graphQLHttpFetcher; + {{ end }} + {{ end }} // When the query and variables string is edited, update the URL bar so // that it can be easily shared. @@ -187,7 +243,7 @@ add "&raw" to the end of the URL within a browser. // Render into the body. ReactDOM.render( React.createElement(GraphiQL, { - fetcher: graphQLFetcher, + fetcher: fetcher, onEditQuery: onEditQuery, onEditVariables: onEditVariables, onEditOperationName: onEditOperationName, diff --git a/handler.go b/handler.go index b9a647c..c5b6aa7 100644 --- a/handler.go +++ b/handler.go @@ -23,13 +23,15 @@ const ( type ResultCallbackFn func(ctx context.Context, params *graphql.Params, result *graphql.Result, responseBody []byte) type Handler struct { - Schema *graphql.Schema - pretty bool - graphiql bool - playground bool - rootObjectFn RootObjectFn - resultCallbackFn ResultCallbackFn - formatErrorFn func(err error) gqlerrors.FormattedError + Schema *graphql.Schema + pretty bool + graphiql bool + Endpoint string + SubscriptionsEndpoint string + playground bool + rootObjectFn RootObjectFn + resultCallbackFn ResultCallbackFn + formatErrorFn func(err error) gqlerrors.FormattedError } type RequestOptions struct { @@ -153,7 +155,7 @@ func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r * acceptHeader := r.Header.Get("Accept") _, raw := r.URL.Query()["raw"] if !raw && !strings.Contains(acceptHeader, "application/json") && strings.Contains(acceptHeader, "text/html") { - renderGraphiQL(w, params) + renderGraphiQL(w, params, *h) return } } @@ -197,21 +199,25 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { type RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{} type Config struct { - Schema *graphql.Schema - Pretty bool - GraphiQL bool - Playground bool - RootObjectFn RootObjectFn - ResultCallbackFn ResultCallbackFn - FormatErrorFn func(err error) gqlerrors.FormattedError + Schema *graphql.Schema + Pretty bool + GraphiQL bool + Endpoint string + SubscriptionsEndpoint string + Playground bool + RootObjectFn RootObjectFn + ResultCallbackFn ResultCallbackFn + FormatErrorFn func(err error) gqlerrors.FormattedError } func NewConfig() *Config { return &Config{ - Schema: nil, - Pretty: true, - GraphiQL: true, - Playground: false, + Schema: nil, + Pretty: true, + GraphiQL: true, + Endpoint: "", + SubscriptionsEndpoint: "", + Playground: false, } } @@ -225,12 +231,14 @@ func New(p *Config) *Handler { } return &Handler{ - Schema: p.Schema, - pretty: p.Pretty, - graphiql: p.GraphiQL, - playground: p.Playground, - rootObjectFn: p.RootObjectFn, - resultCallbackFn: p.ResultCallbackFn, - formatErrorFn: p.FormatErrorFn, + Schema: p.Schema, + pretty: p.Pretty, + graphiql: p.GraphiQL, + Endpoint: p.Endpoint, + SubscriptionsEndpoint: p.SubscriptionsEndpoint, + playground: p.Playground, + rootObjectFn: p.RootObjectFn, + resultCallbackFn: p.ResultCallbackFn, + formatErrorFn: p.FormatErrorFn, } }