├── .eslintignore ├── .npmrc ├── .prettierignore ├── tsconfig.json ├── .DS_Store ├── playground ├── package.json ├── app.vue └── nuxt.config.ts ├── src ├── config.ts ├── runtime │ ├── nitro-plugin.ts │ ├── server │ │ └── api │ │ │ └── proxy.ts │ └── public │ │ └── beacon.min.mjs └── module.ts ├── tea.yaml ├── .editorconfig ├── .nuxtrc ├── .prettierrc ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── FUNDING.yml ├── .gitignore ├── LICENSE ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # 3rd party minified scripts 2 | src/runtime/public -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamlogic/nuxt-cloudflare-analytics/HEAD/.DS_Store -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "my-module-playground" 4 | } 5 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | export const pluginName = 'nuxt-cloudflare-analytics' 2 | export const configKey = 'cloudflareAnalytics' 3 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0xE5d9F0aDC6FBd567cd4D9031541B35B5a218bC87' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | import Module from '..' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [Module], 6 | cloudflareAnalytics: { 7 | token: '123456', 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=false 2 | typescript.includeWorkspace=true 3 | 4 | # enable TypeScript bundler module resolution - https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler 5 | typescript.tsConfig.compilerOptions.moduleResolution=Bundler -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "jsxSingleQuote": true, 6 | "quoteProps": "preserve", 7 | "tabWidth": 2, 8 | "useTabs": true, 9 | "arrowParens": "avoid", 10 | "bracketSameLine": true, 11 | "printWidth": 120, 12 | "vueIndentScriptAndStyle": true 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@nuxtjs/eslint-config-typescript"], 3 | "rules": { 4 | "@typescript-eslint/no-unused-vars": ["off"], 5 | "no-tabs": ["off"], 6 | "indent": ["off"], 7 | "no-mixed-spaces-and-tabs": ["off"], 8 | "comma-dangle": ["off"], 9 | "space-before-function-paren": ["off"], 10 | "arrow-parens": ["off"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/runtime/nitro-plugin.ts: -------------------------------------------------------------------------------- 1 | import type { NitroAppPlugin } from 'nitropack' 2 | 3 | // @ts-expect-error - This is a runtime file, so we can't use the types 4 | import * as config from '#nuxt-cloudflare-analytics' 5 | 6 | const plugin: NitroAppPlugin = nitro => { 7 | nitro.hooks.hook('render:html', htmlContext => { 8 | const beaconData = JSON.stringify({ 9 | token: config.token, 10 | spa: true, 11 | }) 12 | 13 | const scriptPath = config.scriptPath || 'https://static.cloudflareinsights.com/beacon.min.js' 14 | 15 | htmlContext.body.push(``) 16 | }) 17 | } 18 | 19 | export default plugin 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.buymeacoffee.com/hamjs', 'https://paypal.me/hamknw']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .vercel_build_output 23 | .build-* 24 | .env 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | 58 | # Project 59 | playground/public/_ca -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DevSourceID 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-cloudflare-analytics", 3 | "version": "1.0.8", 4 | "description": "Cloudflare Web Analytics Modules for NuxtJS", 5 | "contributors": [ 6 | "Hamjs ", 7 | "madebyfabian " 8 | ], 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "keywords": [ 13 | "Vue", 14 | "Nuxt", 15 | "analytic", 16 | "cloudflare", 17 | "cloudflare analytic" 18 | ], 19 | "engines": { 20 | "npm": ">= 4.0.0" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/devsourceid/nuxt-cloudflare-analytics.git" 25 | }, 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/devsourceid/nuxt-cloudflare-analytics/issues" 29 | }, 30 | "homepage": "https://github.com/devsourceid/nuxt-cloudflare-analytics#readme", 31 | "type": "module", 32 | "exports": { 33 | ".": { 34 | "import": "./dist/module.mjs", 35 | "require": "./dist/module.cjs" 36 | } 37 | }, 38 | "main": "./dist/module.cjs", 39 | "types": "./dist/types.d.ts", 40 | "files": [ 41 | "dist" 42 | ], 43 | "scripts": { 44 | "prepack": "nuxt-module-build", 45 | "start": "npm run dev", 46 | "dev": "nuxi dev playground", 47 | "dev:build": "nuxi build playground", 48 | "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground" 49 | }, 50 | "dependencies": { 51 | "@nuxt/kit": "^3.0.0" 52 | }, 53 | "devDependencies": { 54 | "@nuxt/module-builder": "^0.2.1", 55 | "@nuxt/schema": "^3.0.0", 56 | "@nuxtjs/eslint-config-typescript": "^12.0.0", 57 | "eslint": "^8.31.0", 58 | "nuxt": "^3.0.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/runtime/server/api/proxy.ts: -------------------------------------------------------------------------------- 1 | import { readBody, getHeader, assertMethod, defineEventHandler, createError } from 'h3' 2 | 3 | const filterOnlyValidHeaders = (headers: Record) => { 4 | const validHeaders = new Headers() 5 | for (const [key, value] of Object.entries(headers)) { 6 | if (value && typeof value === 'string') { 7 | validHeaders.set(key, value) 8 | } 9 | } 10 | return validHeaders 11 | } 12 | 13 | /** 14 | * This is a proxy endpoint 1:1 forwarding the request to Cloudflare Tracking API. 15 | * The reason for this is that some browsers may block requests to this by accident. 16 | */ 17 | export default defineEventHandler(async event => { 18 | assertMethod(event, 'POST') 19 | 20 | // Forward request to Cloudflare Tracking API 21 | const cloudflareUrl = 'https://cloudflareinsights.com/cdn-cgi/rum' 22 | const body = await readBody(event) 23 | const headers = filterOnlyValidHeaders({ 24 | accept: getHeader(event, 'accept'), 25 | 'accept-encoding': getHeader(event, 'accept-encoding'), 26 | 'accept-language': getHeader(event, 'accept-language'), 27 | 'cache-control': getHeader(event, 'cache-control'), 28 | 'content-length': getHeader(event, 'content-length'), 29 | 'content-type': getHeader(event, 'content-type'), 30 | origin: getHeader(event, 'origin'), 31 | referer: getHeader(event, 'referer'), 32 | 'user-agent': getHeader(event, 'user-agent'), 33 | }) 34 | 35 | try { 36 | await $fetch(cloudflareUrl, { 37 | method: 'POST', 38 | headers, 39 | body, 40 | }) 41 | 42 | return true 43 | } catch (error) { 44 | throw createError({ 45 | statusCode: 500, 46 | message: 'Failed to forward request to Cloudflare Tracking API', 47 | }) 48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url' 2 | import { promises as fsp } from 'fs' 3 | import { join, dirname } from 'path' 4 | import { defineNuxtModule, createResolver, addServerHandler, addTemplate } from '@nuxt/kit' 5 | import { pluginName, configKey } from './config' 6 | 7 | export interface ModuleOptions { 8 | addPlugin: boolean 9 | token: string | undefined 10 | scriptPath: string | false | undefined 11 | proxyPath: string | false | undefined 12 | customProxyPath: string | undefined 13 | } 14 | 15 | const scriptPathDefault = '/_ca/b.js' 16 | const proxyPathDefault = false 17 | 18 | export default defineNuxtModule({ 19 | meta: { 20 | name: pluginName, 21 | configKey, 22 | compatibility: { 23 | nuxt: '^3.0.0', 24 | }, 25 | }, 26 | defaults: { 27 | addPlugin: true, 28 | token: undefined, 29 | scriptPath: scriptPathDefault, 30 | proxyPath: proxyPathDefault, 31 | customProxyPath: undefined, 32 | }, 33 | setup(options, nuxt) { 34 | if (options.addPlugin) { 35 | const { resolve } = createResolver(import.meta.url) 36 | const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) 37 | 38 | // Options 39 | const scriptPath = 40 | typeof options.scriptPath === 'string' 41 | ? options.scriptPath 42 | : options.scriptPath === false 43 | ? undefined 44 | : scriptPathDefault 45 | const proxyPath = 46 | typeof options.proxyPath === 'string' 47 | ? options.proxyPath 48 | : options.proxyPath === false 49 | ? undefined 50 | : proxyPathDefault 51 | 52 | if (!options.token) { 53 | // eslint-disable-next-line no-console 54 | return console.warn(`[${pluginName}]: No '${configKey}.token' option provided!`) 55 | } 56 | 57 | // Inject options via virtual template 58 | const virtualConfig = [ 59 | `export const scriptPath = ${JSON.stringify(scriptPath)}`, 60 | `export const token = ${JSON.stringify(options.token)}`, 61 | ].join('\n') 62 | nuxt.options.alias['#nuxt-cloudflare-analytics'] = addTemplate({ 63 | filename: 'nuxt-cloudflare-analytics.mjs', 64 | getContents: () => virtualConfig, 65 | }).dst 66 | 67 | // Public runtime config 68 | nuxt.options.runtimeConfig.public[configKey] = { 69 | token: options.token, 70 | } 71 | 72 | // Everything below is only needed in production 73 | if (nuxt.options.dev) { 74 | return 75 | } 76 | 77 | // Add server proxy handler 78 | if (proxyPath) { 79 | addServerHandler({ 80 | route: join('/', proxyPath), 81 | handler: resolve(runtimeDir, 'server/api/proxy'), 82 | }) 83 | } 84 | 85 | const addBeaconFile = async () => { 86 | // If user disabled the custom script path, we don't need to do anything. 87 | if (!scriptPath) { 88 | return 89 | } 90 | 91 | // Read file from runtime dir 92 | const file = await fsp.readFile(join(runtimeDir, '/public/beacon.min.mjs'), 'utf-8') 93 | 94 | // Replace the original url with the proxy path 95 | const newProxyPath = options.customProxyPath || proxyPath 96 | const newFile = newProxyPath ? file.replace('https://cloudflareinsights.com/cdn-cgi/rum', newProxyPath) : file 97 | 98 | // Write file to public dir of nuxt project 99 | const newFilePath = join(nuxt.options.rootDir, '/public/', scriptPath) 100 | const newDirPath = dirname(newFilePath) 101 | await fsp.mkdir(newDirPath, { recursive: true }) 102 | await fsp.writeFile(newFilePath, newFile) 103 | } 104 | 105 | // Nuxt 3 and Bridge - inject script on runtime 106 | nuxt.hook('nitro:config', async config => { 107 | await addBeaconFile() 108 | config.externals = config.externals || {} 109 | config.externals.inline = config.externals.inline || [] 110 | config.externals.inline.push(runtimeDir) 111 | config.virtual = config.virtual || {} 112 | config.virtual['#nuxt-cloudflare-analytics'] = virtualConfig 113 | config.plugins = config.plugins || [] 114 | config.plugins.push(resolve(runtimeDir, 'nitro-plugin')) 115 | }) 116 | 117 | nuxt.options.build.transpile.push(runtimeDir) 118 | } 119 | }, 120 | }) 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt Cloudflare Web Analytics Modules 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | 6 | Add Cloudflare Web Analytics to your Nuxt Project. 7 | The latest versions, above `1.0.8+` are made for Nuxt 3, if you want to use nuxt 2, use `0.1.5` 8 | 9 | **Note:** this modules is not enabled in dev mode. 10 | You can set environment variable `NODE_ENV` to `production` for testing in dev mode. 11 | 12 | ## Setup 13 | 14 | - Add `nuxt-cloudflare-analytics` dependency using yarn or npm to your project `npm i nuxt-cloudflare-analytics` or `yarn install nuxt-cloudflare-analytics` 15 | - Add `nuxt-cloudflare-analytics` to `modules` section of `nuxt.config.ts` 16 | 17 | ```ts 18 | { 19 | // either 20 | modules: [ 21 | [ 22 | 'nuxt-cloudflare-analytics', 23 | { 24 | // See below for more options 25 | token: 'your-token', // Example 1a2b3v4a5er6ac7r8afd 26 | }, 27 | ], 28 | ] 29 | 30 | // or 31 | modules: [ 32 | 'nuxt-cloudflare-analytics' 33 | ], 34 | cloudflareAnalytics: { 35 | // See below for more options 36 | token: 'your-token', // Example 1a2b3v4a5er6ac7r8afd 37 | } 38 | } 39 | ``` 40 | 41 | You can find token on Web Aalytics Page at Cloudflare Dashboard. 42 | 43 | ## Options 44 | 45 | ### `token` (!string) 46 | 47 | - Required 48 | - Cloudflare analytics token, example: `1a2b3v4a5er6ac7r8afd` 49 | 50 | ### `scriptPath` (string | false | undefined) 51 | 52 | - (Optional), defaults to `/_ca/b.js`. This is the `beacon.min.js` from cloudflare. 53 | - You can set it to `false` to not use a local script and instead use the CDN script (https://static.cloudflareinsights.com/beacon.min.js). 54 | This is not recommended though, since some browsers may not load this script otherwise. 55 | - You can set it to a custom path to define where your local script is. This **must** be a `.js` file inside your `public` folder. So if you have this file 56 | under `public/my/beacon.js`, you should set this option to `my/beacon.js`. 57 | 58 | ### `proxyPath` (string | false | undefined) 59 | 60 | - (Optional), defaults to `false`. 61 | - You can set it to a custom path to generate a proxy nuxt server api endpoint. This **must** start with `/api`. 62 | E.g. set `proxyPath` to `/api/_ca/p`, then the module will automatically 63 | - Generate this endpoint 64 | - Change the `scriptPath` to use this endpoint (as long as you don't set it to something own) 65 | - The automatically created proxy endpoint is used to send data to Cloudflare. 66 | - Benefit: This avoids some browsers blocking this request. 67 | - Downside: Depending where you host your page, cloudflare will take this country as the origin of the page click. So if you host your page in the US (e.g. vercel) but your visitor is from Germany, you will see the US as the origin of the click in your dashboard. 68 | - You can set it to `false` to not use a proxy and directly call cloudflare. 69 | Be prepared that some browsers may block the request and you will not see any data. 70 | - If you have another solution for this, e.g. vercels rewrite config, set `proxyPath` to `false` and define a `customProxyPath` (see below). 71 | 72 | ### `customProxyPath` (string | undefined) 73 | 74 | - (Optional), defaults to `undefined`. Only define this if you set `proxyPath` to `false`. 75 | - This is the path to your custom proxy endpoint, e.g. from vercels rewrite config. 76 | - e.g. 77 | 78 | ```ts 79 | // nuxt.config.ts 80 | { 81 | cloudflareAnalytics: { 82 | token: 'your-token', // Example 1a2b3v4a5er6ac7r8afd 83 | proxyPath: false, 84 | customProxyPath: '/my-proxy' 85 | } 86 | } 87 | ``` 88 | 89 | ```json 90 | // vercel.json 91 | { 92 | "rewrites": [{ "source": "/my-proxy", "destination": "https://cloudflareinsights.com/cdn-cgi/rum" }] 93 | } 94 | ``` 95 | 96 | ## Contributors 97 | 98 | - hamlogic (https://github.com/hamlogic) 99 | - madebyfabian (https://github.com/madebyfabian) 100 | 101 | ## License 102 | 103 | MIT © [Ham](https://hamlogic.art) 104 | 105 | 106 | 107 | [npm-version-src]: https://img.shields.io/npm/v/nuxt-cloudflare-analytics/latest.svg 108 | [npm-version-href]: https://www.npmjs.com/package/nuxt-cloudflare-analytics 109 | [npm-downloads-src]: https://img.shields.io/npm/dt/nuxt-cloudflare-analytics.svg 110 | [npm-downloads-href]: https://www.npmjs.com/package/nuxt-cloudflare-analytics 111 | 112 | ## Development 113 | 114 | - Clone repo 115 | - Install dependencies 116 | - Switch into [**Take Over Mode**](https://vuejs.org/guide/typescript/overview.html#volar-takeover-mode) 117 | - Ensure local files are generated using npm run dev:prepare 118 | - Start playground using npm run dev 119 | - Follow this document to learn more about Nuxt modules: https://nuxt.com/docs/guide/going-further/modules 120 | -------------------------------------------------------------------------------- /src/runtime/public/beacon.min.mjs: -------------------------------------------------------------------------------- 1 | !function(e){function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}var t={};n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n(n.s=0)}([function(e,n,t){"use strict";var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var n,t=1,r=arguments.length;t0&&(n+=r)}return n}function n(){var e=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);if(!e)return!1;var n=parseInt(e[2],10),t=navigator.connection;return n>=55&&!!t&&"cellular"===t.type&&t.downlinkMax<=.115}function t(e){return null==e?void 0:Math.round(1e3*e)/1e3}function u(e,n){for(var t in e){var r=e[t];void 0!==n&&("number"==typeof r||"string"==typeof r?n[t]=r:Array.isArray(r)&&(n[t]=JSON.parse(JSON.stringify(r))))}}!function(){function s(){return g.timeOrigin}function f(){var e=document.referrer||"",n=w[w.length-1];return x&&B&&n?n.url:e}function d(){return navigator.webdriver}function l(e){if("function"==typeof g.getEntriesByType){var n=g.getEntriesByType("navigation"),t={};e.timingsV2={},n&&n[0]&&(n[0].serverTiming&&(t.serverTiming=n[0].serverTiming),n[0].nextHopProtocol&&(t.nextHopProtocol=n[0].nextHopProtocol)),u(t,e.timingsV2)}}function p(){var n=g.getEntriesByType("navigation")[0],t="";try{t="function"==typeof g.getEntriesByType?new URL(null===n||void 0===n?void 0:n.name).pathname:R?new URL(R).pathname:window.location.pathname}catch(e){}var r={referrer:document.referrer||"",eventType:i.EventType.WebVitalsV2,si:T?T.si:0,versions:{js:"2022.10.1"},pageloadId:h,location:e(),landingPath:t,startTime:s(),wd:d()};return T&&(T.version&&(r.versions.fl=T.version),T.icTag&&(r.icTag=T.icTag),r.siteToken=T.token),A&&["lcp","fid","cls","fcp","ttfb","inp"].forEach(function(e){r[e]={value:-1,path:void 0},A[e]&&void 0!==A[e].value&&(r[e]=A[e])}),l(r),r}function v(r){var o,a=g.timing,c=g.memory,p=r||e(),v={memory:{},timings:{},resources:[],tempResources:[],referrer:f(),documentWriteIntervention:!1,errorCount:0,eventType:i.EventType.Load,firstPaint:0,firstContentfulPaint:0,si:T?T.si:0,startTime:s(),versions:{fl:T?T.version:"",js:"2022.10.1",timings:1},pageloadId:h,location:p,wd:d()};if(void 0==P){if("function"==typeof g.getEntriesByType){var y=g.getEntriesByType("navigation");y&&Array.isArray(y)&&y.length>0&&(v.timingsV2={},v.versions.timings=2,delete v.timings,u(y[0],v.timingsV2))}1===v.versions.timings&&u(a,v.timings),u(c,v.memory)}else l(v);if(v.documentWriteIntervention=n(),v.firstPaint=m("first-paint"),v.firstContentfulPaint=m("first-contentful-paint"),v.errorCount=window.__cfErrCount||0,T&&(T.icTag&&(v.icTag=T.icTag),v.siteToken=T.token),"function"==typeof g.getEntriesByType){var w=null!==(o=g.getEntriesByType("resource"))&&void 0!==o?o:[],E=0,S=0;w.forEach(function(e){var n={n:e.name,s:t(e.startTime),d:t(e.duration),i:e.initiatorType,p:e.nextHopProtocol,rs:t(e.redirectStart),re:t(e.redirectEnd),fs:t(e.fetchStart),ds:t(e.domainLookupStart),de:t(e.domainLookupEnd),cs:t(e.connectStart),ce:t(e.connectEnd),qs:t(e.requestStart),ps:t(e.responseStart),pe:t(e.responseEnd),ws:t(e.workerStart),ss:t(e.secureConnectionStart),ts:e.transferSize,ec:e.encodedBodySize,dc:e.decodedBodySize};v.tempResources&&void 0===v.tempResources[S]&&(v.tempResources[S]=[]);var r=JSON.stringify(n).length;E+r<62e3&&v.tempResources?(E+=r,v.tempResources[S].push(n)):(S++,E=0)})}return JSON.stringify(v).length>=64e3&&(v.resources=[]),void 0!==P&&(delete v.timings,delete v.memory,delete v.errorCount,delete v.documentWriteIntervention),v}function m(e){var n;if("first-contentful-paint"===e&&A.fcp&&A.fcp.value)return A.fcp.value;if("function"==typeof g.getEntriesByType){var t=null===(n=g.getEntriesByType("paint"))||void 0===n?void 0:n.filter(function(n){return n.name===e})[0];return t?t.startTime:0}return 0}var g=window.performance||window.webkitPerformance||window.msPerformance||window.mozPerformance,y=document.currentScript||("function"==typeof document.querySelector?document.querySelector("script[data-cf-beacon]"):void 0),h=c(),w=[],T=window.__cfBeacon?window.__cfBeacon:{};if(!T||"single"!==T.load){if(y){var E=y.getAttribute("data-cf-beacon");if(E)try{T=r(r({},T),JSON.parse(E))}catch(e){}else{var S=y.getAttribute("src");if(S&&"function"==typeof URLSearchParams){var b=new URLSearchParams(S.replace(/^[^\?]+\??/,"")),C=b.get("token");C&&(T.token=C);var L=b.get("spa");T.spa=null===L||"true"===L}}T&&"multi"!==T.load&&(T.load="single"),window.__cfBeacon=T}if(g&&T&&T.token){var P,R,B,O=!1,_=0;document.addEventListener("visibilitychange",function(){if("hidden"===document.visibilityState){if(x&&q()){var n=e();(null===B||void 0===B?void 0:B.url)==n&&null!==B&&void 0!==B&&B.triggered||M(),H(n)}!O&&B&&(O=!0,V())}else"visible"===document.visibilityState&&(_=(new Date).getTime())});var A={},I=function(e){if(!e||0===e.length)return null;var n=e.reduce(function(e,n){return e&&e.value>n.value?e:n});if(n&&n.sources&&n.sources.length){var t=n.sources.reduce(function(e,n){return e.node&&e.previousRect.width*e.previousRect.height>n.previousRect.width*n.previousRect.height?e:n});if(t)return t}},F=function(e){return e&&0!==e.length?e[e.length-1]:null},N=function(e){if(!e)return"";var n=e.localName;return e.id&&e.id.length>0&&(n+="#"+e.id),e.className&&e.className.length>0&&(n+="."+e.className.split(" ").join(".")),n},k=function(e){var n=window.location.pathname,t=F(e.entries);switch(A[e.name.toLowerCase()]={value:e.value,path:n},e.name){case"CLS":(t=I(e.entries))&&A.cls&&(A.cls.element=N(t.node),A.cls.currentRect=t.currentRect,A.cls.previousRect=t.previousRect);break;case"FID":t&&A.fid&&(A.fid.element=N(t.target),A.fid.name=t.name);break;case"LCP":t&&A.lcp&&(A.lcp.element=N(t.element),A.lcp.size=t.size,A.lcp.url=t.url)}};"function"==typeof PerformanceObserver&&((0,a.onLCP)(k),(0,a.onFID)(k),(0,a.onFCP)(k),(0,a.onINP)(k),(0,a.onTTFB)(k),PerformanceObserver.supportedEntryTypes&&PerformanceObserver.supportedEntryTypes.includes("layout-shift")&&(0,a.onCLS)(k));var x=T&&(void 0===T.spa||!0===T.spa),j=T.send&&T.send.to?T.send.to:void 0===T.version?"https://cloudflareinsights.com/cdn-cgi/rum":null,M=function(e){var n=function(e,n){t.resources=e,0!=n&&(t.bypassTiming=!0),T&&(1===T.r&&(t.resources=[]),(0,o.sendObjectBeacon)("",t,function(){},!1,j),void 0!==T.forward&&void 0!==T.forward.url&&(0,o.sendObjectBeacon)("",t,function(){},!1,T.forward.url))},t=v(e);if(t&&T){var r=t.tempResources;if(delete t.tempResources,x&&r&&0===r.length&&n([],0),!r)return;r.forEach(function(e,t){n(e,t)})}},V=function(){var e=p();x||(e.resources=[],delete e.tempResources),T&&(0,o.sendObjectBeacon)("",e,function(){},!0,j)},D=function(){var n=window.__cfRl&&window.__cfRl.done||window.__cfQR&&window.__cfQR.done;n?n.then(M):M(),B={id:h,url:e(),ts:(new Date).getTime(),triggered:!0}};"complete"===window.document.readyState?D():window.addEventListener("load",function(){window.setTimeout(D)});var q=function(){return x&&0===w.filter(function(e){return e.id===h}).length},H=function(e){w.push({id:h,url:e,ts:(new Date).getTime()}),w.length>3&&w.shift()};x&&(R=e(),function(n){var t=n.pushState;if(t){var r=function(){h=c(),"function"==typeof g.clearResourceTimings&&g.clearResourceTimings(),_=0};n.pushState=function(i,o,a){P=e(a);var c=e(),u=!0;return P==c&&(u=!1),u&&(q()&&((null===B||void 0===B?void 0:B.url)==c&&null!==B&&void 0!==B&&B.triggered||M(c),H(c)),r()),t.apply(n,[i,o,a])},window.addEventListener("popstate",function(n){q()&&((null===B||void 0===B?void 0:B.url)==P&&null!==B&&void 0!==B&&B.triggered||M(P),H(P)),P=e(),r()})}}(window.history))}}}()}()},function(e,n,t){"use strict";n.__esModule=!0,n.EventType=void 0;!function(e){e[e.Load=1]="Load",e[e.Additional=2]="Additional",e[e.WebVitalsV2=3]="WebVitalsV2"}(n.EventType||(n.EventType={}))},function(e,n,t){"use strict";function r(e,n,t,r,i){void 0===r&&(r=!1),void 0===i&&(i=null);var o=i||(n.siteToken&&n.versions.fl?"/cdn-cgi/rum?"+e:"/cdn-cgi/beacon/performance?"+e),a=!0;if(navigator&&"string"==typeof navigator.userAgent)try{var c=navigator.userAgent.match(/Chrome\/([0-9]+)/);c&&c[0].toLowerCase().indexOf("chrome")>-1&&parseInt(c[1])<81&&(a=!1)}catch(e){}if(navigator&&"function"==typeof navigator.sendBeacon&&a&&r){n.st=1;var u=JSON.stringify(n),s={type:"application/json"};navigator.sendBeacon(o,new Blob([u],s))}else{n.st=2;var u=JSON.stringify(n),f=new XMLHttpRequest;t&&(f.onreadystatechange=function(){4==this.readyState&&204==this.status&&t()}),f.open("POST",o,!0),f.setRequestHeader("content-type","application/json"),f.send(u)}}n.__esModule=!0,n.sendObjectBeacon=void 0,n.sendObjectBeacon=r},function(e,n,t){"use strict";n.__esModule=!0,n.onTTFB=n.onLCP=n.onINP=n.onFID=n.onFCP=n.onCLS=n.getTTFB=n.getLCP=n.getINP=n.getFID=n.getFCP=n.getCLS=void 0;var r,i,o,a,c,u=-1,s=function(e){addEventListener("pageshow",function(n){n.persisted&&(u=n.timeStamp,e(n))},!0)},f=function(){return window.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0]},d=function(){var e=f();return e&&e.activationStart||0},l=function(e,n){var t=f(),r="navigate";return u>=0?r="back-forward-cache":t&&(r=document.prerendering||d()>0?"prerender":t.type.replace(/_/g,"-")),{name:e,value:void 0===n?-1:n,rating:"good",delta:0,entries:[],id:"v3-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},p=function(e,n,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var r=new PerformanceObserver(function(e){n(e.getEntries())});return r.observe(Object.assign({type:e,buffered:!0},t||{})),r}}catch(e){}},v=function(e,n){var t=function t(r){"pagehide"!==r.type&&"hidden"!==document.visibilityState||(e(r),n&&(removeEventListener("visibilitychange",t,!0),removeEventListener("pagehide",t,!0)))};addEventListener("visibilitychange",t,!0),addEventListener("pagehide",t,!0)},m=function(e,n,t,r){var i,o;return function(a){n.value>=0&&(a||r)&&((o=n.value-(i||0))||void 0===i)&&(i=n.value,n.delta=o,n.rating=function(e,n){return e>n[1]?"poor":e>n[0]?"needs-improvement":"good"}(n.value,t),e(n))}},g=-1,y=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},h=function(){v(function(e){var n=e.timeStamp;g=n},!0)},w=function(){return g<0&&(g=y(),h(),s(function(){setTimeout(function(){g=y(),h()},0)})),{get firstHiddenTime(){return g}}},T=function(e,n){n=n||{};var t,r=[1800,3e3],i=w(),o=l("FCP"),a=function(e){e.forEach(function(e){"first-contentful-paint"===e.name&&(u&&u.disconnect(),e.startTime-1&&e(n)},o=l("CLS",0),a=0,c=[],u=function(e){e.forEach(function(e){if(!e.hadRecentInput){var n=c[0],t=c[c.length-1];a&&e.startTime-t.startTime<1e3&&e.startTime-n.startTime<5e3?(a+=e.value,c.push(e)):(a=e.value,c=[e]),a>o.value&&(o.value=a,o.entries=c,r())}})},f=p("layout-shift",u);f&&(r=m(i,o,t,n.reportAllChanges),v(function(){u(f.takeRecords()),r(!0)}),s(function(){a=0,S=-1,o=l("CLS",0),r=m(i,o,t,n.reportAllChanges)}))},C={passive:!0,capture:!0},L=new Date,P=function(e,n){r||(r=n,i=e,o=new Date,O(removeEventListener),R())},R=function(){if(i>=0&&i1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,n){var t=function(){P(e,n),i()},r=function(){i()},i=function(){removeEventListener("pointerup",t,C),removeEventListener("pointercancel",r,C)};addEventListener("pointerup",t,C),addEventListener("pointercancel",r,C)}(n,e):P(n,e)}},O=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach(function(n){return e(n,B,C)})},_=function(e,n){n=n||{};var t,o=[100,300],c=w(),u=l("FID"),f=function(e){e.startTimen.latency){if(t)t.entries.push(e),t.latency=Math.max(t.latency,e.duration);else{var r={id:e.interactionId,latency:e.duration,entries:[e]};D[r.id]=r,V.push(r)}V.sort(function(e,n){return n.latency-e.latency}),V.splice(10).forEach(function(e){delete D[e.id]})}},H=function(e,n){n=n||{};var t=[200,500];x();var r,i=l("INP"),o=function(e){e.forEach(function(e){e.interactionId&&q(e),"first-input"===e.entryType&&!V.some(function(n){return n.entries.some(function(n){return e.duration===n.duration&&e.startTime===n.startTime})})&&q(e)});var n,t=(n=Math.min(V.length-1,Math.floor(M()/50)),V[n]);t&&t.latency!==i.value&&(i.value=t.latency,i.entries=t.entries,r())},a=p("event",o,{durationThreshold:n.durationThreshold||40});r=m(e,i,t,n.reportAllChanges),a&&(a.observe({type:"first-input",buffered:!0}),v(function(){o(a.takeRecords()),i.value<0&&M()>0&&(i.value=0,i.entries=[]),r(!0)}),s(function(){V=[],j=k(),i=l("INP"),r=m(e,i,t,n.reportAllChanges)}))},J={},z=function(e,n){n=n||{};var t,r=[2500,4e3],i=w(),o=l("LCP"),a=function(e){var n=e[e.length-1];if(n){var r=n.startTime-d();rperformance.now())return;r.entries=[o],i(!0),s(function(){r=l("TTFB",0),(i=m(e,r,t,n.reportAllChanges))(!0)})}})};n.getFCP=T,n.onFCP=T,n.getCLS=b,n.onCLS=b,n.getFID=_,n.onFID=_,n.getINP=H,n.onINP=H,n.getLCP=z,n.onLCP=z,n.getTTFB=W,n.onTTFB=W},function(e,n,t){"use strict";function r(e,n,t){var r=n&&t||0;"string"==typeof e&&(n="binary"===e?new Array(16):null,e=null),e=e||{};var a=e.random||(e.rng||i)();if(a[6]=15&a[6]|64,a[8]=63&a[8]|128,n)for(var c=0;c<16;++c)n[r+c]=a[c];return n||o(a)}var i=t(5),o=t(6);e.exports=r},function(e,n,t){"use strict";var r="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof window.msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto);if(r){var i=new Uint8Array(16);e.exports=function(){return r(i),i}}else{var o=new Array(16);e.exports=function(){for(var e,n=0;n<16;n++)0==(3&n)&&(e=4294967296*Math.random()),o[n]=e>>>((3&n)<<3)&255;return o}}},function(e,n,t){"use strict";function r(e,n){var t=n||0,r=i;return[r[e[t++]],r[e[t++]],r[e[t++]],r[e[t++]],"-",r[e[t++]],r[e[t++]],"-",r[e[t++]],r[e[t++]],"-",r[e[t++]],r[e[t++]],"-",r[e[t++]],r[e[t++]],r[e[t++]],r[e[t++]],r[e[t++]],r[e[t++]]].join("")}for(var i=[],o=0;o<256;++o)i[o]=(o+256).toString(16).substr(1);e.exports=r}]); --------------------------------------------------------------------------------