use axum::{
extract::State,
response::{Html, IntoResponse},
};
use crate::server_config::PlaygroundTool;
#[derive(Clone)]
pub struct PlaygroundState {
pub graphql_endpoint: String,
pub tool: PlaygroundTool,
}
impl PlaygroundState {
#[must_use]
pub fn new(graphql_endpoint: impl Into<String>, tool: PlaygroundTool) -> Self {
Self {
graphql_endpoint: graphql_endpoint.into(),
tool,
}
}
}
const PLAYGROUND_CSP: &str = "default-src 'self'; \
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://unpkg.com https://embeddable-sandbox.cdn.apollographql.com; \
style-src 'self' 'unsafe-inline' https://unpkg.com; \
img-src 'self' data: https:; \
font-src 'self' data: https://unpkg.com; \
worker-src 'self' blob:; \
connect-src 'self' https://*.apollographql.com; \
frame-src https://sandbox.embed.apollographql.com";
pub async fn playground_handler(State(state): State<PlaygroundState>) -> impl IntoResponse {
let html = match state.tool {
PlaygroundTool::GraphiQL => graphiql_html(&state.graphql_endpoint),
PlaygroundTool::ApolloSandbox => apollo_sandbox_html(&state.graphql_endpoint),
};
([(axum::http::header::CONTENT_SECURITY_POLICY, PLAYGROUND_CSP)], Html(html))
}
pub(crate) fn graphiql_html(endpoint: &str) -> String {
format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>FraiseQL - GraphiQL</title>
<style>
body {{
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}}
#graphiql {{
height: 100vh;
}}
</style>
<link rel="stylesheet" href="https://unpkg.com/graphiql/graphiql.min.css" />
</head>
<body>
<div id="graphiql">Loading...</div>
<script
crossorigin
src="https://unpkg.com/react@18/umd/react.production.min.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
></script>
<script
crossorigin
src="https://unpkg.com/graphiql/graphiql.min.js"
></script>
<script>
const fetcher = GraphiQL.createFetcher({{
url: '{endpoint}',
}});
ReactDOM.createRoot(document.getElementById('graphiql')).render(
React.createElement(GraphiQL, {{
fetcher,
defaultEditorToolsVisibility: true,
}})
);
</script>
</body>
</html>"#
)
}
pub(crate) fn apollo_sandbox_html(endpoint: &str) -> String {
format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>FraiseQL - Apollo Sandbox</title>
<style>
body {{
height: 100vh;
margin: 0;
width: 100%;
overflow: hidden;
}}
#sandbox {{
height: 100%;
width: 100%;
}}
</style>
</head>
<body>
<div id="sandbox">Loading Apollo Sandbox...</div>
<script src="https://embeddable-sandbox.cdn.apollographql.com/_latest/embeddable-sandbox.umd.production.min.js"></script>
<script>
new window.EmbeddedSandbox({{
target: '#sandbox',
initialEndpoint: window.location.origin + '{endpoint}',
includeCookies: false,
}});
</script>
</body>
</html>"#
)
}