diff --git a/keyserver/src/responders/landing-handler.js b/keyserver/src/responders/landing-handler.js index 8eb631e57..9eb2d66b2 100644 --- a/keyserver/src/responders/landing-handler.js +++ b/keyserver/src/responders/landing-handler.js @@ -1,176 +1,183 @@ // @flow import html from 'common-tags/lib/html'; import type { $Response, $Request } from 'express'; import fs from 'fs'; import * as React from 'react'; import ReactDOMServer from 'react-dom/server'; import { promisify } from 'util'; import { type LandingSSRProps } from '../landing/landing-ssr.react'; import { waitForStream } from '../utils/json-stream'; import { getAndAssertLandingURLFacts } from '../utils/urls'; import { getMessageForException } from './utils'; async function landingHandler(req: $Request, res: $Response) { try { await landingResponder(req, res); } catch (e) { console.warn(e); if (!res.headersSent) { res.status(500).send(getMessageForException(e)); } } } const access = promisify(fs.access); const readFile = promisify(fs.readFile); const googleFontsURL = 'https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500&family=IBM+Plex+Sans:wght@400;500&display=swap'; const iaDuoFontsURL = 'fonts/duo.css'; const localFontsURL = 'fonts/local-fonts.css'; async function getDevFontURLs(): Promise<$ReadOnlyArray> { try { await access(localFontsURL); return [localFontsURL, iaDuoFontsURL]; } catch { return [googleFontsURL, iaDuoFontsURL]; } } type AssetInfo = { +jsURL: string, +fontURLs: $ReadOnlyArray, +cssInclude: string, }; let assetInfo: ?AssetInfo = null; async function getAssetInfo() { if (assetInfo) { return assetInfo; } if (process.env.NODE_ENV === 'development') { const fontURLs = await getDevFontURLs(); assetInfo = { jsURL: 'http://localhost:8082/dev.build.js', fontURLs, cssInclude: '', }; return assetInfo; } try { const assetsString = await readFile('../landing/dist/assets.json', 'utf8'); const assets = JSON.parse(assetsString); assetInfo = { jsURL: `compiled/${assets.browser.js}`, fontURLs: [googleFontsURL, iaDuoFontsURL], cssInclude: html` `, }; return assetInfo; } catch { throw new Error( 'Could not load assets.json for landing build. ' + 'Did you forget to run `yarn dev` in the landing folder?', ); } } type LandingApp = React.ComponentType; let webpackCompiledRootComponent: ?LandingApp = null; async function getWebpackCompiledRootComponentForSSR() { if (webpackCompiledRootComponent) { return webpackCompiledRootComponent; } try { // $FlowFixMe landing/dist doesn't always exist const webpackBuild = await import('landing/dist/landing.build.cjs'); webpackCompiledRootComponent = webpackBuild.default.default; return webpackCompiledRootComponent; } catch { throw new Error( 'Could not load landing.build.cjs. ' + 'Did you forget to run `yarn dev` in the landing folder?', ); } } const { renderToNodeStream } = ReactDOMServer; async function landingResponder(req: $Request, res: $Response) { + const siweNonce = req.header('siwe-nonce'); const [{ jsURL, fontURLs, cssInclude }, LandingSSR] = await Promise.all([ getAssetInfo(), getWebpackCompiledRootComponentForSSR(), ]); const fontsInclude = fontURLs .map(url => ``) .join(''); const urlFacts = getAndAssertLandingURLFacts(); const { basePath } = urlFacts; // prettier-ignore res.write(html` Comm ${fontsInclude} ${cssInclude}
`); // We remove trailing slash for `react-router` const routerBasename = basePath.replace(/\/$/, ''); const clientPath = routerBasename + req.url; const reactStream = renderToNodeStream( - , + , ); reactStream.pipe(res, { end: false }); await waitForStream(reactStream); + const siweNonceString = siweNonce ? `"${siweNonce}"` : 'null'; // prettier-ignore res.end(html`
+ `); } export default landingHandler; diff --git a/landing/landing-ssr.react.js b/landing/landing-ssr.react.js index 09aa370e7..438d66338 100644 --- a/landing/landing-ssr.react.js +++ b/landing/landing-ssr.react.js @@ -1,22 +1,33 @@ // @flow import * as React from 'react'; import { StaticRouter } from 'react-router'; import Landing from './landing.react'; +import { SIWENonceContext } from './siwe-nonce-context'; export type LandingSSRProps = { +url: string, +basename: string, + +siweNonce: ?string, }; function LandingSSR(props: LandingSSRProps): React.Node { - const { url, basename } = props; + const { url, basename, siweNonce } = props; + + const siweNonceContextValue = React.useMemo( + () => ({ + siweNonce, + }), + [siweNonce], + ); const routerContext = React.useMemo(() => ({}), []); return ( - + + + ); } export default LandingSSR; diff --git a/landing/root.js b/landing/root.js index 3598ddefb..049b836d8 100644 --- a/landing/root.js +++ b/landing/root.js @@ -1,18 +1,28 @@ // @flow import * as React from 'react'; import { BrowserRouter } from 'react-router-dom'; import Landing from './landing.react'; +import { SIWENonceContext } from './siwe-nonce-context.js'; declare var routerBasename: string; +declare var siweNonce: ?string; function RootComponent(): React.Node { + const siweNonceContextValue = React.useMemo( + () => ({ + siweNonce, + }), + [], + ); return ( - + + + ); } export default RootComponent;