Skip to content

Commit bcb4b37

Browse files
author
Nico Schett
committed
feat: allow enable/disable of graphiql, viewer and introspection
1 parent b1eeb43 commit bcb4b37

File tree

7 files changed

+2356
-2238
lines changed

7 files changed

+2356
-2238
lines changed

packages/pylon/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"homepage": "https://pylon.cronit.io",
2323
"dependencies": {
2424
"@envelop/core": "^5.0.3",
25+
"@envelop/disable-introspection": "^8.0.0",
2526
"@getcronit/pylon-telemetry": "workspace:^",
2627
"@hono/sentry": "^1.2.0",
2728
"@sentry/bun": "^8.17.0",
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {Plugin} from 'graphql-yoga'
2+
import {getContext} from '../../context'
3+
import {html} from 'hono/html'
4+
5+
export type ViewerPluginOptions<PluginContext extends Record<string, any>> = {
6+
disableIf?: () => boolean
7+
}
8+
9+
export const useViewer = <PluginContext extends Record<string, any> = {}>(
10+
options: ViewerPluginOptions<PluginContext> = {}
11+
): Plugin<PluginContext> => {
12+
return {
13+
async onRequest({url, endResponse}) {
14+
const c = getContext()
15+
16+
if (
17+
url.pathname === '/viewer' &&
18+
(typeof options.disableIf !== 'undefined' ? options.disableIf() : true)
19+
) {
20+
const res = await c.html(html`
21+
<!DOCTYPE html>
22+
<html>
23+
<head>
24+
<title>Pylon Viewer</title>
25+
<script src="https://cdn.jsdelivr.net/npm/react@16/umd/react.production.min.js"></script>
26+
<script src="https://cdn.jsdelivr.net/npm/react-dom@16/umd/react-dom.production.min.js"></script>
27+
28+
<link
29+
rel="stylesheet"
30+
href="https://cdn.jsdelivr.net/npm/graphql-voyager/dist/voyager.css"
31+
/>
32+
<style>
33+
body {
34+
padding: 0;
35+
margin: 0;
36+
width: 100%;
37+
height: 100vh;
38+
overflow: hidden;
39+
}
40+
41+
#voyager {
42+
height: 100%;
43+
position: relative;
44+
}
45+
}
46+
</style>
47+
<script src="https://cdn.jsdelivr.net/npm/graphql-voyager/dist/voyager.min.js"></script>
48+
</head>
49+
<body>
50+
<div id="voyager">Loading...</div>
51+
<script>
52+
function introspectionProvider(introspectionQuery) {
53+
// ... do a call to server using introspectionQuery provided
54+
// or just return pre-fetched introspection
55+
56+
// Endpoint is current path instead of root/graphql
57+
const endpoint = window.location.pathname.replace(
58+
'/viewer',
59+
'/graphql'
60+
)
61+
62+
return fetch(endpoint, {
63+
method: 'post',
64+
headers: {
65+
'Content-Type': 'application/json'
66+
},
67+
body: JSON.stringify({query: introspectionQuery})
68+
}).then(response => response.json())
69+
}
70+
71+
// Render <Voyager />
72+
GraphQLVoyager.init(document.getElementById('voyager'), {
73+
introspection: introspectionProvider
74+
})
75+
</script>
76+
</body>
77+
</html>
78+
`)
79+
80+
return endResponse(res)
81+
}
82+
}
83+
}
84+
}

packages/pylon/src/app/handler/graphql-viewer-handler.ts

Lines changed: 0 additions & 64 deletions
This file was deleted.

packages/pylon/src/app/handler/pylon-handler.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import {createSchema, createYoga} from 'graphql-yoga'
21
import {GraphQLScalarType, Kind} from 'graphql'
32
import {
43
DateTimeISOResolver,
54
GraphQLVoid,
65
JSONObjectResolver,
76
JSONResolver
87
} from 'graphql-scalars'
8+
import {createSchema, createYoga} from 'graphql-yoga'
99

10-
import {useSentry} from '../envelop/use-sentry'
11-
import {Context} from '../../context'
12-
import {resolversToGraphQLResolvers} from '../../define-pylon'
13-
import {PylonConfig} from '../..'
10+
import {useDisableIntrospection} from '@envelop/disable-introspection'
1411
import {readFileSync} from 'fs'
1512
import path from 'path'
13+
import {PylonConfig} from '../..'
14+
import {Context, getContext} from '../../context'
15+
import {resolversToGraphQLResolvers} from '../../define-pylon'
16+
import {useSentry} from '../envelop/use-sentry'
17+
import {useViewer} from '../envelop/use-viewer'
1618

1719
interface PylonHandlerOptions {
1820
graphql: {
@@ -102,18 +104,44 @@ export const handler = (options: PylonHandlerOptions) => {
102104
}
103105
})
104106

107+
const resolveGraphiql = (config?: PylonConfig) => {
108+
const graphiqlOptions = {
109+
shouldPersistHeaders: true,
110+
title: 'Pylon Playground',
111+
defaultQuery: `# Welcome to the Pylon Playground!`
112+
}
113+
114+
if (typeof config?.graphiql === 'undefined') {
115+
return graphiqlOptions
116+
}
117+
118+
if (typeof config.graphiql === 'boolean') {
119+
return config.graphiql ? graphiqlOptions : false
120+
}
121+
122+
if (typeof config.graphiql === 'function') {
123+
const c = getContext()
124+
return config.graphiql(c) ? graphiqlOptions : false
125+
}
126+
127+
return false // fallback safeguard
128+
}
129+
105130
const yoga = createYoga({
106131
landingPage: false,
107-
graphiql: req => {
108-
return {
109-
shouldPersistHeaders: true,
110-
title: 'Pylon Playground',
111-
defaultQuery: `# Welcome to the Pylon Playground!`
112-
}
113-
},
114132
graphqlEndpoint: '/graphql',
115133
...config,
116-
plugins: [useSentry(), ...(config?.plugins || [])],
134+
graphiql: async () => resolveGraphiql(config),
135+
plugins: [
136+
useSentry(),
137+
useDisableIntrospection({
138+
disableIf: () => resolveGraphiql(config) !== false
139+
}),
140+
useViewer({
141+
disableIf: () => resolveGraphiql(config) !== false
142+
}),
143+
...(config?.plugins || [])
144+
],
117145
schema
118146
})
119147

packages/pylon/src/app/index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
import {sentry} from '@hono/sentry'
12
import {Hono} from 'hono'
23
import {logger} from 'hono/logger'
3-
import {sentry} from '@hono/sentry'
44

55
import {asyncContext, Env} from '../context'
6-
import {graphqlViewerHandler} from './handler/graphql-viewer-handler'
76

87
export const app = new Hono<Env>()
98

@@ -28,5 +27,3 @@ app.use((c, next) => {
2827
c.req.id = crypto.randomUUID()
2928
return next()
3029
})
31-
32-
app.get('/viewer', graphqlViewerHandler)

packages/pylon/src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ export {getEnv} from './get-env.js'
1818
export {createDecorator} from './create-decorator.js'
1919
export {createPubSub as experimentalCreatePubSub} from 'graphql-yoga'
2020

21-
export type PylonConfig = Pick<YogaServerOptions<Context, Context>, 'plugins'>
21+
export type PylonConfig = Pick<
22+
YogaServerOptions<Context, Context>,
23+
'plugins'
24+
> & {
25+
graphiql: boolean | ((c: Context) => boolean)
26+
}
2227

2328
export type ID = string & {readonly brand?: unique symbol}
2429
export type Int = number & {readonly brand?: unique symbol}

0 commit comments

Comments
 (0)