import type { ImplicitlyInstallablePlugin } from '../../../ApolloServer'; import type { ApolloServerPluginEmbeddedLandingPageProductionDefaultOptions, ApolloServerPluginLandingPageLocalDefaultOptions, ApolloServerPluginLandingPageProductionDefaultOptions, LandingPageConfig, } from './types'; export function ApolloServerPluginLandingPageLocalDefault( options: ApolloServerPluginLandingPageLocalDefaultOptions = {}, ): ImplicitlyInstallablePlugin { const { version, __internal_apolloStudioEnv__, ...rest } = options; return ApolloServerPluginLandingPageDefault(version, { isProd: false, apolloStudioEnv: __internal_apolloStudioEnv__, ...rest, }); } export function ApolloServerPluginLandingPageProductionDefault( options: ApolloServerPluginLandingPageProductionDefaultOptions = {}, ): ImplicitlyInstallablePlugin { const { version, __internal_apolloStudioEnv__, ...rest } = options; return ApolloServerPluginLandingPageDefault(version, { isProd: true, apolloStudioEnv: __internal_apolloStudioEnv__, ...rest, }); } // A triple encoding! Wow! First we use JSON.stringify to turn our object into a // string. Then we encodeURIComponent so we don't have to stress about what // would happen if the config contained ``. Finally, we JSON.stringify // it again, which in practice just wraps it in a pair of double quotes (since // there shouldn't be any backslashes left after encodeURIComponent). The // consumer of this needs to decodeURIComponent and then JSON.parse; there's // only one JSON.parse because the outermost JSON string is parsed by the JS // parser itself. function encodeConfig(config: LandingPageConfig): string { return JSON.stringify(encodeURIComponent(JSON.stringify(config))); } // This function turns an object into a string and replaces // <, >, &, ' with their unicode chars to avoid adding html tags to // the landing page html that might be passed from the config. // The only place these characters can appear in the output of // JSON.stringify is within string literals, where they can equally // well appear \u-escaped. This specifically means that // `` won't terminate the script block early. // (Perhaps we should have done this instead of the triple-encoding // of encodeConfig for the main landing page.) function getConfigStringForHtml(config: LandingPageConfig) { return JSON.stringify(config) .replace('<', '\\u003c') .replace('>', '\\u003e') .replace('&', '\\u0026') .replace("'", '\\u0027'); } export const getEmbeddedExplorerHTML = ( version: string, config: ApolloServerPluginEmbeddedLandingPageProductionDefaultOptions, ) => { interface EmbeddableExplorerOptions { graphRef: string; target: string; initialState?: { document?: string; variables?: Record; headers?: Record; displayOptions: { docsPanelState?: 'open' | 'closed'; // default to 'open', showHeadersAndEnvVars?: boolean; // default to `false` theme?: 'dark' | 'light'; }; }; persistExplorerState?: boolean; // defaults to 'false' endpointUrl: string; includeCookies?: boolean; // defaults to 'false' } const productionLandingPageConfigOrDefault = { displayOptions: {}, persistExplorerState: false, ...(typeof config.embed === 'boolean' ? {} : config.embed), }; const embeddedExplorerParams: Omit = { ...config, target: '#embeddableExplorer', initialState: { ...config, displayOptions: { ...productionLandingPageConfigOrDefault.displayOptions, }, }, persistExplorerState: productionLandingPageConfigOrDefault.persistExplorerState, }; return `

Welcome to Apollo Server

Apollo Explorer cannot be loaded; it appears that you might be offline.

`; }; export const getEmbeddedSandboxHTML = ( version: string, config: LandingPageConfig, ) => { return `

Welcome to Apollo Server

Apollo Sandbox cannot be loaded; it appears that you might be offline.

`; }; const getNonEmbeddedLandingPageHTML = ( version: string, config: LandingPageConfig, ) => { const encodedConfig = encodeConfig(config); return `

Welcome to Apollo Server

The full landing page cannot be loaded; it appears that you might be offline.

`; }; // Helper for the two actual plugin functions. function ApolloServerPluginLandingPageDefault( maybeVersion: string | undefined, config: LandingPageConfig & { isProd: boolean; apolloStudioEnv: 'staging' | 'prod' | undefined; }, ): ImplicitlyInstallablePlugin { const version = maybeVersion ?? '_latest'; return { __internal_installed_implicitly__: false, async serverWillStart() { return { async renderLandingPage() { const html = ` Apollo Server
${ config.embed ? 'graphRef' in config && config.graphRef ? getEmbeddedExplorerHTML(version, config) : getEmbeddedSandboxHTML(version, config) : getNonEmbeddedLandingPageHTML(version, config) }
`; return { html }; }, }; }, }; }