diff --git a/configuration.d.ts b/configuration.d.ts index d1a885e..e827106 100644 --- a/configuration.d.ts +++ b/configuration.d.ts @@ -38,6 +38,7 @@ declare module 'virtual:vuetify-ssr-client-hints-configuration' { cookieName: string darkThemeName: string lightThemeName: string + cookieDomain?: string useBrowserThemeOnly: boolean } } diff --git a/src/module.ts b/src/module.ts index 94cbb0c..2c6babb 100644 --- a/src/module.ts +++ b/src/module.ts @@ -6,6 +6,7 @@ import { isNuxtMajorVersion, useLogger, } from '@nuxt/kit' +import { defu } from 'defu' import { getPackageInfo } from 'local-pkg' import semver from 'semver' import type { HookResult } from '@nuxt/schema' @@ -109,6 +110,24 @@ export default defineNuxtModule({ await load(options, nuxt, ctx) + const { + reloadOnFirstRequest, + viewportSize, + prefersColorScheme, + prefersReducedMotion, + prefersColorSchemeOptions, + } = ctx.ssrClientHints + + nuxt.options.runtimeConfig.public.vuetify = defu(nuxt.options.runtimeConfig.public.vuetify as any, { + ssrClientHints: { + reloadOnFirstRequest, + viewportSize, + prefersColorScheme, + prefersReducedMotion, + prefersColorSchemeOptions, + }, + }) + configureNuxt(CONFIG_KEY, nuxt, ctx) registerWatcher(options, nuxt, ctx) diff --git a/src/runtime/plugins/vuetify-client-hints.client.ts b/src/runtime/plugins/vuetify-client-hints.client.ts index ff1ae5a..0a03ab4 100644 --- a/src/runtime/plugins/vuetify-client-hints.client.ts +++ b/src/runtime/plugins/vuetify-client-hints.client.ts @@ -3,7 +3,7 @@ import type { UnwrapNestedRefs } from 'vue' import { reactive, ref, watch } from 'vue' import type { SSRClientHints } from './types' import { VuetifyHTTPClientHints } from './client-hints' -import { defineNuxtPlugin, useNuxtApp, useState } from '#imports' +import { defineNuxtPlugin, useNuxtApp, useRuntimeConfig, useState } from '#imports' import type { Plugin } from '#app' const plugin: Plugin<{ @@ -143,19 +143,32 @@ function useSSRClientHints() { const initial = ref(defaultClientValues()) - if (!ssrClientHintsConfiguration.prefersColorScheme || !ssrClientHintsConfiguration.prefersColorSchemeOptions) + const runtimeConfig = useRuntimeConfig() + const ssrClientHints = runtimeConfig.public.vuetify?.ssrClientHints ?? {} + const prefersColorScheme = ssrClientHints.prefersColorScheme ?? ssrClientHintsConfiguration.prefersColorScheme + const prefersColorSchemeOptions = ssrClientHintsConfiguration.prefersColorSchemeOptions + ? { + ...ssrClientHintsConfiguration.prefersColorSchemeOptions, + ...ssrClientHints.prefersColorSchemeOptions, + } + : ssrClientHints.prefersColorSchemeOptions + + if (!prefersColorScheme || !prefersColorSchemeOptions) return initial const { baseUrl, cookieName, + cookieDomain, defaultTheme, - } = ssrClientHintsConfiguration.prefersColorSchemeOptions + } = prefersColorSchemeOptions const cookieNamePrefix = `${cookieName}=` initial.value.colorSchemeFromCookie = document.cookie?.split(';')?.find(c => c.trim().startsWith(cookieNamePrefix))?.split('=')[1] ?? defaultTheme const date = new Date() const expires = new Date(date.setDate(date.getDate() + 365)) initial.value.colorSchemeCookie = `${cookieName}=${initial.value.colorSchemeFromCookie}; Path=${baseUrl}; Expires=${expires.toUTCString()}; SameSite=Lax` + if (cookieDomain) + initial.value.colorSchemeCookie += `; Domain=${cookieDomain}` return initial } diff --git a/src/runtime/plugins/vuetify-client-hints.server.ts b/src/runtime/plugins/vuetify-client-hints.server.ts index 69a1070..1571076 100644 --- a/src/runtime/plugins/vuetify-client-hints.server.ts +++ b/src/runtime/plugins/vuetify-client-hints.server.ts @@ -14,6 +14,7 @@ import { useNuxtApp, useRequestEvent, useRequestHeaders, + useRuntimeConfig, useState, } from '#imports' import type { Plugin } from '#app' @@ -53,15 +54,32 @@ const plugin: Plugin<{ const userAgent = userAgentHeader ? parseUserAgent(userAgentHeader) : null + + const runtimeConfig = useRuntimeConfig() + const ssrClientHints = (runtimeConfig.public.vuetify as any)?.ssrClientHints ?? {} + const configuration = { + ...ssrClientHintsConfiguration, + reloadOnFirstRequest: ssrClientHints.reloadOnFirstRequest ?? ssrClientHintsConfiguration.reloadOnFirstRequest, + viewportSize: ssrClientHints.viewportSize ?? ssrClientHintsConfiguration.viewportSize, + prefersReducedMotion: ssrClientHints.prefersReducedMotion ?? ssrClientHintsConfiguration.prefersReducedMotion, + prefersColorScheme: ssrClientHints.prefersColorScheme ?? ssrClientHintsConfiguration.prefersColorScheme, + prefersColorSchemeOptions: ssrClientHintsConfiguration.prefersColorSchemeOptions + ? { + ...ssrClientHintsConfiguration.prefersColorSchemeOptions, + ...ssrClientHints.prefersColorSchemeOptions, + } + : ssrClientHints.prefersColorSchemeOptions, + } + // 2. prepare client hints request - const clientHintsRequest = collectClientHints(userAgent, ssrClientHintsConfiguration, requestHeaders) + const clientHintsRequest = collectClientHints(userAgent, configuration, requestHeaders) // 3. write client hints response headers - writeClientHintsResponseHeaders(clientHintsRequest, ssrClientHintsConfiguration) + writeClientHintsResponseHeaders(clientHintsRequest, configuration) state.value = clientHintsRequest // 4. send the theme cookie to the client when required state.value.colorSchemeCookie = writeThemeCookie( clientHintsRequest, - ssrClientHintsConfiguration, + configuration, ) nuxtApp.hook('vuetify:before-create', async ({ vuetifyOptions }) => { @@ -88,7 +106,7 @@ const plugin: Plugin<{ await nuxtApp.hooks.callHook('vuetify:ssr-client-hints', { vuetifyOptions, ssrClientHintsConfiguration: { - ...ssrClientHintsConfiguration, + ...configuration, enabled: true, }, ssrClientHints: state.value, @@ -359,18 +377,24 @@ function writeThemeCookie( const cookieName = ssrClientHintsConfiguration.prefersColorSchemeOptions.cookieName const themeName = clientHintsRequest.colorSchemeFromCookie ?? ssrClientHintsConfiguration.prefersColorSchemeOptions.defaultTheme const path = ssrClientHintsConfiguration.prefersColorSchemeOptions.baseUrl + const domain = ssrClientHintsConfiguration.prefersColorSchemeOptions.cookieDomain const date = new Date() const expires = new Date(date.setDate(date.getDate() + 365)) if (!clientHintsRequest.firstRequest || !ssrClientHintsConfiguration.reloadOnFirstRequest) { useCookie(cookieName, { path, + domain, expires, sameSite: 'lax', }).value = themeName } - return `${cookieName}=${themeName}; Path=${path}; Expires=${expires.toUTCString()}; SameSite=Lax` + let cookie = `${cookieName}=${themeName}; Path=${path}; Expires=${expires.toUTCString()}; SameSite=Lax` + if (domain) + cookie += `; Domain=${domain}` + + return cookie } export default plugin diff --git a/src/types.ts b/src/types.ts index cc0b0b6..8f77986 100644 --- a/src/types.ts +++ b/src/types.ts @@ -355,6 +355,12 @@ export interface MOptions { * @default 'light' */ lightThemeName?: string + /** + * The domain for the cookie. + * + * @default undefined + */ + cookieDomain?: string /** * Use the browser theme only? * diff --git a/src/utils/ssr-client-hints.ts b/src/utils/ssr-client-hints.ts index 57a6a64..0e37c3f 100644 --- a/src/utils/ssr-client-hints.ts +++ b/src/utils/ssr-client-hints.ts @@ -13,6 +13,7 @@ export interface ResolvedClientHints { cookieName: string darkThemeName: string lightThemeName: string + cookieDomain?: string useBrowserThemeOnly: boolean } } @@ -75,6 +76,7 @@ export function prepareSSRClientHints(baseUrl: string, ctx: VuetifyNuxtContext) cookieName: ssrClientHintsConfiguration.prefersColorSchemeOptions?.cookieName ?? 'color-scheme', darkThemeName, lightThemeName, + cookieDomain: ssrClientHintsConfiguration.prefersColorSchemeOptions?.cookieDomain, useBrowserThemeOnly: ssrClientHintsConfiguration.prefersColorSchemeOptions?.useBrowserThemeOnly ?? false, } }