2023-04-19 06:46:17 +00:00
import type { NextPageContext } from "next" ;
2023-02-16 22:39:57 +00:00
import type { DocumentContext , DocumentProps } from "next/document" ;
import Document , { Head , Html , Main , NextScript } from "next/document" ;
2023-02-06 22:50:08 +00:00
import { z } from "zod" ;
2021-07-07 19:58:18 +00:00
2023-08-10 16:32:20 +00:00
import { IS_PRODUCTION } from "@calcom/lib/constants" ;
2023-02-06 22:50:08 +00:00
import { csp } from "@lib/csp" ;
2022-07-28 10:50:25 +00:00
2023-02-06 22:50:08 +00:00
type Props = Record < string , unknown > & DocumentProps ;
2023-04-19 06:46:17 +00:00
function setHeader ( ctx : NextPageContext , name : string , value : string ) {
try {
ctx . res ? . setHeader ( name , value ) ;
} catch ( e ) {
// Getting "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" when revalidate calendar chache
console . log ( ` Error setting header ${ name } = ${ value } for ${ ctx . asPath || "unknown asPath" } ` , e ) ;
}
}
2021-09-09 13:51:06 +00:00
class MyDocument extends Document < Props > {
static async getInitialProps ( ctx : DocumentContext ) {
2023-02-06 22:50:08 +00:00
const { nonce } = csp ( ctx . req || null , ctx . res || null ) ;
if ( ! process . env . CSP_POLICY ) {
2023-04-19 06:46:17 +00:00
setHeader ( ctx , "x-csp" , "not-opted-in" ) ;
2023-02-06 22:50:08 +00:00
} else if ( ! ctx . res ? . getHeader ( "x-csp" ) ) {
// If x-csp not set by gSSP, then it's initialPropsOnly
2023-04-19 06:46:17 +00:00
setHeader ( ctx , "x-csp" , "initialPropsOnly" ) ;
2023-02-06 22:50:08 +00:00
}
2023-05-09 19:27:05 +00:00
2023-02-20 11:31:56 +00:00
const asPath = ctx . asPath || "" ;
// Use a dummy URL as default so that URL parsing works for relative URLs as well. We care about searchParams and pathname only
const parsedUrl = new URL ( asPath , "https://dummyurl" ) ;
2023-09-28 18:08:42 +00:00
const isEmbedSnippetGeneratorPath = parsedUrl . pathname . startsWith ( "/event-types" ) ;
// FIXME: Revisit this logic to remove embedType query param check completely. Ideally, /embed should always be there at the end of the URL. Test properly and then remove it.
const isEmbed =
( parsedUrl . pathname . endsWith ( "/embed" ) || parsedUrl . searchParams . get ( "embedType" ) !== null ) &&
! isEmbedSnippetGeneratorPath ;
2023-07-18 16:46:35 +00:00
const embedColorScheme = parsedUrl . searchParams . get ( "ui.color-scheme" ) ;
2021-07-07 19:58:18 +00:00
const initialProps = await Document . getInitialProps ( ctx ) ;
2023-07-18 16:46:35 +00:00
return { isEmbed , embedColorScheme , nonce , . . . initialProps } ;
2021-07-07 19:58:18 +00:00
}
render() {
2022-07-12 14:18:53 +00:00
const { locale } = this . props . __NEXT_DATA__ ;
2023-07-18 16:46:35 +00:00
const { isEmbed , embedColorScheme } = this . props ;
2023-02-06 22:50:08 +00:00
const nonceParsed = z . string ( ) . safeParse ( this . props . nonce ) ;
const nonce = nonceParsed . success ? nonceParsed . data : "" ;
2021-07-07 19:58:18 +00:00
return (
2023-07-18 16:46:35 +00:00
< Html lang = { locale } style = { embedColorScheme ? { colorScheme : embedColorScheme as string } : undefined } >
2023-02-06 22:50:08 +00:00
< Head nonce = { nonce } >
2023-05-30 16:31:49 +00:00
< link rel = "apple-touch-icon" sizes = "180x180" href = "/api/logo?type=apple-touch-icon" / >
< link rel = "icon" type = "image/png" sizes = "32x32" href = "/api/logo?type=favicon-32" / >
< link rel = "icon" type = "image/png" sizes = "16x16" href = "/api/logo?type=favicon-16" / >
2021-08-03 09:39:06 +00:00
< link rel = "manifest" href = "/site.webmanifest" / >
< link rel = "mask-icon" href = "/safari-pinned-tab.svg" color = "#000000" / >
< meta name = "msapplication-TileColor" content = "#ff0000" / >
2023-05-15 20:15:05 +00:00
< meta name = "theme-color" media = "(prefers-color-scheme: light)" content = "#f9fafb" / >
2023-05-11 23:50:11 +00:00
< meta name = "theme-color" media = "(prefers-color-scheme: dark)" content = "#1C1C1C" / >
2023-08-24 08:12:53 +00:00
{ ! IS_PRODUCTION && process . env . VERCEL_ENV === "preview" && (
2023-07-11 11:37:59 +00:00
// eslint-disable-next-line @next/next/no-sync-scripts
< script
data - project - id = "KjpMrKTnXquJVKfeqmjdTffVPf1a6Unw2LZ58iE4"
src = "https://snippet.meticulous.ai/v1/stagingMeticulousSnippet.js"
/ >
) }
2021-08-03 09:32:37 +00:00
< / Head >
2022-03-31 08:45:47 +00:00
2022-10-19 21:25:03 +00:00
< body
2023-04-05 18:14:46 +00:00
className = "dark:bg-darkgray-50 desktop-transparent bg-subtle antialiased"
2022-10-19 21:25:03 +00:00
style = {
2023-02-06 22:50:08 +00:00
isEmbed
2022-10-19 21:25:03 +00:00
? {
background : "transparent" ,
// Keep the embed hidden till parent initializes and
// - gives it the appropriate styles if UI instruction is there.
// - gives iframe the appropriate height(equal to document height) which can only be known after loading the page once in browser.
// - Tells iframe which mode it should be in (dark/light) - if there is a a UI instruction for that
visibility : "hidden" ,
}
: { }
} >
2021-07-07 19:58:18 +00:00
< Main / >
2023-02-06 22:50:08 +00:00
< NextScript nonce = { nonce } / >
2021-07-07 19:58:18 +00:00
< / body >
< / Html >
) ;
}
}
export default MyDocument ;