import type { ApolloServerPlugin, BaseContext, } from '../../../externalTypes/index.js'; import type { ImplicitlyInstallablePlugin } from '../../../ApolloServer.js'; import type { ApolloServerPluginLandingPageLocalDefaultOptions, ApolloServerPluginLandingPageProductionDefaultOptions, LandingPageConfig, } from './types.js'; import { getEmbeddedExplorerHTML, getEmbeddedSandboxHTML, } from './getEmbeddedHTML.js'; import { packageVersion } from '../../../generated/packageVersion.js'; import { createHash } from '@apollo/utils.createhash'; import { v4 as uuidv4 } from 'uuid'; export type { ApolloServerPluginLandingPageLocalDefaultOptions, ApolloServerPluginLandingPageProductionDefaultOptions, }; export function ApolloServerPluginLandingPageLocalDefault( options: ApolloServerPluginLandingPageLocalDefaultOptions = {}, ): ApolloServerPlugin { const { version, __internal_apolloStudioEnv__, ...rest } = { // we default to Sandbox unless embed is specified as false embed: true as const, ...options, }; return ApolloServerPluginLandingPageDefault(version, { isProd: false, apolloStudioEnv: __internal_apolloStudioEnv__, ...rest, }); } export function ApolloServerPluginLandingPageProductionDefault( options: ApolloServerPluginLandingPageProductionDefaultOptions = {}, ): ApolloServerPlugin { 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))); } const getNonEmbeddedLandingPageHTML = ( cdnVersion: string, config: LandingPageConfig, apolloServerVersion: string, nonce: string, ) => { const encodedConfig = encodeConfig(config); return `

Welcome to Apollo Server

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

`; }; export const DEFAULT_EMBEDDED_EXPLORER_VERSION = 'v3'; export const DEFAULT_EMBEDDED_SANDBOX_VERSION = 'v2'; export const DEFAULT_APOLLO_SERVER_LANDING_PAGE_VERSION = '_latest'; // Helper for the two actual plugin functions. function ApolloServerPluginLandingPageDefault( maybeVersion: string | undefined, config: LandingPageConfig & { isProd: boolean; apolloStudioEnv: 'staging' | 'prod' | undefined; }, ): ImplicitlyInstallablePlugin { const explorerVersion = maybeVersion ?? DEFAULT_EMBEDDED_EXPLORER_VERSION; const sandboxVersion = maybeVersion ?? DEFAULT_EMBEDDED_SANDBOX_VERSION; const apolloServerLandingPageVersion = maybeVersion ?? DEFAULT_APOLLO_SERVER_LANDING_PAGE_VERSION; const apolloServerVersion = `@apollo/server@${packageVersion}`; const scriptSafeList = [ 'https://apollo-server-landing-page.cdn.apollographql.com', 'https://embeddable-sandbox.cdn.apollographql.com', 'https://embeddable-explorer.cdn.apollographql.com', ].join(' '); const styleSafeList = [ 'https://apollo-server-landing-page.cdn.apollographql.com', 'https://embeddable-sandbox.cdn.apollographql.com', 'https://embeddable-explorer.cdn.apollographql.com', 'https://fonts.googleapis.com', ].join(' '); const iframeSafeList = [ 'https://explorer.embed.apollographql.com', 'https://sandbox.embed.apollographql.com', 'https://embed.apollo.local:3000', ].join(' '); return { __internal_installed_implicitly__: false, async serverWillStart() { return { async renderLandingPage() { const encodedASLandingPageVersion = encodeURIComponent( apolloServerLandingPageVersion, ); async function html() { const nonce = createHash('sha256').update(uuidv4()).digest('hex'); const scriptCsp = `script-src 'self' 'nonce-${nonce}' ${scriptSafeList}`; const styleCsp = `style-src 'nonce-${nonce}' ${styleSafeList}`; const imageCsp = `img-src https://apollo-server-landing-page.cdn.apollographql.com`; const manifestCsp = `manifest-src https://apollo-server-landing-page.cdn.apollographql.com`; const frameCsp = `frame-src ${iframeSafeList}`; return ` Apollo Server
${ config.embed ? 'graphRef' in config && config.graphRef ? getEmbeddedExplorerHTML( explorerVersion, config, apolloServerVersion, nonce, ) : !('graphRef' in config) ? getEmbeddedSandboxHTML( sandboxVersion, config, apolloServerVersion, nonce, ) : getNonEmbeddedLandingPageHTML( apolloServerLandingPageVersion, config, apolloServerVersion, nonce, ) : getNonEmbeddedLandingPageHTML( apolloServerLandingPageVersion, config, apolloServerVersion, nonce, ) }
`; } return { html }; }, }; }, }; }