@dargmuesli/nuxt-vio 11.2.6 → 12.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.config.ts +37 -54
- package/composables/dateTime.ts +11 -0
- package/composables/useAppLayout.ts +2 -2
- package/nuxt.config.ts +117 -190
- package/package.json +3 -3
- package/server/middleware/headers.ts +5 -23
- package/server/middleware/timezone.ts +9 -0
- package/server/plugins/security.ts +55 -0
- package/server/utils/timezone.ts +32 -0
- package/utils/autoImport.ts +10 -0
- package/utils/constants.ts +110 -0
- package/utils/networking.ts +1 -22
- package/composables/useDateTime.ts +0 -15
- package/server/utils/util.ts +0 -2
    
        package/app.config.ts
    CHANGED
    
    | @@ -1,80 +1,63 @@ | |
| 1 1 | 
             
            export default defineAppConfig({
         | 
| 2 2 | 
             
              vio: {
         | 
| 3 3 | 
             
                pages: undefined,
         | 
| 4 | 
            -
                server: {
         | 
| 5 | 
            -
                  middleware: {
         | 
| 6 | 
            -
                    headers: {
         | 
| 7 | 
            -
                      csp: {
         | 
| 8 | 
            -
                        default: {
         | 
| 9 | 
            -
                          NEL: '\'{"report_to":"default","max_age":31536000,"include_subdomains":true}\'',
         | 
| 10 | 
            -
                          'Report-To':
         | 
| 11 | 
            -
                            '\'{"group":"default":"max_age":31536000:"endpoints":[{"url":"https://dargmuesli.report-uri.com/a/d/g"}]:"include_subdomains":true}\'',
         | 
| 12 | 
            -
                        },
         | 
| 13 | 
            -
                        production: {} as Record<string, string>,
         | 
| 14 | 
            -
                      },
         | 
| 15 | 
            -
                    },
         | 
| 16 | 
            -
                  },
         | 
| 17 | 
            -
                },
         | 
| 18 4 | 
             
                themeColor: undefined,
         | 
| 19 5 | 
             
              },
         | 
| 20 6 | 
             
            })
         | 
| 21 7 |  | 
| 22 | 
            -
             | 
| 23 | 
            -
               | 
| 24 | 
            -
                 | 
| 25 | 
            -
                   | 
| 26 | 
            -
                     | 
| 27 | 
            -
                       | 
| 28 | 
            -
             | 
| 8 | 
            +
            export type AppConfig = {
         | 
| 9 | 
            +
              vio: {
         | 
| 10 | 
            +
                pages?: {
         | 
| 11 | 
            +
                  legalNotice?: {
         | 
| 12 | 
            +
                    contact: {
         | 
| 13 | 
            +
                      email: string
         | 
| 14 | 
            +
                    }
         | 
| 15 | 
            +
                    responsibility: {
         | 
| 16 | 
            +
                      address: {
         | 
| 17 | 
            +
                        city: string
         | 
| 18 | 
            +
                        name: string
         | 
| 19 | 
            +
                        street: string
         | 
| 20 | 
            +
                      }
         | 
| 21 | 
            +
                    }
         | 
| 22 | 
            +
                    tmg: {
         | 
| 23 | 
            +
                      address: {
         | 
| 24 | 
            +
                        city: string
         | 
| 25 | 
            +
                        name: string
         | 
| 26 | 
            +
                        street: string
         | 
| 29 27 | 
             
                      }
         | 
| 30 | 
            -
             | 
| 28 | 
            +
                    }
         | 
| 29 | 
            +
                  }
         | 
| 30 | 
            +
                  privacyPolicy?: {
         | 
| 31 | 
            +
                    hostingCdn?: {
         | 
| 32 | 
            +
                      external: {
         | 
| 31 33 | 
             
                        address: {
         | 
| 32 34 | 
             
                          city: string
         | 
| 33 35 | 
             
                          name: string
         | 
| 34 36 | 
             
                          street: string
         | 
| 35 37 | 
             
                        }
         | 
| 36 38 | 
             
                      }
         | 
| 37 | 
            -
             | 
| 39 | 
            +
                    }
         | 
| 40 | 
            +
                    mandatoryInfo?: {
         | 
| 41 | 
            +
                      responsible: {
         | 
| 38 42 | 
             
                        address: {
         | 
| 39 43 | 
             
                          city: string
         | 
| 44 | 
            +
                          email: string
         | 
| 40 45 | 
             
                          name: string
         | 
| 41 46 | 
             
                          street: string
         | 
| 42 47 | 
             
                        }
         | 
| 43 48 | 
             
                      }
         | 
| 44 49 | 
             
                    }
         | 
| 45 | 
            -
                    privacyPolicy?: {
         | 
| 46 | 
            -
                      hostingCdn?: {
         | 
| 47 | 
            -
                        external: {
         | 
| 48 | 
            -
                          address: {
         | 
| 49 | 
            -
                            city: string
         | 
| 50 | 
            -
                            name: string
         | 
| 51 | 
            -
                            street: string
         | 
| 52 | 
            -
                          }
         | 
| 53 | 
            -
                        }
         | 
| 54 | 
            -
                      }
         | 
| 55 | 
            -
                      mandatoryInfo?: {
         | 
| 56 | 
            -
                        responsible: {
         | 
| 57 | 
            -
                          address: {
         | 
| 58 | 
            -
                            city: string
         | 
| 59 | 
            -
                            email: string
         | 
| 60 | 
            -
                            name: string
         | 
| 61 | 
            -
                            street: string
         | 
| 62 | 
            -
                          }
         | 
| 63 | 
            -
                        }
         | 
| 64 | 
            -
                      }
         | 
| 65 | 
            -
                    }
         | 
| 66 50 | 
             
                  }
         | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
                          default: Record<string, string>
         | 
| 72 | 
            -
                          production: Record<string, string>
         | 
| 73 | 
            -
                        }
         | 
| 74 | 
            -
                      }
         | 
| 75 | 
            -
                    }
         | 
| 51 | 
            +
                }
         | 
| 52 | 
            +
                server?: {
         | 
| 53 | 
            +
                  middleware: {
         | 
| 54 | 
            +
                    headers: Record<string, string>
         | 
| 76 55 | 
             
                  }
         | 
| 77 | 
            -
                  themeColor?: string
         | 
| 78 56 | 
             
                }
         | 
| 57 | 
            +
                themeColor?: string
         | 
| 79 58 | 
             
              }
         | 
