renderGraphiQL.js.flow 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // @flow strict
  2. import type { FormattedExecutionResult } from 'graphql';
  3. export type GraphiQLData = {|
  4. query?: string | null,
  5. variables?: { +[name: string]: mixed, ... } | null,
  6. operationName?: string | null,
  7. result?: FormattedExecutionResult,
  8. |};
  9. export type GraphiQLOptions = {|
  10. /**
  11. * An optional GraphQL string to use when no query is provided and no stored
  12. * query exists from a previous session. If undefined is provided, GraphiQL
  13. * will use its own default query.
  14. */
  15. defaultQuery?: string,
  16. /**
  17. * An optional boolean which enables the header editor when true.
  18. * Defaults to false.
  19. */
  20. headerEditorEnabled?: boolean,
  21. |};
  22. // Ensures string values are safe to be used within a <script> tag.
  23. function safeSerialize(data: string | boolean | null | void): string {
  24. return data != null
  25. ? JSON.stringify(data).replace(/\//g, '\\/')
  26. : 'undefined';
  27. }
  28. // Implemented as Babel transformation, see ../resources/load-statically-from-npm.js
  29. declare function loadFileStaticallyFromNPM(npmPath: string): string;
  30. /**
  31. * When express-graphql receives a request which does not Accept JSON, but does
  32. * Accept HTML, it may present GraphiQL, the in-browser GraphQL explorer IDE.
  33. *
  34. * When shown, it will be pre-populated with the result of having executed the
  35. * requested query.
  36. */
  37. export function renderGraphiQL(
  38. data: GraphiQLData,
  39. options?: GraphiQLOptions,
  40. ): string {
  41. const queryString = data.query;
  42. const variablesString =
  43. data.variables != null ? JSON.stringify(data.variables, null, 2) : null;
  44. const resultString =
  45. data.result != null ? JSON.stringify(data.result, null, 2) : null;
  46. const operationName = data.operationName;
  47. const defaultQuery = options?.defaultQuery;
  48. const headerEditorEnabled = options?.headerEditorEnabled;
  49. return `<!--
  50. The request to this GraphQL server provided the header "Accept: text/html"
  51. and as a result has been presented GraphiQL - an in-browser IDE for
  52. exploring GraphQL.
  53. If you wish to receive JSON, provide the header "Accept: application/json" or
  54. add "&raw" to the end of the URL within a browser.
  55. -->
  56. <!DOCTYPE html>
  57. <html>
  58. <head>
  59. <meta charset="utf-8" />
  60. <title>GraphiQL</title>
  61. <meta name="robots" content="noindex" />
  62. <meta name="referrer" content="origin" />
  63. <meta name="viewport" content="width=device-width, initial-scale=1" />
  64. <style>
  65. body {
  66. margin: 0;
  67. overflow: hidden;
  68. }
  69. #graphiql {
  70. height: 100vh;
  71. }
  72. </style>
  73. <style>
  74. /* graphiql/graphiql.css */
  75. ${loadFileStaticallyFromNPM('graphiql/graphiql.css')}
  76. </style>
  77. <script>
  78. // promise-polyfill/dist/polyfill.min.js
  79. ${loadFileStaticallyFromNPM('promise-polyfill/dist/polyfill.min.js')}
  80. </script>
  81. <script>
  82. // unfetch/dist/unfetch.umd.js
  83. ${loadFileStaticallyFromNPM('unfetch/dist/unfetch.umd.js')}
  84. </script>
  85. <script>
  86. // react/umd/react.production.min.js
  87. ${loadFileStaticallyFromNPM('react/umd/react.production.min.js')}
  88. </script>
  89. <script>
  90. // react-dom/umd/react-dom.production.min.js
  91. ${loadFileStaticallyFromNPM('react-dom/umd/react-dom.production.min.js')}
  92. </script>
  93. <script>
  94. // graphiql/graphiql.min.js
  95. ${loadFileStaticallyFromNPM('graphiql/graphiql.min.js')}
  96. </script>
  97. </head>
  98. <body>
  99. <div id="graphiql">Loading...</div>
  100. <script>
  101. // Collect the URL parameters
  102. var parameters = {};
  103. window.location.search.substr(1).split('&').forEach(function (entry) {
  104. var eq = entry.indexOf('=');
  105. if (eq >= 0) {
  106. parameters[decodeURIComponent(entry.slice(0, eq))] =
  107. decodeURIComponent(entry.slice(eq + 1));
  108. }
  109. });
  110. // Produce a Location query string from a parameter object.
  111. function locationQuery(params) {
  112. return '?' + Object.keys(params).filter(function (key) {
  113. return Boolean(params[key]);
  114. }).map(function (key) {
  115. return encodeURIComponent(key) + '=' +
  116. encodeURIComponent(params[key]);
  117. }).join('&');
  118. }
  119. // Derive a fetch URL from the current URL, sans the GraphQL parameters.
  120. var graphqlParamNames = {
  121. query: true,
  122. variables: true,
  123. operationName: true
  124. };
  125. var otherParams = {};
  126. for (var k in parameters) {
  127. if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
  128. otherParams[k] = parameters[k];
  129. }
  130. }
  131. var fetchURL = locationQuery(otherParams);
  132. // Defines a GraphQL fetcher using the fetch API.
  133. function graphQLFetcher(graphQLParams, opts) {
  134. return fetch(fetchURL, {
  135. method: 'post',
  136. headers: Object.assign(
  137. {
  138. 'Accept': 'application/json',
  139. 'Content-Type': 'application/json'
  140. },
  141. opts && opts.headers,
  142. ),
  143. body: JSON.stringify(graphQLParams),
  144. credentials: 'include',
  145. }).then(function (response) {
  146. return response.json();
  147. });
  148. }
  149. // When the query and variables string is edited, update the URL bar so
  150. // that it can be easily shared.
  151. function onEditQuery(newQuery) {
  152. parameters.query = newQuery;
  153. updateURL();
  154. }
  155. function onEditVariables(newVariables) {
  156. parameters.variables = newVariables;
  157. updateURL();
  158. }
  159. function onEditOperationName(newOperationName) {
  160. parameters.operationName = newOperationName;
  161. updateURL();
  162. }
  163. function updateURL() {
  164. history.replaceState(null, null, locationQuery(parameters));
  165. }
  166. // Render <GraphiQL /> into the body.
  167. ReactDOM.render(
  168. React.createElement(GraphiQL, {
  169. fetcher: graphQLFetcher,
  170. onEditQuery: onEditQuery,
  171. onEditVariables: onEditVariables,
  172. onEditOperationName: onEditOperationName,
  173. query: ${safeSerialize(queryString)},
  174. response: ${safeSerialize(resultString)},
  175. variables: ${safeSerialize(variablesString)},
  176. operationName: ${safeSerialize(operationName)},
  177. defaultQuery: ${safeSerialize(defaultQuery)},
  178. headerEditorEnabled: ${safeSerialize(headerEditorEnabled)},
  179. }),
  180. document.getElementById('graphiql')
  181. );
  182. </script>
  183. </body>
  184. </html>`;
  185. }