Skip to main content
Home

Built and signed on GitHub Actions

Universal and spec-compliant GraphQL HTTP middleware for Deno. Based on graphql-http.

This package works with Deno, Bun
This package works with Deno
This package works with Bun
JSR Score
70%
Published
10 months ago (4.0.0)
import { filterXSS } from 'npm:xss@1.0.15' import { getLoadingMarkup } from './markup.ts' export interface MiddlewareOptions { endpoint?: string subscriptionEndpoint?: string workspaceName?: string config?: unknown settings?: ISettings schema?: IntrospectionResult tabs?: Tab[] codeTheme?: EditorColours } export type CursorShape = 'line' | 'block' | 'underline' export type Theme = 'dark' | 'light' export interface ISettings { 'general.betaUpdates': boolean 'editor.cursorShape': CursorShape 'editor.theme': Theme 'editor.reuseHeaders': boolean 'tracing.hideTracingResponse': boolean 'tracing.tracingSupported': boolean 'editor.fontSize': number 'editor.fontFamily': string 'request.credentials': string 'request.globalHeaders': { [key: string]: string } 'schema.polling.enable': boolean 'schema.polling.endpointFilter': string 'schema.polling.interval': number } export interface EditorColours { property: string comment: string punctuation: string keyword: string def: string qualifier: string attribute: string number: string string: string builtin: string string2: string variable: string meta: string atom: string ws: string selection: string cursorColor: string editorBackground: string resultBackground: string leftDrawerBackground: string rightDrawerBackground: string } export interface IntrospectionResult { // deno-lint-ignore no-explicit-any __schema: any } export interface RenderPageOptions extends MiddlewareOptions { version?: string cdnUrl?: string title?: string faviconUrl?: string | null } export interface Tab { endpoint: string query: string name?: string variables?: string responses?: string[] headers?: { [key: string]: string } } const loading = getLoadingMarkup() const CONFIG_ID = 'playground-config' const filter = (val: string) => { return filterXSS(val, { stripIgnoreTag: true, stripIgnoreTagBody: ['script'], }) } const getCdnMarkup = ({ version, cdnUrl = '//cdn.jsdelivr.net/npm', faviconUrl, }: { faviconUrl?: string | null version?: string cdnUrl?: string }) => { const buildCDNUrl = (packageName: string, suffix: string) => filter( `${cdnUrl}/${packageName}${version ? `@${version}` : ''}/${suffix}` || '', ) return ` <link rel="stylesheet" href="${ buildCDNUrl('graphql-playground-react', 'build/static/css/index.css') }" /> ${ typeof faviconUrl === 'string' ? `<link rel="shortcut icon" href="${filter(faviconUrl || '')}" />` : '' } ${ faviconUrl === undefined ? `<link rel="shortcut icon" href="${ buildCDNUrl('graphql-playground-react', 'build/favicon.png') }" />` : '' } <script src="${ buildCDNUrl('graphql-playground-react', 'build/static/js/middleware.js') }" ></script> ` } const renderConfig = (config: unknown) => { return filterXSS(`<div id="${CONFIG_ID}">${JSON.stringify(config)}</div>`, { whiteList: { div: ['id'] }, }) } export function renderPlaygroundPage(options: RenderPageOptions): string { const extendedOptions: & Partial<{ canSaveConfig: boolean configString: string }> & RenderPageOptions = { ...options, canSaveConfig: false, } if (options.config) { extendedOptions.configString = JSON.stringify(options.config, null, 2) } if (!extendedOptions.endpoint && !extendedOptions.configString) { console.warn( `WARNING: You didn't provide an endpoint and don't have a config. Make sure you have at least one of them.`, ) } else if (extendedOptions.endpoint) { extendedOptions.endpoint = filter(extendedOptions.endpoint || '') } return ` <!DOCTYPE html> <html> <head> <meta charset=utf-8 /> <meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui"> <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Source+Code+Pro:400,700" rel="stylesheet"> <title>${extendedOptions.title || 'GraphQL Playground'}</title> ${getCdnMarkup(extendedOptions)} </head> <body> <style type="text/css"> html { font-family: "Open Sans", sans-serif; overflow: hidden; } body { margin: 0; background: #172a3a; } #${CONFIG_ID} { display: none; } .playgroundIn { -webkit-animation: playgroundIn 0.5s ease-out forwards; animation: playgroundIn 0.5s ease-out forwards; } @-webkit-keyframes playgroundIn { from { opacity: 0; -webkit-transform: translateY(10px); -ms-transform: translateY(10px); transform: translateY(10px); } to { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } @keyframes playgroundIn { from { opacity: 0; -webkit-transform: translateY(10px); -ms-transform: translateY(10px); transform: translateY(10px); } to { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } </style> ${loading.container} ${renderConfig(extendedOptions)} <div id="root" /> <script type="text/javascript"> window.addEventListener('load', function (event) { ${loading.script} const root = document.getElementById('root'); root.classList.add('playgroundIn'); const configText = document.getElementById('${CONFIG_ID}').innerText; if(configText && configText.length) { try { GraphQLPlayground.init(root, JSON.parse(configText)); } catch(err) { console.error("could not find config") } } else { GraphQLPlayground.init(root); } }) </script> </body> </html> ` }