| 80 59 | 
             
            }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            declare module 'nuxt/schema' {
         | 
| 62 | 
            +
              interface AppConfigInput extends AppConfig {}
         | 
| 63 | 
            +
            }
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            import type { Dayjs } from 'dayjs'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            export const useDateTime = () => {
         | 
| 4 | 
            +
              const { $dayjs, ssrContext } = useNuxtApp()
         | 
| 5 | 
            +
              const timezone = ssrContext
         | 
| 6 | 
            +
                ? ssrContext.event.context.$timezone
         | 
| 7 | 
            +
                : getTimezone()
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              return (dateTime?: string | number | Dayjs | Date | null) =>
         | 
| 10 | 
            +
                $dayjs(dateTime).tz(timezone)
         | 
| 11 | 
            +
            }
         | 
| @@ -10,8 +10,8 @@ export const useAppLayout = () => { | |
| 10 10 | 
             
                },
         | 
| 11 11 | 
             
              })
         | 
| 12 12 |  | 
| 13 | 
            -
              // TODO: convert to `useServerHeadSafe` (https://github.com/ | 
| 14 | 
            -
               | 
| 13 | 
            +
              // TODO: convert to `useServerHeadSafe` (https://github.com/unjs/unhead/issues/221)
         | 
| 14 | 
            +
              useServerSeoMeta({
         | 
| 15 15 | 
             
                titleTemplate: (title) =>
         | 
| 16 16 | 
             
                  TITLE_TEMPLATE({
         | 
| 17 17 | 
             
                    siteName: siteConfig.name,
         | 
    
        package/nuxt.config.ts
    CHANGED
    
    | @@ -9,6 +9,7 @@ import { | |
| 9 9 | 
             
              TIMEZONE_COOKIE_NAME,
         | 
| 10 10 | 
             
              GTAG_COOKIE_ID,
         | 
| 11 11 | 
             
              VIO_NUXT_BASE_CONFIG,
         | 
| 12 | 
            +
              GET_CSP,
         | 
| 12 13 | 
             
            } from './utils/constants'
         | 
| 13 14 |  | 
| 14 15 | 
             
            const currentDir = dirname(fileURLToPath(import.meta.url))
         | 
| @@ -29,9 +30,6 @@ export default defineNuxtConfig( | |
| 29 30 | 
             
                    },
         | 
| 30 31 | 
             
                  },
         | 
| 31 32 | 
             
                  devtools: {
         | 
| 32 | 
            -
                    enabled:
         | 
| 33 | 
            -
                      process.env.NODE_ENV === 'development' &&
         | 
| 34 | 
            -
                      !process.env.NUXT_PUBLIC_VIO_IS_TESTING,
         | 
| 35 33 | 
             
                    timeline: {
         | 
| 36 34 | 
             
                      enabled: true,
         | 
| 37 35 | 
             
                    },
         | 
| @@ -48,31 +46,42 @@ export default defineNuxtConfig( | |
| 48 46 | 
             
                    '@nuxtjs/tailwindcss',
         | 
| 49 47 | 
             
                    '@pinia/nuxt',
         | 
| 50 48 | 
             
                    'nuxt-gtag',
         | 
| 49 | 
            +
                    (_options, nuxt) => {
         | 
| 50 | 
            +
                      if (nuxt.options._generate) {
         | 
| 51 | 
            +
                        nuxt.options.features.inlineStyles = false
         | 
| 52 | 
            +
                      }
         | 
| 53 | 
            +
                    },
         | 
| 51 54 | 
             
                    // nuxt-security: remove invalid `'none'`s and duplicates
         | 
| 52 55 | 
             
                    (_options, nuxt) => {
         | 
| 53 | 
            -
                      const  | 
| 56 | 
            +
                      const nuxtConfigSecurityHeaders = nuxt.options.security.headers
         | 
| 54 57 |  | 
| 55 58 | 
             
                      if (
         | 
| 56 | 
            -
                        typeof  | 
| 57 | 
            -
                         | 
| 58 | 
            -
                        typeof nuxtConfigSecurity.headers.contentSecurityPolicy !==
         | 
| 59 | 
            -
                          'boolean' &&
         | 
| 60 | 
            -
                        typeof nuxtConfigSecurity.headers.contentSecurityPolicy !== 'string'
         | 
| 59 | 
            +
                        typeof nuxtConfigSecurityHeaders !== 'boolean' &&
         | 
| 60 | 
            +
                        nuxtConfigSecurityHeaders.contentSecurityPolicy
         | 
| 61 61 | 
             
                      ) {
         | 
| 62 | 
            -
                         | 
| 63 | 
            -
                           | 
| 64 | 
            -
             | 
| 62 | 
            +
                        if (nuxt.options._generate) {
         | 
| 63 | 
            +
                          nuxtConfigSecurityHeaders.contentSecurityPolicy = defu(
         | 
| 64 | 
            +
                            {
         | 
| 65 | 
            +
                              'script-src-elem': [
         | 
| 66 | 
            +
                                "'unsafe-inline'", // nuxt-color-mode (https://github.com/nuxt-modules/color-mode/issues/266), runtimeConfig (static)
         | 
| 67 | 
            +
                              ],
         | 
| 68 | 
            +
                            },
         | 
| 69 | 
            +
                            GET_CSP(SITE_URL),
         | 
| 70 | 
            +
                            nuxtConfigSecurityHeaders.contentSecurityPolicy,
         | 
| 71 | 
            +
                          )
         | 
| 72 | 
            +
                        }
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                        const csp = nuxtConfigSecurityHeaders.contentSecurityPolicy
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                        for (const [key, value] of Object.entries(csp)) {
         | 
| 65 77 | 
             
                          if (!Array.isArray(value)) continue
         | 
| 66 78 |  | 
| 67 79 | 
             
                          const valueFiltered = value.filter((x) => x !== "'none'")
         | 
| 68 80 |  | 
| 69 81 | 
             
                          if (valueFiltered.length) {
         | 
| 70 | 
            -
                            ;(
         | 
| 71 | 
            -
                               | 
| 72 | 
            -
             | 
| 73 | 
            -
                                unknown
         | 
| 74 | 
            -
                              >
         | 
| 75 | 
            -
                            )[key] = [...new Set(valueFiltered)]
         | 
| 82 | 
            +
                            ;(csp as Record<string, unknown>)[key] = [
         | 
| 83 | 
            +
                              ...new Set(valueFiltered),
         | 
| 84 | 
            +
                            ]
         | 
| 76 85 | 
             
                          }
         | 
| 77 86 | 
             
                        }
         | 
| 78 87 | 
             
                      }
         | 
| @@ -81,32 +90,24 @@ export default defineNuxtConfig( | |
| 81 90 | 
             
                  ],
         | 
| 82 91 | 
             
                  nitro: {
         | 
| 83 92 | 
             
                    compressPublicAssets: true,
         | 
| 84 | 
            -
                    experimental: {
         | 
| 85 | 
            -
                      openAPI: process.env.NODE_ENV === 'development',
         | 
| 86 | 
            -
                    },
         | 
| 87 93 | 
             
                  },
         | 
| 88 94 | 
             
                  runtimeConfig: {
         | 
| 89 95 | 
             
                    public: {
         | 
| 90 | 
            -
                       | 
| 91 | 
            -
                         | 
| 92 | 
            -
                          ? {}
         | 
| 93 | 
            -
                          : { baseUrl: SITE_URL }),
         | 
| 96 | 
            +
                      site: {
         | 
| 97 | 
            +
                        url: SITE_URL,
         | 
| 94 98 | 
             
                      },
         | 
| 95 99 | 
             
                      vio: {
         | 
| 96 | 
            -
                        isInProduction: process.env.NODE_ENV === 'production',
         | 
| 97 100 | 
             
                        isTesting: false,
         | 
| 98 101 | 
             
                      },
         | 
| 99 102 | 
             
                    },
         | 
| 100 103 | 
             
                  },
         | 
| 101 | 
            -
                  typescript: {
         | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
                    // },
         | 
| 109 | 
            -
                  },
         | 
| 104 | 
            +
                  // typescript: {
         | 
| 105 | 
            +
                  //   tsConfig: {
         | 
| 106 | 
            +
                  //     compilerOptions: {
         | 
| 107 | 
            +
                  //       noErrorTruncation: true,
         | 
| 108 | 
            +
                  //     },
         | 
| 109 | 
            +
                  //   },
         | 
| 110 | 
            +
                  // },
         | 
| 110 111 |  | 
| 111 112 | 
             
                  // modules
         | 
| 112 113 | 
             
                  colorMode: {
         | 
| @@ -160,9 +161,7 @@ export default defineNuxtConfig( | |
| 160 161 | 
             
                  },
         | 
| 161 162 | 
             
                  gtag: {
         | 
| 162 163 | 
             
                    config: {
         | 
| 163 | 
            -
                      cookie_flags:  | 
| 164 | 
            -
                        process.env.NODE_ENV === 'production' ? ';secure' : ''
         | 
| 165 | 
            -
                      }`,
         | 
| 164 | 
            +
                      cookie_flags: 'samesite=strict',
         | 
| 166 165 | 
             
                    },
         | 
| 167 166 | 
             
                    enabled: false,
         | 
| 168 167 | 
             
                    initCommands: [
         | 
| @@ -195,164 +194,43 @@ export default defineNuxtConfig( | |
| 195 194 | 
             
                  },
         | 
| 196 195 | 
             
                  security: {
         | 
| 197 196 | 
             
                    headers: {
         | 
| 198 | 
            -
                      contentSecurityPolicy:  | 
| 199 | 
            -
                         | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
                         | 
| 208 | 
            -
                         | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 216 | 
            -
             | 
| 217 | 
            -
             | 
| 218 | 
            -
             | 
| 219 | 
            -
             | 
| 220 | 
            -
                         | 
| 221 | 
            -
                         | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
             | 
| 225 | 
            -
                          'script-src': [
         | 
| 226 | 
            -
                            'https://polyfill.io/v3/polyfill.min.js', // ESLint plugin compat
         | 
| 227 | 
            -
                          ], // TODO: replace with `script-src-elem` once Webkit supports it (https://caniuse.com/mdn-http_headers_content-security-policy_script-src-elem)
         | 
| 228 | 
            -
                        },
         | 
| 229 | 
            -
                        {
         | 
| 230 | 
            -
                          // @nuxt/devtools
         | 
| 231 | 
            -
                          ...(process.env.NODE_ENV === 'development'
         | 
| 232 | 
            -
                            ? {
         | 
| 233 | 
            -
                                'frame-src': [
         | 
| 234 | 
            -
                                  'http://localhost:3000/__nuxt_devtools__/client/',
         | 
| 235 | 
            -
                                ],
         | 
| 236 | 
            -
                              }
         | 
| 237 | 
            -
                            : {}),
         | 
| 238 | 
            -
                        },
         | 
| 239 | 
            -
                        {
         | 
| 240 | 
            -
                          // nuxt-i18n
         | 
| 241 | 
            -
                          ...(process.env.NODE_ENV === 'development'
         | 
| 242 | 
            -
                            ? {}
         | 
| 243 | 
            -
                            : {
         | 
| 244 | 
            -
                                'script-src': ["'self'"], // 'http://localhost:3000/_nuxt/i18n.config.*.js' // TOD: add with subresource integrity?
         | 
| 245 | 
            -
                              }),
         | 
| 246 | 
            -
                        },
         | 
| 247 | 
            -
                        {
         | 
| 248 | 
            -
                          // nuxt-link-checker
         | 
| 249 | 
            -
                          ...(process.env.NODE_ENV === 'development'
         | 
| 250 | 
            -
                            ? {
         | 
| 251 | 
            -
                                'connect-src': ["'self'"], // 'http://localhost:3000/api/__link_checker__/inspect'
         | 
| 252 | 
            -
                              }
         | 
| 253 | 
            -
                            : {}),
         | 
| 254 | 
            -
                        },
         | 
| 255 | 
            -
                        {
         | 
| 256 | 
            -
                          // nuxt-og-image
         | 
| 257 | 
            -
                          ...(process.env.NODE_ENV === 'development'
         | 
| 258 | 
            -
                            ? {
         | 
| 259 | 
            -
                                'font-src': ['https://fonts.gstatic.com/s/inter/'],
         | 
| 260 | 
            -
                                'frame-ancestors': ["'self'"],
         | 
| 261 | 
            -
                                'frame-src': ["'self'"],
         | 
| 262 | 
            -
                                'script-src': ['https://cdn.tailwindcss.com/'], // TODO: replace with `script-src-elem` once Webkit supports it (https://caniuse.com/mdn-http_headers_content-security-policy_script-src-elem)
         | 
| 263 | 
            -
                                'style-src': [
         | 
| 264 | 
            -
                                  // TODO: replace with `style-src-elem` once Webkit supports it
         | 
| 265 | 
            -
                                  'https://cdn.jsdelivr.net/npm/gardevoir https://fonts.googleapis.com/css2',
         | 
| 266 | 
            -
                                ],
         | 
| 267 | 
            -
                              }
         | 
| 268 | 
            -
                            : {}),
         | 
| 269 | 
            -
                        },
         | 
| 270 | 
            -
                        {
         | 
| 271 | 
            -
                          // nuxt-simple-sitemap
         | 
| 272 | 
            -
                          'script-src': [`${SITE_URL}/__sitemap__/style.xsl`], // TODO: replace with `script-src-elem` once Webkit supports it (https://caniuse.com/mdn-http_headers_content-security-policy_script-src-elem)
         | 
| 273 | 
            -
                        },
         | 
| 274 | 
            -
                        {
         | 
| 275 | 
            -
                          // nuxt
         | 
| 276 | 
            -
                          'connect-src': [
         | 
| 277 | 
            -
                            ...(process.env.NODE_ENV === 'development'
         | 
| 278 | 
            -
                              ? [
         | 
| 279 | 
            -
                                  'http://localhost:3000/_nuxt/', // hot reload
         | 
| 280 | 
            -
                                  'https://localhost:3000/_nuxt/', // hot reload
         | 
| 281 | 
            -
                                  'ws://localhost:3000/_nuxt/', // hot reload
         | 
| 282 | 
            -
                                  'wss://localhost:3000/_nuxt/', // hot reload
         | 
| 283 | 
            -
                                ]
         | 
| 284 | 
            -
                              : ["'self'"]), // build metadata and payloads
         | 
| 285 | 
            -
                          ],
         | 
| 286 | 
            -
                          'img-src': [
         | 
| 287 | 
            -
                            "'self'", // TODO: replace with `"'nonce-{{nonce}}'",`
         | 
| 288 | 
            -
                            'data:', // external link icon
         | 
| 289 | 
            -
                          ],
         | 
| 290 | 
            -
                          'script-src': ["'nonce-{{nonce}}'"], // TODO: replace with `script-src-elem` once Webkit supports it (https://caniuse.com/mdn-http_headers_content-security-policy_script-src-elem)
         | 
| 291 | 
            -
                          'style-src': [
         | 
| 292 | 
            -
                            // TODO: replace with `style-src-elem` once Webkit supports it
         | 
| 293 | 
            -
                            "'self'", // TODO: replace with `"'nonce-{{nonce}}'",` (https://github.com/vitejs/vite/pull/11864)
         | 
| 294 | 
            -
                            "'unsafe-inline'", // TODO: replace with `"'nonce-{{nonce}}'",` (https://github.com/vitejs/vite/pull/11864)
         | 
| 295 | 
            -
                          ],
         | 
| 296 | 
            -
                        },
         | 
| 297 | 
            -
                        {
         | 
| 298 | 
            -
                          // nitro
         | 
| 299 | 
            -
                          'connect-src': ["'self'"] /* swagger
         | 
| 300 | 
            -
                          'http://localhost:3000/_nitro/openapi.json',
         | 
| 301 | 
            -
                          'http://localhost:3000/_nitro/swagger', */,
         | 
| 302 | 
            -
                          'script-src': [
         | 
| 303 | 
            -
                            'https://cdn.jsdelivr.net/npm/', // swagger // TODO: increase precision (https://github.com/unjs/nitro/issues/1757)
         | 
| 304 | 
            -
                          ], // TODO: replace with `script-src-elem` once Webkit supports it (https://caniuse.com/mdn-http_headers_content-security-policy_script-src-elem)
         | 
| 305 | 
            -
                          'style-src': [
         | 
| 306 | 
            -
                            'https://cdn.jsdelivr.net/npm/', // swagger // TODO: increase precision (https://github.com/unjs/nitro/issues/1757)
         | 
| 307 | 
            -
                          ],
         | 
| 308 | 
            -
                        },
         | 
| 309 | 
            -
                        {
         | 
| 310 | 
            -
                          // base
         | 
| 311 | 
            -
                          'base-uri': ["'none'"], // does not fallback to `default-src`
         | 
| 312 | 
            -
                          'child-src': false as const,
         | 
| 313 | 
            -
                          'connect-src': false as const,
         | 
| 314 | 
            -
                          'default-src': ["'none'"],
         | 
| 315 | 
            -
                          'font-src': false as const,
         | 
| 316 | 
            -
                          'form-action': ["'none'"], // does not fallback to `default-src`
         | 
| 317 | 
            -
                          'frame-ancestors': ["'none'"], // does not fallback to `default-src`
         | 
| 318 | 
            -
                          'frame-src': false as const,
         | 
| 319 | 
            -
                          'img-src': false as const,
         | 
| 320 | 
            -
                          'media-src': false as const,
         | 
| 321 | 
            -
                          'navigate-to': false as const,
         | 
| 322 | 
            -
                          'object-src': false as const,
         | 
| 323 | 
            -
                          'prefetch-src': false as const,
         | 
| 324 | 
            -
                          'report-to': undefined,
         | 
| 325 | 
            -
                          'report-uri': false as const,
         | 
| 326 | 
            -
                          // TODO: evaluate header (https://github.com/maevsi/maevsi/issues/830) // https://stackoverflow.com/questions/62081028/this-document-requires-trustedscripturl-assignment
         | 
| 327 | 
            -
                          // 'require-trusted-types-for': ["'script'"], // csp-evaluator
         | 
| 328 | 
            -
                          sandbox: false as const,
         | 
| 329 | 
            -
                          'script-src': false as const,
         | 
| 330 | 
            -
                          'script-src-attr': false as const, // TODO: enable once Webkit supports it (https://caniuse.com/mdn-http_headers_content-security-policy_script-src-attr)
         | 
| 331 | 
            -
                          'script-src-elem': false as const, // TODO: enable once Webkit supports it (https://caniuse.com/mdn-http_headers_content-security-policy_script-src-elem)
         | 
| 332 | 
            -
                          'style-src': false as const,
         | 
| 333 | 
            -
                          'style-src-attr': false as const, // TODO: enable once Webkit supports it (https://caniuse.com/mdn-http_headers_content-security-policy_style-src-attr)
         | 
| 334 | 
            -
                          'style-src-elem': false as const, // TODO: enable once Webkit supports it (https://caniuse.com/mdn-http_headers_content-security-policy_style-src-elem)
         | 
| 335 | 
            -
                          'upgrade-insecure-requests': false, // TODO: set to `process.env.NODE_ENV === 'production'` or `true` when tests run on https
         | 
| 336 | 
            -
                          'worker-src': false as const,
         | 
| 337 | 
            -
                        },
         | 
| 338 | 
            -
                      ),
         | 
| 339 | 
            -
                      crossOriginEmbedderPolicy: false, // https://stackoverflow.com/questions/71904052/getting-notsameoriginafterdefaultedtosameoriginbycoep-error-with-helmet
         | 
| 340 | 
            -
                      strictTransportSecurity:
         | 
| 341 | 
            -
                        process.env.NODE_ENV === 'production'
         | 
| 342 | 
            -
                          ? {
         | 
| 343 | 
            -
                              maxAge: 31536000,
         | 
| 344 | 
            -
                              includeSubdomains: true,
         | 
| 345 | 
            -
                              preload: true,
         | 
| 346 | 
            -
                            }
         | 
| 347 | 
            -
                          : false,
         | 
| 197 | 
            +
                      contentSecurityPolicy: {
         | 
| 198 | 
            +
                        'base-uri': ["'none'"], // does not fallback to `default-src`
         | 
| 199 | 
            +
                        'child-src': false as const,
         | 
| 200 | 
            +
                        'connect-src': false as const,
         | 
| 201 | 
            +
                        'default-src': ["'none'"],
         | 
| 202 | 
            +
                        'font-src': false as const,
         | 
| 203 | 
            +
                        'form-action': ["'none'"], // does not fallback to `default-src`
         | 
| 204 | 
            +
                        'frame-ancestors': ["'none'"], // does not fallback to `default-src`
         | 
| 205 | 
            +
                        'frame-src': false as const,
         | 
| 206 | 
            +
                        'img-src': false as const,
         | 
| 207 | 
            +
                        'media-src': false as const,
         | 
| 208 | 
            +
                        'navigate-to': false as const,
         | 
| 209 | 
            +
                        'object-src': false as const,
         | 
| 210 | 
            +
                        'prefetch-src': false as const,
         | 
| 211 | 
            +
                        'report-to': undefined,
         | 
| 212 | 
            +
                        'report-uri': false as const,
         | 
| 213 | 
            +
                        // 'require-trusted-types-for': ["'script'"], // csp-evaluator // TODO: wait for trusted type support in vue (https://github.com/vuejs/core/pull/10844)
         | 
| 214 | 
            +
                        sandbox: false as const,
         | 
| 215 | 
            +
                        'script-src': false as const,
         | 
| 216 | 
            +
                        'script-src-attr': false as const,
         | 
| 217 | 
            +
                        'script-src-elem': false as const,
         | 
| 218 | 
            +
                        'style-src': false as const,
         | 
| 219 | 
            +
                        'style-src-attr': false as const,
         | 
| 220 | 
            +
                        'style-src-elem': false as const,
         | 
| 221 | 
            +
                        'upgrade-insecure-requests': false, // TODO: set to `process.env.NODE_ENV === 'production'` or `true` when tests run on https
         | 
| 222 | 
            +
                        'worker-src': false as const,
         | 
| 223 | 
            +
                      },
         | 
| 348 224 | 
             
                      xXSSProtection: '1; mode=block', // TODO: set back to `0` once CSP does not use `unsafe-*` anymore (https://github.com/maevsi/maevsi/issues/1047)
         | 
| 349 225 | 
             
                    },
         | 
| 226 | 
            +
                    ssg: {
         | 
| 227 | 
            +
                      hashStyles: true,
         | 
| 228 | 
            +
                    },
         | 
| 350 229 | 
             
                  },
         | 
| 351 230 | 
             
                  seo: {
         | 
| 352 231 | 
             
                    splash: false,
         | 
| 353 232 | 
             
                  },
         | 
| 354 233 | 
             
                  site: {
         | 
| 355 | 
            -
                    debug: process.env.NODE_ENV === 'development',
         | 
| 356 234 | 
             
                    id: 'vio',
         | 
| 357 235 | 
             
                    url: SITE_URL,
         | 
| 358 236 | 
             
                  },
         | 
| @@ -365,9 +243,58 @@ export default defineNuxtConfig( | |
| 365 243 |  | 
| 366 244 | 
             
                  // environments
         | 
| 367 245 | 
             
                  $development: {
         | 
| 246 | 
            +
                    devtools: {
         | 
| 247 | 
            +
                      enabled: !process.env.NUXT_PUBLIC_VIO_IS_TESTING,
         | 
| 248 | 
            +
                    },
         | 
| 249 | 
            +
                    nitro: {
         | 
| 250 | 
            +
                      experimental: {
         | 
| 251 | 
            +
                        openAPI: true,
         | 
| 252 | 
            +
                      },
         | 
| 253 | 
            +
                    },
         | 
| 254 | 
            +
                    runtimeConfig: {
         | 
| 255 | 
            +
                      public: {
         | 
| 256 | 
            +
                        vio: {
         | 
| 257 | 
            +
                          isInProduction: false,
         | 
| 258 | 
            +
                        },
         | 
| 259 | 
            +
                      },
         | 
| 260 | 
            +
                    },
         | 
| 261 | 
            +
             | 
| 368 262 | 
             
                    // modules
         | 
| 369 263 | 
             
                    security: {
         | 
| 370 | 
            -
                       | 
| 264 | 
            +
                      headers: {
         | 
| 265 | 
            +
                        crossOriginEmbedderPolicy: 'unsafe-none',
         | 
| 266 | 
            +
                        strictTransportSecurity: false, // prevent endless reload in Chrome
         | 
| 267 | 
            +
                      },
         | 
| 268 | 
            +
                    },
         | 
| 269 | 
            +
                    site: {
         | 
| 270 | 
            +
                      debug: true,
         | 
| 271 | 
            +
                    },
         | 
| 272 | 
            +
                  },
         | 
| 273 | 
            +
                  $production: {
         | 
| 274 | 
            +
                    runtimeConfig: {
         | 
| 275 | 
            +
                      public: {
         | 
| 276 | 
            +
                        i18n: {
         | 
| 277 | 
            +
                          baseUrl: SITE_URL,
         | 
| 278 | 
            +
                        },
         | 
| 279 | 
            +
                        vio: {
         | 
| 280 | 
            +
                          isInProduction: true,
         | 
| 281 | 
            +
                        },
         | 
| 282 | 
            +
                      },
         | 
| 283 | 
            +
                    },
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                    // modules
         | 
| 286 | 
            +
                    gtag: {
         | 
| 287 | 
            +
                      config: {
         | 
| 288 | 
            +
                        cookie_flags: 'samesite=strict;secure',
         | 
| 289 | 
            +
                      },
         | 
| 290 | 
            +
                    },
         | 
| 291 | 
            +
                    security: {
         | 
| 292 | 
            +
                      headers: {
         | 
| 293 | 
            +
                        strictTransportSecurity: {
         | 
| 294 | 
            +
                          maxAge: 31536000,
         | 
| 295 | 
            +
                          preload: true,
         | 
| 296 | 
            +
                        },
         | 
| 297 | 
            +
                      },
         | 
| 371 298 | 
             
                    },
         | 
| 372 299 | 
             
                  },
         | 
| 373 300 | 
             
                },
         | 
    
        package/package.json
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "@dargmuesli/nuxt-vio",
         | 
| 3 | 
            -
              "version": " | 
| 3 | 
            +
              "version": "12.0.1",
         | 
| 4 4 | 
             
              "repository": {
         | 
| 5 5 | 
             
                "type": "git",
         | 
| 6 6 | 
             
                "url": "git+https://github.com/dargmuesli/vio.git"
         | 
| @@ -87,6 +87,7 @@ | |
| 87 87 | 
             
                "prettier": "3.2.5",
         | 
| 88 88 | 
             
                "prettier-plugin-tailwindcss": "0.5.14",
         | 
| 89 89 | 
             
                "serve": "14.2.3",
         | 
| 90 | 
            +
                "sharp": "0.33.4",
         | 
| 90 91 | 
             
                "stylelint": "16.5.0",
         | 
| 91 92 | 
             
                "stylelint-config-recommended-vue": "1.5.0",
         | 
| 92 93 | 
             
                "stylelint-config-standard": "36.0.0",
         | 
| @@ -95,8 +96,7 @@ | |
| 95 96 | 
             
                "ufo": "1.5.3",
         | 
| 96 97 | 
             
                "unhead": "1.9.10",
         | 
| 97 98 | 
             
                "vue": "3.4.27",
         | 
| 98 | 
            -
                "vue-router": "4.3.2" | 
| 99 | 
            -
                "vue-tsc": "2.0.17"
         | 
| 99 | 
            +
                "vue-router": "4.3.2"
         | 
| 100 100 | 
             
              },
         | 
| 101 101 | 
             
              "peerDependencies": {
         | 
| 102 102 | 
             
                "nuxt": "3.11.2",
         | 
| @@ -1,29 +1,11 @@ | |
| 1 | 
            -
            import type {  | 
| 2 | 
            -
            import type { AppConfig } from 'nuxt/schema'
         | 
| 1 | 
            +
            import type { AppConfig } from '../../app.config'
         | 
| 3 2 |  | 
| 4 3 | 
             
            export default defineEventHandler(async (event) => {
         | 
| 5 | 
            -
              setRequestHeader(event, TIMEZONE_HEADER_KEY, await getTimezone(event))
         | 
| 6 | 
            -
              setResponseHeaders(event)
         | 
| 7 | 
            -
            })
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            const setRequestHeader = (event: H3Event, name: string, value?: string) => {
         | 
| 10 | 
            -
              event.node.req.headers[name] = value
         | 
| 11 | 
            -
            }
         | 
| 12 | 
            -
             | 
| 13 | 
            -
            const setResponseHeaders = (event: H3Event) => {
         | 
| 14 4 | 
             
              const config = useAppConfig() as AppConfig
         | 
| 15 5 |  | 
| 16 | 
            -
               | 
| 17 | 
            -
                config.vio.server.middleware.headers.csp.default,
         | 
| 18 | 
            -
              )) {
         | 
| 19 | 
            -
                appendHeader(event, entry[0], entry[1])
         | 
| 20 | 
            -
              }
         | 
| 6 | 
            +
              if (!config.vio.server) return
         | 
| 21 7 |  | 
| 22 | 
            -
               | 
| 23 | 
            -
                 | 
| 24 | 
            -
                  config.vio.server.middleware.headers.csp.production,
         | 
| 25 | 
            -
                )) {
         | 
| 26 | 
            -
                  appendHeader(event, entry[0], entry[1])
         | 
| 27 | 
            -
                }
         | 
| 8 | 
            +
              for (const entry of Object.entries(config.vio.server.middleware.headers)) {
         | 
| 9 | 
            +
                appendHeader(event, entry[0], entry[1])
         | 
| 28 10 | 
             
              }
         | 
| 29 | 
            -
            }
         | 
| 11 | 
            +
            })
         | 
| @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            import { defu } from 'defu'
         | 
| 2 | 
            +
            import type { NuxtOptions } from 'nuxt/schema'
         | 
| 3 | 
            +
            import { GET_CSP } from '../../utils/constants'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            // remove invalid `'none'`s and duplicates
         | 
| 6 | 
            +
            const cleanupCsp = (
         | 
| 7 | 
            +
              nuxtSecurityConfiguration: Partial<NuxtOptions['security']>,
         | 
| 8 | 
            +
            ) => {
         | 
| 9 | 
            +
              if (
         | 
| 10 | 
            +
                nuxtSecurityConfiguration.headers &&
         | 
| 11 | 
            +
                typeof nuxtSecurityConfiguration.headers !== 'boolean' &&
         | 
| 12 | 
            +
                nuxtSecurityConfiguration.headers.contentSecurityPolicy
         | 
| 13 | 
            +
              ) {
         | 
| 14 | 
            +
                const csp = nuxtSecurityConfiguration.headers.contentSecurityPolicy
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                for (const [key, value] of Object.entries(csp)) {
         | 
| 17 | 
            +
                  if (!Array.isArray(value)) continue
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  const valueFiltered = value.filter((x) => x !== "'none'")
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  if (valueFiltered.length) {
         | 
| 22 | 
            +
                    ;(csp as Record<string, unknown>)[key] = [...new Set(valueFiltered)]
         | 
| 23 | 
            +
                  }
         | 
| 24 | 
            +
                }
         | 
| 25 | 
            +
              }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              return nuxtSecurityConfiguration
         | 
| 28 | 
            +
            }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            export default defineNitroPlugin((nitroApp) => {
         | 
| 31 | 
            +
              // TODO: migrate to nuxt-security (https://github.com/Baroshem/nuxt-security/discussions/454)
         | 
| 32 | 
            +
              if (import.meta.dev) {
         | 
| 33 | 
            +
                nitroApp.hooks.hook('render:html', (html, { event }) => {
         | 
| 34 | 
            +
                  html.head.push(
         | 
| 35 | 
            +
                    `<meta property="csp-nonce" nonce="${event.context.security.nonce}">`,
         | 
| 36 | 
            +
                  )
         | 
| 37 | 
            +
                })
         | 
| 38 | 
            +
              }
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              nitroApp.hooks.hook('nuxt-security:routeRules', async (routeRules) => {
         | 
| 41 | 
            +
                const runtimeConfig = useRuntimeConfig()
         | 
| 42 | 
            +
                const siteUrl = runtimeConfig.public.site.url
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                routeRules['/**'] = cleanupCsp(
         | 
| 45 | 
            +
                  defu(
         | 
| 46 | 
            +
                    {
         | 
| 47 | 
            +
                      headers: {
         | 
| 48 | 
            +
                        contentSecurityPolicy: GET_CSP(siteUrl),
         | 
| 49 | 
            +
                      },
         | 
| 50 | 
            +
                    },
         | 
| 51 | 
            +
                    routeRules['/**'],
         | 
| 52 | 
            +
                  ),
         | 
| 53 | 
            +
                )
         | 
| 54 | 
            +
              })
         | 
| 55 | 
            +
            })
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            import type { H3Event } from 'h3'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import { TIMEZONE_COOKIE_NAME } from '../../utils/constants'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            // TODO: rename to `getTimezone` (https://github.com/nuxt/cli/issues/266)
         | 
| 6 | 
            +
            export const getTimezoneServer = async (event: H3Event) => {
         | 
| 7 | 
            +
              const timezoneBySsr = event.context.$timezone
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              if (timezoneBySsr) return timezoneBySsr
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              const timezoneByCookie = getCookie(event, TIMEZONE_COOKIE_NAME)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              if (timezoneByCookie) return timezoneByCookie
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              const ip = event.node.req.headers['x-real-ip']
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              if (ip && !Array.isArray(ip)) {
         | 
| 18 | 
            +
                const timezoneByIpApi = await getTimezoneByIpApi(ip)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                if (timezoneByIpApi) return timezoneByIpApi
         | 
| 21 | 
            +
              }
         | 
| 22 | 
            +
            }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            export const getTimezoneByIpApi = async (ip: string) => {
         | 
| 25 | 
            +
              const ipApiResult = await $fetch<{ timezone: string }>(
         | 
| 26 | 
            +
                `http://ip-api.com/json/${ip}`,
         | 
| 27 | 
            +
              ).catch(() => {})
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              if (ipApiResult) {
         | 
| 30 | 
            +
                return ipApiResult.timezone
         | 
| 31 | 
            +
              }
         | 
| 32 | 
            +
            }
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            export const getTimezone = () =>
         | 
| 2 | 
            +
              useNuxtApp().ssrContext?.event.context.$timezone ||
         | 
| 3 | 
            +
              useCookie(TIMEZONE_COOKIE_NAME, {
         | 
| 4 | 
            +
                httpOnly: false,
         | 
| 5 | 
            +
                sameSite: 'strict',
         | 
| 6 | 
            +
                secure: !import.meta.dev,
         | 
| 7 | 
            +
              }).value ||
         | 
| 8 | 
            +
              import.meta.client
         | 
| 9 | 
            +
                ? Intl.DateTimeFormat().resolvedOptions().timeZone
         | 
| 10 | 
            +
                : undefined
         | 
    
        package/utils/constants.ts
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            import { helpers } from '@vuelidate/validators'
         | 
| 2 | 
            +
            import { defu } from 'defu'
         | 
| 2 3 |  | 
| 3 4 | 
             
            export const SITE_NAME = 'Vio'
         | 
| 4 5 |  | 
| @@ -13,6 +14,115 @@ export const CACHE_VERSION = 'bOXMwoKlJr' | |
| 13 14 | 
             
            export const COOKIE_PREFIX = SITE_NAME.toLocaleLowerCase()
         | 
| 14 15 | 
             
            export const COOKIE_SEPARATOR = '_'
         | 
| 15 16 | 
             
            export const FETCH_RETRY_AMOUNT = 3
         | 
| 17 | 
            +
            export const GET_CSP = (siteUrl: string) =>
         | 
| 18 | 
            +
              defu(
         | 
| 19 | 
            +
                {
         | 
| 20 | 
            +
                  // Cloudflare
         | 
| 21 | 
            +
                  ...(process.env.NODE_ENV === 'production'
         | 
| 22 | 
            +
                    ? {
         | 
| 23 | 
            +
                        'connect-src': ['https://cloudflareinsights.com'],
         | 
| 24 | 
            +
                        'script-src-elem': ['https://static.cloudflareinsights.com'],
         | 
| 25 | 
            +
                      }
         | 
| 26 | 
            +
                    : {}),
         | 
| 27 | 
            +
                },
         | 
| 28 | 
            +
                {
         | 
| 29 | 
            +
                  // Google Analytics 4 (https://developers.google.com/tag-platform/tag-manager/web/csp)
         | 
| 30 | 
            +
                  'connect-src': [
         | 
| 31 | 
            +
                    'https://*.analytics.google.com',
         | 
| 32 | 
            +
                    'https://*.google-analytics.com',
         | 
| 33 | 
            +
                    'https://*.googletagmanager.com',
         | 
| 34 | 
            +
                  ],
         | 
| 35 | 
            +
                  'img-src': [
         | 
| 36 | 
            +
                    'https://*.google-analytics.com',
         | 
| 37 | 
            +
                    'https://*.googletagmanager.com',
         | 
| 38 | 
            +
                  ],
         | 
| 39 | 
            +
                  'script-src-elem': ['https://*.googletagmanager.com'],
         | 
| 40 | 
            +
                },
         | 
| 41 | 
            +
                {
         | 
| 42 | 
            +
                  // vio
         | 
| 43 | 
            +
                  'manifest-src': [`${siteUrl}/site.webmanifest`],
         | 
| 44 | 
            +
                  // 'script-src-elem': [
         | 
| 45 | 
            +
                  //   'https://polyfill.io/v3/polyfill.min.js', // ESLint plugin compat
         | 
| 46 | 
            +
                  // ],
         | 
| 47 | 
            +
                },
         | 
| 48 | 
            +
                {
         | 
| 49 | 
            +
                  // nuxt-link-checker
         | 
| 50 | 
            +
                  ...(process.env.NODE_ENV === 'development'
         | 
| 51 | 
            +
                    ? {
         | 
| 52 | 
            +
                        'connect-src': [`${siteUrl}/api/__link_checker__/inspect`],
         | 
| 53 | 
            +
                      }
         | 
| 54 | 
            +
                    : {}),
         | 
| 55 | 
            +
                },
         | 
| 56 | 
            +
                {
         | 
| 57 | 
            +
                  // nuxt-og-image
         | 
| 58 | 
            +
                  ...(process.env.NODE_ENV === 'development'
         | 
| 59 | 
            +
                    ? {
         | 
| 60 | 
            +
                        'connect-src': [`${siteUrl}/__og-image__/`],
         | 
| 61 | 
            +
                      }
         | 
| 62 | 
            +
                    : {}),
         | 
| 63 | 
            +
                },
         | 
| 64 | 
            +
                {
         | 
| 65 | 
            +
                  // nuxt-schema-org
         | 
| 66 | 
            +
                  ...(process.env.NODE_ENV === 'development'
         | 
| 67 | 
            +
                    ? {
         | 
| 68 | 
            +
                        'connect-src': [`${siteUrl}/__schema-org__/debug.json`],
         | 
| 69 | 
            +
                      }
         | 
| 70 | 
            +
                    : {}),
         | 
| 71 | 
            +
                },
         | 
| 72 | 
            +
                {
         | 
| 73 | 
            +
                  // nuxt-simple-robots
         | 
| 74 | 
            +
                  ...(process.env.NODE_ENV === 'development'
         | 
| 75 | 
            +
                    ? {
         | 
| 76 | 
            +
                        'connect-src': [
         | 
| 77 | 
            +
                          `${siteUrl}/__robots__/debug.json`,
         | 
| 78 | 
            +
                          `${siteUrl}/__robots__/debug-path.json`,
         | 
| 79 | 
            +
                        ],
         | 
| 80 | 
            +
                      }
         | 
| 81 | 
            +
                    : {}),
         | 
| 82 | 
            +
                },
         | 
| 83 | 
            +
                {
         | 
| 84 | 
            +
                  // nuxt-simple-sitemap
         | 
| 85 | 
            +
                  ...(process.env.NODE_ENV === 'development'
         | 
| 86 | 
            +
                    ? {
         | 
| 87 | 
            +
                        'connect-src': [`${siteUrl}/__sitemap__/debug.json`],
         | 
| 88 | 
            +
                      }
         | 
| 89 | 
            +
                    : {}),
         | 
| 90 | 
            +
                },
         | 
| 91 | 
            +
                {
         | 
| 92 | 
            +
                  // nuxt-site-config
         | 
| 93 | 
            +
                  ...(process.env.NODE_ENV === 'development'
         | 
| 94 | 
            +
                    ? {
         | 
| 95 | 
            +
                        'connect-src': [`${siteUrl}/__site-config__/debug.json`],
         | 
| 96 | 
            +
                      }
         | 
| 97 | 
            +
                    : {}),
         | 
| 98 | 
            +
                },
         | 
| 99 | 
            +
                {
         | 
| 100 | 
            +
                  // nuxt
         | 
| 101 | 
            +
                  'connect-src': [
         | 
| 102 | 
            +
                    "'self'", // e.g. `/_nuxt/builds/meta/`, `/_payload.json`, `/privacy-policy/_payload.json`
         | 
| 103 | 
            +
                    ...(process.env.NODE_ENV === 'development'
         | 
| 104 | 
            +
                      ? [
         | 
| 105 | 
            +
                          'http://localhost:3000/_nuxt/', // hot reload
         | 
| 106 | 
            +
                          'https://localhost:3000/_nuxt/', // hot reload
         | 
| 107 | 
            +
                          'ws://localhost:3000/_nuxt/', // hot reload
         | 
| 108 | 
            +
                          'wss://localhost:3000/_nuxt/', // hot reload
         | 
| 109 | 
            +
                        ] // TODO: generalize for different ports
         | 
| 110 | 
            +
                      : []),
         | 
| 111 | 
            +
                  ],
         | 
| 112 | 
            +
                  'img-src': [
         | 
| 113 | 
            +
                    "'self'", // e.g. favicon
         | 
| 114 | 
            +
                    'data:', // external link icon
         | 
| 115 | 
            +
                  ],
         | 
| 116 | 
            +
                  'script-src-elem': [
         | 
| 117 | 
            +
                    "'nonce-{{nonce}}'",
         | 
| 118 | 
            +
                    `${siteUrl}/_nuxt/`, // bundle
         | 
| 119 | 
            +
                  ],
         | 
| 120 | 
            +
                  'style-src': [
         | 
| 121 | 
            +
                    "'nonce-{{nonce}}'",
         | 
| 122 | 
            +
                    "'self'", // TODO: `${siteUrl}/_nuxt/`, // bundle
         | 
| 123 | 
            +
                  ], // TODO: use `style-src-elem` once Playwright WebKit supports it
         | 
| 124 | 
            +
                },
         | 
| 125 | 
            +
              )
         | 
| 16 126 | 
             
            export const GTAG_COOKIE_ID = 'ga'
         | 
| 17 127 | 
             
            export const I18N_MODULE_CONFIG = {
         | 
| 18 128 | 
             
              langDir: 'locales',
         | 
    
        package/utils/networking.ts
    CHANGED
    
    | @@ -1,10 +1,9 @@ | |
| 1 1 | 
             
            import type { CombinedError } from '@urql/core'
         | 
| 2 | 
            -
            import { type H3Event | 
| 2 | 
            +
            import { type H3Event } from 'h3'
         | 
| 3 3 |  | 
| 4 4 | 
             
            import { type Ref } from 'vue'
         | 
| 5 5 |  | 
| 6 6 | 
             
            import type { ApiData, BackendError } from '../types/api'
         | 
| 7 | 
            -
            import { TIMEZONE_COOKIE_NAME } from './constants'
         | 
| 8 7 |  | 
| 9 8 | 
             
            export const getApiDataDefault = (): ApiData =>
         | 
| 10 9 | 
             
              computed(() =>
         | 
| @@ -109,23 +108,3 @@ export const getServiceHref = ({ | |
| 109 108 | 
             
                return `https://${nameSubdomainString}${getDomainTldPort(host)}`
         | 
| 110 109 | 
             
              }
         | 
| 111 110 | 
             
            }
         | 
| 112 | 
            -
             | 
| 113 | 
            -
            export const getTimezone = async (event: H3Event) => {
         | 
| 114 | 
            -
              const timezoneCookie = getCookie(event, TIMEZONE_COOKIE_NAME)
         | 
| 115 | 
            -
             | 
| 116 | 
            -
              if (timezoneCookie) {
         | 
| 117 | 
            -
                return timezoneCookie
         | 
| 118 | 
            -
              }
         | 
| 119 | 
            -
             | 
| 120 | 
            -
              if (event.node.req.headers['x-real-ip']) {
         | 
| 121 | 
            -
                const ipApiResult = await $fetch<{ timezone: string }>(
         | 
| 122 | 
            -
                  `http://ip-api.com/json/${event.node.req.headers['x-real-ip']}`,
         | 
| 123 | 
            -
                ).catch(() => {})
         | 
| 124 | 
            -
             | 
| 125 | 
            -
                if (ipApiResult) {
         | 
| 126 | 
            -
                  return ipApiResult.timezone
         | 
| 127 | 
            -
                }
         | 
| 128 | 
            -
              }
         | 
| 129 | 
            -
             | 
| 130 | 
            -
              return undefined
         | 
| 131 | 
            -
            }
         | 
| @@ -1,15 +0,0 @@ | |
| 1 | 
            -
            import type { Dayjs } from 'dayjs'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            export const useDateTime = () => {
         | 
| 4 | 
            -
              const { $dayjs, ssrContext } = useNuxtApp()
         | 
| 5 | 
            -
              const timezoneCookie = useCookie(TIMEZONE_COOKIE_NAME)
         | 
| 6 | 
            -
             | 
| 7 | 
            -
              const timezoneHeader = ssrContext?.event.node.req.headers[TIMEZONE_HEADER_KEY]
         | 
| 8 | 
            -
              const timezone =
         | 
| 9 | 
            -
                timezoneHeader && !Array.isArray(timezoneHeader)
         | 
| 10 | 
            -
                  ? timezoneHeader
         | 
| 11 | 
            -
                  : timezoneCookie.value || undefined
         | 
| 12 | 
            -
             | 
| 13 | 
            -
              return (dateTime?: string | number | Dayjs | Date | null) =>
         | 
| 14 | 
            -
                $dayjs(dateTime).tz(timezone)
         | 
| 15 | 
            -
            }
         | 
    
        package/server/utils/util.ts
    DELETED