From ab2e319fee866ab5f34bb656f6f19b3138028730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Tue, 17 Mar 2026 10:33:40 +0100 Subject: [PATCH 01/62] fix: prevent favicon corruption from gulp 5 encoding gulp 5 / vinyl-fs 4 changed the default encoding from binary to UTF-8, corrupting PNG/ICO bytes > 0x7F during copyFavicons. Add encoding: false to gulp.src() to restore binary copying. --- gulpfile.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 7081a94c..1eac9f9f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -69,7 +69,8 @@ function copyFavicons(done) { return gulp.src(path.join(faviconDir, '*'), { base: path.join(__dirname, 'theme', projectConf.theme), - allowEmpty: true + allowEmpty: true, + encoding: false }) .pipe(gulp.dest(config.output.path)); } From 41cf4fb6d9a8167f38212efe8cb47c6182f2ff6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Tue, 17 Mar 2026 10:36:48 +0000 Subject: [PATCH 02/62] ci: enable npm commands for Claude in GitHub Actions Add Node.js setup and npm ci steps to the Claude workflow so it can run tests and type checks. Create repo-level .claude/settings.json granting npm/npx permissions. --- .claude/settings.json | 10 ++++++++++ .github/workflows/claude.yml | 8 ++++++++ 2 files changed, 18 insertions(+) create mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..9a2a7255 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(npm test:*)", + "Bash(npm run:*)", + "Bash(npx jest:*)", + "Bash(npm install:*)" + ] + } +} diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index d300267f..4519ecb1 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -30,6 +30,14 @@ jobs: with: fetch-depth: 1 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + run: npm ci + - name: Run Claude Code id: claude uses: anthropics/claude-code-action@v1 From 82beec862289c3795702ad25de2c5abb7d79de59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Tue, 17 Mar 2026 12:21:53 +0000 Subject: [PATCH 03/62] ci: enable PR branch rebasing for Claude in GitHub Actions Grant contents:write permission and full git history so Claude can rebase PR branches. Add git command permissions to settings. --- .claude/settings.json | 7 ++++++- .github/workflows/claude.yml | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 9a2a7255..87e0055b 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -4,7 +4,12 @@ "Bash(npm test:*)", "Bash(npm run:*)", "Bash(npx jest:*)", - "Bash(npm install:*)" + "Bash(npm install:*)", + "Bash(git fetch:*)", + "Bash(git rebase:*)", + "Bash(git push:*)", + "Bash(git status:*)", + "Bash(git log:*)" ] } } diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 4519ecb1..fd5ccb29 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -19,7 +19,7 @@ jobs: (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest permissions: - contents: read + contents: write pull-requests: read issues: read id-token: write @@ -28,7 +28,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 1 + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 From 4a854a1f0f8fad8f106e0f20ba74d5ddd9ade41a Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:03:02 +0000 Subject: [PATCH 04/62] feat: improve PWA support for native mobile experience MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add iOS/Apple PWA meta tags (apple-mobile-web-app-capable, status-bar-style, title) to index.html - Replace hardcoded white theme-color with per-project brand color via HtmlWebpackPlugin options - Add themeColor and shortName to all project JSON configs - Add GenerateSW (Workbox) service worker in webpack for production builds with NetworkFirst/CacheFirst runtime caching - Register service worker in src/app.tsx (production only) - Add generateManifest gulp task that generates build/favicons/ manifest.json with correct name, short_name, theme_color, start_url, scope, and orientation per aerodrome - Add workbox-webpack-plugin devDependency - Add service-worker.js no-cache header in firebase.json - Add SPA catch-all rewrite in firebase.json for deep links Co-authored-by: Roli Züger --- firebase.json | 22 +++++++++++++++----- gulpfile.js | 52 +++++++++++++++++++++++++++++++++++++++++++++- index.html | 5 ++++- package.json | 3 ++- projects/lspv.json | 2 ++ projects/lsze.json | 2 ++ projects/lszk.json | 2 ++ projects/lszm.json | 2 ++ projects/lszo.json | 2 ++ projects/lszt.json | 2 ++ src/app.tsx | 6 ++++++ webpack.config.js | 28 +++++++++++++++++++++++++ 12 files changed, 120 insertions(+), 8 deletions(-) diff --git a/firebase.json b/firebase.json index f227d607..4e6e0ab1 100644 --- a/firebase.json +++ b/firebase.json @@ -5,11 +5,17 @@ }], "hosting": { "public": "build", - "rewrites": [{ - "source": "/api/**", - "function": "api", - "region": "europe-west1" - }], + "rewrites": [ + { + "source": "/api/**", + "function": "api", + "region": "europe-west1" + }, + { + "source": "**", + "destination": "/index.html" + } + ], "headers": [ { "source": "/index.html", @@ -17,6 +23,12 @@ { "key": "Cache-Control", "value": "no-cache" } ] }, + { + "source": "/service-worker.js", + "headers": [ + { "key": "Cache-Control", "value": "no-cache" } + ] + }, { "source": "**/*.js", "headers": [ diff --git a/gulpfile.js b/gulpfile.js index 1eac9f9f..57d40d97 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -75,6 +75,56 @@ function copyFavicons(done) { .pipe(gulp.dest(config.output.path)); } +function generateManifest(done) { + const config = require('./webpack.config.js'); + const projectName = process.env.npm_config_project || 'lszt'; + const projectConf = projects.load(projectName); + + const themeColor = projectConf.themeColor || '#ffffff'; + const shortName = projectConf.shortName || projectConf.title; + const themeName = projectConf.theme; + + const faviconDir = path.join(__dirname, 'theme', themeName, 'favicons'); + const manifestPath = path.join(faviconDir, 'manifest.json'); + + let icons; + if (fs.existsSync(manifestPath)) { + const existing = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + icons = existing.icons || []; + } else { + const pngFiles = fs.existsSync(faviconDir) + ? fs.readdirSync(faviconDir).filter(f => f.startsWith('android-chrome') && f.endsWith('.png')) + : []; + icons = pngFiles.map(f => { + const match = f.match(/(\d+)x(\d+)/); + const size = match ? `${match[1]}x${match[2]}` : '192x192'; + return { src: `/favicons/${f}`, sizes: size, type: 'image/png' }; + }); + } + + const manifest = { + name: projectConf.title, + short_name: shortName, + icons, + theme_color: themeColor, + background_color: '#ffffff', + display: 'standalone', + start_url: '/', + scope: '/', + orientation: 'portrait', + }; + + const outDir = path.join(config.output.path, 'favicons'); + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir, { recursive: true }); + } + fs.writeFileSync( + path.join(outDir, 'manifest.json'), + JSON.stringify(manifest, null, 2) + ); + done(); +} + function buildFirebaseRules() { const config = require('./webpack.config.js'); const projectName = process.env.npm_config_project || 'lszt'; @@ -87,7 +137,7 @@ function buildFirebaseRules() { .pipe(gulp.dest(config.output.path)); } -const assets = gulp.parallel(copyResetCss, copyFavicons, buildFirebaseRules); +const assets = gulp.parallel(copyResetCss, copyFavicons, buildFirebaseRules, generateManifest); exports.clean = clean; exports.build = gulp.series(clean, bundleJS, assets); diff --git a/index.html b/index.html index 64b8c0e5..985c2c8e 100644 --- a/index.html +++ b/index.html @@ -20,7 +20,10 @@ - + + + + diff --git a/package.json b/package.json index f28a28a5..605035e1 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,8 @@ "webpack": "^5.105.3", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.2", - "webpack-stream": "^7.0.0" + "webpack-stream": "^7.0.0", + "workbox-webpack-plugin": "^7.3.0" }, "repository": { "type": "git", diff --git a/projects/lspv.json b/projects/lspv.json index dae599dc..b54b5d7c 100644 --- a/projects/lspv.json +++ b/projects/lspv.json @@ -89,6 +89,8 @@ }, "theme": "lspv", "title": "Flightbox - Flugplatz Wangen-Lachen", + "themeColor": "#1d77d7", + "shortName": "Flightbox LSPV", "departureCommitRequirements": [ { "de": "Keine Starts auf RWY 26 von 12:00h bis 13:30h LT!", diff --git a/projects/lsze.json b/projects/lsze.json index 578b8832..6ee863f2 100644 --- a/projects/lsze.json +++ b/projects/lsze.json @@ -57,6 +57,8 @@ }, "theme": "lsze", "title": "Flightbox - Flugplatz Bad Ragaz", + "themeColor": "#063155", + "shortName": "Flightbox LSZE", "paymentMethods": [ { "name": "checkout", diff --git a/projects/lszk.json b/projects/lszk.json index edf57994..8310c077 100644 --- a/projects/lszk.json +++ b/projects/lszk.json @@ -46,6 +46,8 @@ "flightnetCompany": "fgzo", "theme": "lszk", "title": "FGZO Bewegungen", + "themeColor": "#284496", + "shortName": "Flightbox LSZK", "enabledFlightTypes": [ "private", "instruction", diff --git a/projects/lszm.json b/projects/lszm.json index 368fc24f..6283bcd1 100644 --- a/projects/lszm.json +++ b/projects/lszm.json @@ -64,6 +64,8 @@ }, "theme": "lszm", "title": "Flightbox - Flugplatz Mollis", + "themeColor": "#324158", + "shortName": "Flightbox LSZM", "paymentMethods": ["checkout", "invoice"], "homebasePayment": true, "loginForm": "email", diff --git a/projects/lszo.json b/projects/lszo.json index 5d1afe8b..0e112007 100644 --- a/projects/lszo.json +++ b/projects/lszo.json @@ -49,6 +49,8 @@ }, "theme": "lszo", "title": "Flightbox - Flugplatz Luzern-Beromünster", + "themeColor": "#19295d", + "shortName": "Flightbox LSZO", "paymentMethods": [ "checkout", "cash", diff --git a/projects/lszt.json b/projects/lszt.json index 74fc1d39..b9dd5fa3 100644 --- a/projects/lszt.json +++ b/projects/lszt.json @@ -102,6 +102,8 @@ "flightnetCompany": "mfgt", "theme": "lszt", "title": "Flightbox - Flugplatz Lommis", + "themeColor": "#003863", + "shortName": "Flightbox LSZT", "paymentMethods": ["cash", "card"], "memberManagement": true } diff --git a/src/app.tsx b/src/app.tsx index 00d3419c..7a78bdb0 100755 --- a/src/app.tsx +++ b/src/app.tsx @@ -57,3 +57,9 @@ setTimeout( () => window.location.reload(), moment('24:00:00', 'hh:mm:ss').diff(moment(), 'milliseconds') ); + +if (!__DEV__ && 'serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/service-worker.js'); + }); +} diff --git a/webpack.config.js b/webpack.config.js index 16c2e1ca..e3eeb010 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,6 +2,7 @@ const projects = require('./projects'); const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const { GenerateSW } = require('workbox-webpack-plugin'); const projectName = process.env.npm_config_project || 'lszt'; @@ -91,6 +92,33 @@ module.exports = { filename: 'index.html', inject: 'body', title: projectConf.title, + themeColor: projectConf.themeColor || '#ffffff', + shortName: projectConf.shortName || projectConf.title, }), + ...(process.env.ENV === 'production' ? [ + new GenerateSW({ + clientsClaim: true, + skipWaiting: true, + exclude: [/\.map$/], + runtimeCaching: [ + { + urlPattern: /^https?.*/, + handler: 'NetworkFirst', + options: { + cacheName: 'html-cache', + expiration: { maxEntries: 10 }, + }, + }, + { + urlPattern: /\.(?:js|css|woff2?|png|jpg|svg)$/, + handler: 'CacheFirst', + options: { + cacheName: 'static-cache', + expiration: { maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 }, + }, + }, + ], + }), + ] : []), ], }; From ef089ae9f8a9e716dbb628b43c03ad09223cfbae Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:51:24 +0000 Subject: [PATCH 05/62] chore: update package-lock.json with workbox-webpack-plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roli Züger --- package-lock.json | 3543 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 3349 insertions(+), 194 deletions(-) diff --git a/package-lock.json b/package-lock.json index c7942521..a34ff162 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,7 +76,8 @@ "webpack": "^5.105.3", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.2", - "webpack-stream": "^7.0.0" + "webpack-stream": "^7.0.0", + "workbox-webpack-plugin": "^7.3.0" } }, "node_modules/@adobe/css-tools": { @@ -86,6 +87,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -4255,6 +4274,160 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true, + "license": "MIT" + }, "node_modules/@sentry-internal/browser-utils": { "version": "10.41.0", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.41.0.tgz", @@ -4400,6 +4573,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, "node_modules/@swc/helpers": { "version": "0.5.18", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", @@ -4930,6 +5116,13 @@ "@types/react-router": "*" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -5025,6 +5218,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -5827,6 +6027,23 @@ "node": ">=0.10.0" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-differ": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", @@ -5871,6 +6088,28 @@ "node": ">=0.10.0" } }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -5925,6 +6164,13 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/async-done": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", @@ -5940,6 +6186,16 @@ "node": ">= 10.13.0" } }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/async-settle": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", @@ -7304,6 +7560,16 @@ "dev": true, "license": "MIT" }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -7537,6 +7803,60 @@ "node": ">=18" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/dateformat": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", @@ -7659,6 +7979,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/del": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/del/-/del-8.0.1.tgz", @@ -7920,6 +8258,22 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -8035,6 +8389,75 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -8086,15 +8509,33 @@ "node": ">= 0.4" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, "engines": { - "node": ">=6" - } - }, + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -8167,6 +8608,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -8266,19 +8714,6 @@ "node": ">=8.12.0" } }, - "node_modules/execa/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/executable": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", @@ -8611,6 +9046,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -9031,6 +9499,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -9082,6 +9581,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -9121,6 +9627,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -9292,6 +9816,23 @@ "node": ">=0.10.0" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", @@ -9674,6 +10215,19 @@ "node": ">=0.10.0" } }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -9707,6 +10261,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -9751,19 +10321,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hasha/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/hasha/node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -10233,6 +10790,21 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -10291,12 +10863,66 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -10309,6 +10935,23 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -10336,6 +10979,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -10384,6 +11062,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -10470,17 +11164,50 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-network-error": { + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", @@ -10501,6 +11228,33 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-path-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz", @@ -10559,6 +11313,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -10572,6 +11336,83 @@ "node": ">=0.10.0" } }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -10630,6 +11471,52 @@ "node": ">=0.10.0" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -10655,6 +11542,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -10806,6 +11700,24 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", @@ -10885,19 +11797,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-changed-files/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-changed-files/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -11979,6 +12878,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jsprim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", @@ -12266,6 +13175,13 @@ "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==", "dev": true }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.template": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", @@ -12428,6 +13344,16 @@ "lz-string": "bin/bin.js" } }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, "node_modules/make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -12861,6 +13787,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", @@ -12967,6 +13924,24 @@ "dev": true, "license": "MIT" }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -14040,6 +15015,29 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -14064,6 +15062,27 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpu-core": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", @@ -14424,8 +15443,24 @@ "dev": true, "license": "MIT" }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", + "node_modules/rollup": { + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", "dev": true, @@ -14477,12 +15512,49 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -14766,6 +15838,37 @@ "node": ">= 0.4" } }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -14936,6 +16039,16 @@ "node": ">=8" } }, + "node_modules/smob": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz", + "integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -14947,6 +16060,13 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true, + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -14975,6 +16095,14 @@ "source-map": "^0.6.0" } }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, "node_modules/sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", @@ -15156,18 +16284,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/start-server-and-test/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -15177,6 +16293,20 @@ "node": ">= 0.8" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -15293,6 +16423,108 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -15329,6 +16561,16 @@ "node": ">=8" } }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -15557,6 +16799,48 @@ "streamx": "^2.12.5" } }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/terser": { "version": "5.44.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", @@ -15935,6 +17219,84 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -15976,6 +17338,25 @@ "typescript-compare": "^0.0.2" } }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -16103,6 +17484,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -16167,6 +17561,17 @@ "node": ">=8" } }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -16898,149 +18303,614 @@ "node": ">= 0.10" } }, - "node_modules/webpack-stream/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/webpack-stream/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/webpack-stream/node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/workbox-background-sync": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz", + "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz", + "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-build": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz", + "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^2.4.1", + "@rollup/plugin-terser": "^0.4.3", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^11.0.1", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.79.2", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.4.0", + "workbox-broadcast-update": "7.4.0", + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-google-analytics": "7.4.0", + "workbox-navigation-preload": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-range-requests": "7.4.0", + "workbox-recipes": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0", + "workbox-streams": "7.4.0", + "workbox-sw": "7.4.0", + "workbox-window": "7.4.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/workbox-build/node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/workbox-build/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/workbox-build/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/workbox-build/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/workbox-build/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz", + "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-core": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz", + "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz", + "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "idb": "^7.0.1", + "workbox-core": "7.4.0" } }, - "node_modules/webpack-stream/node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "node_modules/workbox-google-analytics": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz", + "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==", "dev": true, + "license": "MIT", "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" + "workbox-background-sync": "7.4.0", + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" } }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "node_modules/workbox-navigation-preload": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz", + "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==", "dev": true, + "license": "MIT", "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" + "workbox-core": "7.4.0" } }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "node_modules/workbox-precaching": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz", + "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==", "dev": true, - "engines": { - "node": ">=0.8.0" + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" } }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "node_modules/workbox-range-requests": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz", + "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==", "dev": true, "license": "MIT", "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" + "workbox-core": "7.4.0" } }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/workbox-recipes": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz", + "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" } }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "node_modules/workbox-routing": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz", + "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "workbox-core": "7.4.0" } }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "node_modules/workbox-strategies": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz", + "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==", "dev": true, "license": "MIT", "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" + "workbox-core": "7.4.0" } }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/workbox-streams": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz", + "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0" } }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "node_modules/workbox-sw": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz", + "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-webpack-plugin": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-7.4.0.tgz", + "integrity": "sha512-NRgx4lYe4JP5I8qqiROmngbc38WyyN3BZh48lUir2XYJ63EuHWN0KpDxgcYQ/fJtQQIBoswwUPmpqwQmaupnxQ==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" + "fast-json-stable-stringify": "^2.1.0", + "pretty-bytes": "^5.4.1", + "upath": "^1.2.0", + "webpack-sources": "^1.4.3", + "workbox-build": "7.4.0" }, "engines": { - "node": ">= 0.4" + "node": ">=20.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "webpack": "^4.4.0 || ^5.91.0" } }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true + "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/workbox-window": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz", + "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.4.0" + } }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -17265,6 +19135,17 @@ "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", "dev": true }, + "@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dev": true, + "requires": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + } + }, "@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -20131,6 +22012,94 @@ "integrity": "sha512-YRCrJdhQLobGIQ8Cj1sta3nn6DrZDTSUnrIYhS2e5V590BmfVDleKoAquclAiKSBKWJwmuXTb+b4BL6rSHnahw==", "dev": true }, + "@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + } + }, + "@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "dependencies": { + "@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + } + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + } + } + }, + "@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + } + }, + "@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "requires": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + } + } + }, "@sentry-internal/browser-utils": { "version": "10.41.0", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.41.0.tgz", @@ -20234,6 +22203,18 @@ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true }, + "@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dev": true, + "requires": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, "@swc/helpers": { "version": "0.5.18", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", @@ -20682,6 +22663,12 @@ "@types/react-router": "*" } }, + "@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", @@ -20774,6 +22761,12 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true + }, "@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -21314,6 +23307,16 @@ "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", "dev": true }, + "array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + } + }, "array-differ": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", @@ -21344,6 +23347,21 @@ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true }, + "arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + } + }, "asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -21382,6 +23400,12 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, "async-done": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", @@ -21393,6 +23417,12 @@ "stream-exhaust": "^1.0.2" } }, + "async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true + }, "async-settle": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", @@ -22351,6 +24381,12 @@ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", "dev": true }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, "css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -22519,6 +24555,39 @@ "whatwg-url": "^14.0.0" } }, + "data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + } + }, + "data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + } + }, + "data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, "dateformat": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", @@ -22591,6 +24660,17 @@ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "del": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/del/-/del-8.0.1.tgz", @@ -22792,6 +24872,15 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, + "ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "requires": { + "jake": "^10.8.5" + } + }, "electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -22877,6 +24966,68 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + } + }, "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -22916,6 +25067,17 @@ "hasown": "^2.0.2" } }, + "es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "requires": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + } + }, "escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -22973,6 +25135,12 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -23049,12 +25217,6 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true } } }, @@ -23303,7 +25465,36 @@ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "escape-string-regexp": "^1.0.5" + } + }, + "filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "fill-range": { @@ -23609,6 +25800,26 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, + "function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, "generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -23645,6 +25856,12 @@ "math-intrinsics": "^1.1.0" } }, + "get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -23670,6 +25887,17 @@ "pump": "^3.0.0" } }, + "get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -23792,6 +26020,16 @@ "which": "^1.2.14" } }, + "globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "requires": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + } + }, "globby": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", @@ -24076,6 +26314,12 @@ } } }, + "has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -24100,6 +26344,15 @@ "es-define-property": "^1.0.0" } }, + "has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.0" + } + }, "has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -24125,12 +26378,6 @@ "type-fest": "^0.8.0" }, "dependencies": { - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -24445,6 +26692,17 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + } + }, "interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -24486,12 +26744,45 @@ "has-tostringtag": "^1.0.2" } }, + "is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "requires": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } + }, + "is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "requires": { + "has-bigints": "^1.0.2" + } + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -24501,6 +26792,16 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -24516,6 +26817,27 @@ "hasown": "^2.0.2" } }, + "is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + } + }, + "is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + } + }, "is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -24548,6 +26870,15 @@ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, + "is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -24601,12 +26932,30 @@ "is-path-inside": "^3.0.2" } }, + "is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", "dev": true }, + "is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true + }, "is-network-error": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", @@ -24619,6 +26968,22 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true + }, "is-path-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz", @@ -24655,6 +27020,12 @@ "hasown": "^2.0.2" } }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true + }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -24664,6 +27035,48 @@ "is-unc-path": "^1.0.0" } }, + "is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + } + }, "is-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -24700,6 +27113,31 @@ "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", "dev": true }, + "is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true + }, + "is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, + "is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -24715,6 +27153,12 @@ "is-inside-container": "^1.0.0" } }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -24819,6 +27263,17 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "requires": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + } + }, "jest": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", @@ -24865,12 +27320,6 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -25656,6 +28105,12 @@ "universalify": "^2.0.0" } }, + "jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true + }, "jsprim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", @@ -25891,6 +28346,12 @@ "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==", "dev": true }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, "lodash.template": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", @@ -26025,6 +28486,15 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -26334,6 +28804,26 @@ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + } + }, "object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", @@ -26412,6 +28902,17 @@ "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", "dev": true }, + "own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -27176,6 +29677,22 @@ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true }, + "reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -27197,6 +29714,20 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, + "regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + } + }, "regexpu-core": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", @@ -27460,6 +29991,15 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, + "rollup": { + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, "rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -27490,12 +30030,35 @@ "tslib": "^2.1.0" } }, + "safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + } + }, "safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -27723,10 +30286,33 @@ "requires": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, + "set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" } }, "setprototypeof": { @@ -27850,6 +30436,12 @@ "is-fullwidth-code-point": "^3.0.0" } }, + "smob": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz", + "integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==", + "dev": true + }, "sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -27861,6 +30453,12 @@ "websocket-driver": "^0.7.4" } }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -27883,6 +30481,12 @@ "source-map": "^0.6.0" } }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", @@ -28016,12 +30620,6 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true } } }, @@ -28031,6 +30629,16 @@ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true }, + "stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + } + }, "stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -28130,6 +30738,76 @@ "strip-ansi": "^6.0.1" } }, + "string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + } + }, + "string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + } + }, + "string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -28154,6 +30832,12 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true }, + "strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -28277,6 +30961,32 @@ "streamx": "^2.12.5" } }, + "temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true + }, + "tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "dependencies": { + "type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true + } + } + }, "terser": { "version": "5.44.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", @@ -28537,6 +31247,59 @@ "mime-types": "~2.1.24" } }, + "typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + } + }, + "typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + } + }, + "typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + } + }, + "typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + } + }, "typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -28567,6 +31330,18 @@ "typescript-compare": "^0.0.2" } }, + "unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + } + }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -28662,6 +31437,15 @@ "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, "universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -28708,6 +31492,12 @@ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", "dev": true }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, "update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -29301,6 +32091,52 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "requires": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + } + }, + "which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + } + }, + "which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "requires": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + } + }, "which-typed-array": { "version": "1.1.19", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", @@ -29322,6 +32158,325 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, + "workbox-background-sync": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz", + "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==", + "dev": true, + "requires": { + "idb": "^7.0.1", + "workbox-core": "7.4.0" + } + }, + "workbox-broadcast-update": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz", + "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==", + "dev": true, + "requires": { + "workbox-core": "7.4.0" + } + }, + "workbox-build": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz", + "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==", + "dev": true, + "requires": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^2.4.1", + "@rollup/plugin-terser": "^0.4.3", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^11.0.1", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.79.2", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.4.0", + "workbox-broadcast-update": "7.4.0", + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-google-analytics": "7.4.0", + "workbox-navigation-preload": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-range-requests": "7.4.0", + "workbox-recipes": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0", + "workbox-streams": "7.4.0", + "workbox-sw": "7.4.0", + "workbox-window": "7.4.0" + }, + "dependencies": { + "@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true + }, + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "dev": true, + "requires": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + } + }, + "jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "requires": { + "@isaacs/cliui": "^9.0.0" + } + }, + "lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true + }, + "minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + }, + "path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + }, + "source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "requires": { + "whatwg-url": "^7.0.0" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "workbox-cacheable-response": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz", + "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==", + "dev": true, + "requires": { + "workbox-core": "7.4.0" + } + }, + "workbox-core": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz", + "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==", + "dev": true + }, + "workbox-expiration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz", + "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==", + "dev": true, + "requires": { + "idb": "^7.0.1", + "workbox-core": "7.4.0" + } + }, + "workbox-google-analytics": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz", + "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==", + "dev": true, + "requires": { + "workbox-background-sync": "7.4.0", + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "workbox-navigation-preload": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz", + "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==", + "dev": true, + "requires": { + "workbox-core": "7.4.0" + } + }, + "workbox-precaching": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz", + "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==", + "dev": true, + "requires": { + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "workbox-range-requests": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz", + "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==", + "dev": true, + "requires": { + "workbox-core": "7.4.0" + } + }, + "workbox-recipes": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz", + "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==", + "dev": true, + "requires": { + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "workbox-routing": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz", + "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==", + "dev": true, + "requires": { + "workbox-core": "7.4.0" + } + }, + "workbox-strategies": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz", + "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==", + "dev": true, + "requires": { + "workbox-core": "7.4.0" + } + }, + "workbox-streams": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz", + "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==", + "dev": true, + "requires": { + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0" + } + }, + "workbox-sw": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz", + "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==", + "dev": true + }, + "workbox-webpack-plugin": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-7.4.0.tgz", + "integrity": "sha512-NRgx4lYe4JP5I8qqiROmngbc38WyyN3BZh48lUir2XYJ63EuHWN0KpDxgcYQ/fJtQQIBoswwUPmpqwQmaupnxQ==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "^2.1.0", + "pretty-bytes": "^5.4.1", + "upath": "^1.2.0", + "webpack-sources": "^1.4.3", + "workbox-build": "7.4.0" + }, + "dependencies": { + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "workbox-window": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz", + "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==", + "dev": true, + "requires": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.4.0" + } + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", From 57ecb16d9c54f9a6070a6a9898ad5e08834ca4d9 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:08:20 +0000 Subject: [PATCH 06/62] fix: generate service worker for all non-dev builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Service worker registration in app.tsx runs whenever !__DEV__, but GenerateSW was only included when ENV=production. Test deployments (e.g. lsze-test.web.app) built without ENV=production had no service-worker.js, causing Firebase's catch-all rewrite to serve index.html with a text/html MIME type error. Fix: use !process.env.DEV as the condition for GenerateSW, matching the registration condition in app.tsx. Fixes #640 Co-authored-by: Roli Züger --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index e3eeb010..755be032 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -95,7 +95,7 @@ module.exports = { themeColor: projectConf.themeColor || '#ffffff', shortName: projectConf.shortName || projectConf.title, }), - ...(process.env.ENV === 'production' ? [ + ...(!process.env.DEV ? [ new GenerateSW({ clientsClaim: true, skipWaiting: true, From facbfc5272f7c2eecdfcf57749b9c82795b75e32 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:21:24 +0000 Subject: [PATCH 07/62] feat: replace magic link email auth with OTP code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the email magic link authentication with a 6-digit OTP code flow that works reliably in iOS PWA standalone mode and avoids link expiry/mangling issues. New Cloud Functions: - generateSignInCode: generates crypto-random 6-digit code, stores SHA-256 hash in RTDB /signInCodes (admin-only path) - verifySignInCode: verifies code against stored hash, rate limits to 5 attempts, deletes code on success (single-use), returns Firebase custom auth token Updated Cloud Functions: - sendSignInEmail: accepts signInCode instead of signInLink - emailTemplates: updated to render OTP code in email body - Old generateSignInLink function kept (will be removed later) Frontend changes: - OtpCodeForm: world-class 6-box OTP input with auto-advance, backspace navigation, paste support, autoComplete="one-time-code" - EmailLoginForm: shows OtpCodeForm after email is sent - EmailLoginFormContainer: wires verifyOtpCode, removes magic link props - firebase.ts: adds requestSignInCode/verifyOtpCode, removes isSignInWithEmail/signInWithEmail - Auth module: adds VERIFY_OTP_CODE/OTP_VERIFICATION_FAILURE actions, removes magic link completion actions/sagas - RTDB rules: /signInCodes path set to read:false/write:false Co-authored-by: Roli Züger --- firebase-rules-template.json | 5 + functions/auth/emailTemplates.js | 4 +- functions/auth/emailTemplates.spec.js | 36 ++-- functions/auth/generateSignInCode.js | 64 +++++++ functions/auth/sendSignInEmail.js | 10 +- functions/auth/sendSignInEmail.spec.js | 22 +-- functions/auth/templates/signin.html | 23 +-- functions/auth/templates/signin.txt | 10 +- functions/auth/verifySignInCode.js | 117 ++++++++++++ functions/index.js | 4 + src/components/LoginPage/EmailLoginForm.tsx | 47 ++--- src/components/LoginPage/OtpCodeForm.tsx | 198 ++++++++++++++++++++ src/containers/EmailLoginFormContainer.tsx | 12 +- src/locales/de.json | 11 +- src/modules/auth/actions.ts | 25 +-- src/modules/auth/index.ts | 4 +- src/modules/auth/reducer.spec.ts | 18 +- src/modules/auth/reducer.ts | 10 +- src/modules/auth/sagas.spec.ts | 102 +++------- src/modules/auth/sagas.ts | 53 ++---- src/util/firebase.spec.ts | 112 ++++++----- src/util/firebase.ts | 84 ++++----- 22 files changed, 625 insertions(+), 346 deletions(-) create mode 100644 functions/auth/generateSignInCode.js create mode 100644 functions/auth/verifySignInCode.js create mode 100644 src/components/LoginPage/OtpCodeForm.tsx diff --git a/firebase-rules-template.json b/firebase-rules-template.json index b05faf55..a1f38dd5 100644 --- a/firebase-rules-template.json +++ b/firebase-rules-template.json @@ -423,6 +423,11 @@ } } }, + "signInCodes": { + ".read": false, + ".write": false, + ".indexOn": ["email"] + }, "profiles": { "$profile_id": { ".read": "auth !== null && $profile_id === auth.uid", diff --git a/functions/auth/emailTemplates.js b/functions/auth/emailTemplates.js index c766170f..9090e869 100644 --- a/functions/auth/emailTemplates.js +++ b/functions/auth/emailTemplates.js @@ -14,9 +14,9 @@ const readTemplate = (templateName, format) => { return fs.readFileSync(templatePath, 'utf8'); }; -const getSignInEmailContent = ({ signInLink, airportName, themeColor }) => { +const getSignInEmailContent = ({ signInCode, airportName, themeColor }) => { const replacements = { - signInLink, + signInCode, airportName, themeColor }; diff --git a/functions/auth/emailTemplates.spec.js b/functions/auth/emailTemplates.spec.js index 839a3a16..e03b0039 100644 --- a/functions/auth/emailTemplates.spec.js +++ b/functions/auth/emailTemplates.spec.js @@ -11,9 +11,9 @@ describe('functions', () => { beforeEach(() => { fs.readFileSync.mockImplementation((filePath) => { if (filePath.endsWith('.html')) { - return '{{airportName}}{{themeColor}}'; + return '

{{signInCode}}

{{airportName}}{{themeColor}}'; } - return 'Sign in at {{signInLink}} for {{airportName}}'; + return 'Code: {{signInCode}} for {{airportName}}'; }); }); @@ -23,7 +23,7 @@ describe('functions', () => { it('returns subject, html, and text', () => { const result = getSignInEmailContent({ - signInLink: 'https://example.com/signin', + signInCode: '123456', airportName: 'Thun Airport', themeColor: '#003863' }); @@ -32,19 +32,19 @@ describe('functions', () => { expect(result.text).toBeDefined(); }); - it('replaces signInLink placeholder in html', () => { + it('replaces signInCode placeholder in html', () => { const result = getSignInEmailContent({ - signInLink: 'https://example.com/signin', + signInCode: '123456', airportName: 'Thun', themeColor: '#003863' }); - expect(result.html).toContain('https://example.com/signin'); - expect(result.html).not.toContain('{{signInLink}}'); + expect(result.html).toContain('123456'); + expect(result.html).not.toContain('{{signInCode}}'); }); it('replaces airportName placeholder in html', () => { const result = getSignInEmailContent({ - signInLink: 'https://example.com/signin', + signInCode: '123456', airportName: 'Thun Airport', themeColor: '#003863' }); @@ -54,7 +54,7 @@ describe('functions', () => { it('replaces themeColor placeholder in html', () => { const result = getSignInEmailContent({ - signInLink: 'https://example.com/signin', + signInCode: '123456', airportName: 'Thun', themeColor: '#003863' }); @@ -62,30 +62,30 @@ describe('functions', () => { expect(result.html).not.toContain('{{themeColor}}'); }); - it('replaces signInLink placeholder in text', () => { + it('replaces signInCode placeholder in text', () => { const result = getSignInEmailContent({ - signInLink: 'https://example.com/signin', + signInCode: '123456', airportName: 'Thun', themeColor: '#003863' }); - expect(result.text).toContain('https://example.com/signin'); - expect(result.text).not.toContain('{{signInLink}}'); + expect(result.text).toContain('123456'); + expect(result.text).not.toContain('{{signInCode}}'); }); it('leaves unknown placeholders unchanged', () => { - fs.readFileSync.mockReturnValue('{{unknownKey}} {{signInLink}}'); + fs.readFileSync.mockReturnValue('{{unknownKey}} {{signInCode}}'); const result = getSignInEmailContent({ - signInLink: 'https://example.com', + signInCode: '654321', airportName: 'Thun', themeColor: '#003863' }); expect(result.html).toContain('{{unknownKey}}'); - expect(result.html).toContain('https://example.com'); + expect(result.html).toContain('654321'); }); it('reads html template from correct path', () => { getSignInEmailContent({ - signInLink: 'https://example.com', + signInCode: '123456', airportName: 'Thun', themeColor: '#003863' }); @@ -97,7 +97,7 @@ describe('functions', () => { it('reads text template from correct path', () => { getSignInEmailContent({ - signInLink: 'https://example.com', + signInCode: '123456', airportName: 'Thun', themeColor: '#003863' }); diff --git a/functions/auth/generateSignInCode.js b/functions/auth/generateSignInCode.js new file mode 100644 index 00000000..504bb5c9 --- /dev/null +++ b/functions/auth/generateSignInCode.js @@ -0,0 +1,64 @@ +'use strict'; + +const functions = require('firebase-functions'); +const admin = require('firebase-admin'); +const crypto = require('crypto'); +const cors = require('cors')({origin: true}); + +const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +const CODE_EXPIRY_MS = 10 * 60 * 1000; // 10 minutes + +const hashCode = (code) => { + return crypto.createHash('sha256').update(code).digest('hex'); +}; + +const validateRequest = (method, body) => { + if (method !== 'POST') { + return { error: 'Method not allowed', status: 405 }; + } + + const { email } = body; + + if (!email) { + return { error: 'Email is required', status: 400 }; + } + + if (!EMAIL_REGEX.test(email)) { + return { error: 'Invalid email format', status: 400 }; + } + + return null; +}; + +exports.generateSignInCode = functions.region('europe-west1').https.onRequest((req, res) => { + return cors(req, res, async () => { + try { + const validationError = validateRequest(req.method, req.body); + if (validationError) { + return res.status(validationError.status).json({ error: validationError.error }); + } + + const { email } = req.body; + const normalizedEmail = email.toLowerCase(); + + const code = String(crypto.randomInt(0, 1000000)).padStart(6, '0'); + const codeHash = hashCode(code); + const expiry = Date.now() + CODE_EXPIRY_MS; + + await admin.database().ref('/signInCodes').push({ + email: normalizedEmail, + codeHash, + expiry, + attempts: 0, + }); + + res.status(200).json({ code, email: normalizedEmail }); + } catch (error) { + console.error('Error generating sign-in code:', error); + res.status(500).json({ + error: 'Failed to generate sign-in code', + details: error.message, + }); + } + }); +}); diff --git a/functions/auth/sendSignInEmail.js b/functions/auth/sendSignInEmail.js index 0c57d968..69c49dda 100644 --- a/functions/auth/sendSignInEmail.js +++ b/functions/auth/sendSignInEmail.js @@ -11,9 +11,9 @@ const validateRequest = (method, body) => { return { error: 'Method not allowed', status: 405 }; } - const { email, signInLink } = body; - if (!email || !signInLink) { - return { error: 'Email and signInLink are required', status: 400 }; + const { email, signInCode } = body; + if (!email || !signInCode) { + return { error: 'Email and signInCode are required', status: 400 }; } return null; @@ -55,10 +55,10 @@ exports.sendSignInEmail = functions.region('europe-west1').https.onRequest((req, return res.status(validationError.status).json({ error: validationError.error }); } - const { email, signInLink, airportName, themeColor } = req.body; + const { email, signInCode, airportName, themeColor } = req.body; const smtpSettings = await loadSmtpSettings(); - const emailContent = getSignInEmailContent({ signInLink, airportName, themeColor }); + const emailContent = getSignInEmailContent({ signInCode, airportName, themeColor }); const transporter = createTransporter(smtpSettings); const info = await transporter.sendMail({ diff --git a/functions/auth/sendSignInEmail.spec.js b/functions/auth/sendSignInEmail.spec.js index 1e981749..277707a5 100644 --- a/functions/auth/sendSignInEmail.spec.js +++ b/functions/auth/sendSignInEmail.spec.js @@ -47,8 +47,8 @@ describe('functions', () => { mockEmailTemplates = { getSignInEmailContent: jest.fn().mockReturnValue({ subject: 'Sign in', - html: '

Click here

', - text: 'Click here' + html: '

Your code

', + text: 'Your code' }) }; @@ -75,14 +75,14 @@ describe('functions', () => { }); it('returns 400 when email is missing', async () => { - const req = makeReq('POST', { signInLink: 'https://link' }); + const req = makeReq('POST', { signInCode: '123456' }); const res = makeRes(); await capturedHandler(req, res); expect(res.status).toHaveBeenCalledWith(400); - expect(res.json).toHaveBeenCalledWith({ error: 'Email and signInLink are required' }); + expect(res.json).toHaveBeenCalledWith({ error: 'Email and signInCode are required' }); }); - it('returns 400 when signInLink is missing', async () => { + it('returns 400 when signInCode is missing', async () => { const req = makeReq('POST', { email: 'user@example.com' }); const res = makeRes(); await capturedHandler(req, res); @@ -92,7 +92,7 @@ describe('functions', () => { it('sends email and returns 200 on success', async () => { const req = makeReq('POST', { email: 'user@example.com', - signInLink: 'https://sign-in.example.com', + signInCode: '123456', airportName: 'Thun', themeColor: '#003863' }); @@ -108,14 +108,14 @@ describe('functions', () => { it('calls getSignInEmailContent with correct params', async () => { const req = makeReq('POST', { email: 'user@example.com', - signInLink: 'https://link', + signInCode: '123456', airportName: 'Thun', themeColor: '#003' }); const res = makeRes(); await capturedHandler(req, res); expect(mockEmailTemplates.getSignInEmailContent).toHaveBeenCalledWith({ - signInLink: 'https://link', + signInCode: '123456', airportName: 'Thun', themeColor: '#003' }); @@ -125,7 +125,7 @@ describe('functions', () => { mockAdmin.database().ref().once.mockResolvedValue({ val: () => null }); const req = makeReq('POST', { email: 'user@example.com', - signInLink: 'https://link' + signInCode: '123456' }); const res = makeRes(); await capturedHandler(req, res); @@ -138,7 +138,7 @@ describe('functions', () => { }); const req = makeReq('POST', { email: 'user@example.com', - signInLink: 'https://link' + signInCode: '123456' }); const res = makeRes(); await capturedHandler(req, res); @@ -148,7 +148,7 @@ describe('functions', () => { it('creates transporter with correct SMTP settings', async () => { const req = makeReq('POST', { email: 'user@example.com', - signInLink: 'https://link' + signInCode: '123456' }); const res = makeRes(); await capturedHandler(req, res); diff --git a/functions/auth/templates/signin.html b/functions/auth/templates/signin.html index e1773044..5df00a02 100644 --- a/functions/auth/templates/signin.html +++ b/functions/auth/templates/signin.html @@ -18,26 +18,23 @@

Flightbox

Hallo!

-

Klicken Sie auf die Schaltfläche unten, um sich sicher bei Ihrem Flightbox-Konto anzumelden:

+

Geben Sie den folgenden Code in der Flightbox-App ein, um sich anzumelden:

- +
- - Bei Flightbox anmelden - +
+

Ihr Anmelde-Code

+

{{signInCode}}

+
-

Falls die Schaltfläche nicht funktioniert, kopieren Sie diesen Link und fügen Sie ihn in Ihren Browser ein:

-

{{signInLink}}

-
-

🔒 Sicherheitshinweis

+

Sicherheitshinweis

- • Dieser Link läuft in 1 Stunde aus Sicherheitsgründen ab
- • Falls Sie diesen Anmelde-Link nicht angefordert haben, können Sie diese E-Mail ignorieren
- • Teilen Sie diesen Link niemals mit anderen + • Dieser Code ist 10 Minuten gültig
+ • Falls Sie diesen Code nicht angefordert haben, können Sie diese E-Mail ignorieren
+ • Teilen Sie diesen Code niemals mit anderen

diff --git a/functions/auth/templates/signin.txt b/functions/auth/templates/signin.txt index 4112a2f3..9b1f4455 100644 --- a/functions/auth/templates/signin.txt +++ b/functions/auth/templates/signin.txt @@ -1,13 +1,13 @@ Hallo! -Willkommen bei Flightbox! Klicken Sie auf den folgenden Link, um sich sicher bei Ihrem Konto anzumelden: +Geben Sie den folgenden Code in der Flightbox-App ein, um sich anzumelden: -{{signInLink}} + {{signInCode}} SICHERHEITSHINWEIS: -• Dieser Link läuft in 1 Stunde aus Sicherheitsgründen ab -• Falls Sie diesen Anmelde-Link nicht angefordert haben, können Sie diese E-Mail ignorieren -• Teilen Sie diesen Link niemals mit anderen +- Dieser Code ist 10 Minuten gültig +- Falls Sie diesen Code nicht angefordert haben, können Sie diese E-Mail ignorieren +- Teilen Sie diesen Code niemals mit anderen --- Diese E-Mail wurde von der open digital Flightbox gesendet diff --git a/functions/auth/verifySignInCode.js b/functions/auth/verifySignInCode.js new file mode 100644 index 00000000..17fa6ed0 --- /dev/null +++ b/functions/auth/verifySignInCode.js @@ -0,0 +1,117 @@ +'use strict'; + +const functions = require('firebase-functions'); +const admin = require('firebase-admin'); +const crypto = require('crypto'); +const cors = require('cors')({origin: true}); + +const MAX_ATTEMPTS = 5; + +const hashCode = (code) => { + return crypto.createHash('sha256').update(code).digest('hex'); +}; + +const validateRequest = (method, body) => { + if (method !== 'POST') { + return { error: 'Method not allowed', status: 405 }; + } + + const { email, code } = body; + + if (!email || !code) { + return { error: 'Email and code are required', status: 400 }; + } + + return null; +}; + +exports.verifySignInCode = functions.region('europe-west1').https.onRequest((req, res) => { + return cors(req, res, async () => { + try { + const validationError = validateRequest(req.method, req.body); + if (validationError) { + return res.status(validationError.status).json({ error: validationError.error }); + } + + const { email, code } = req.body; + const normalizedEmail = email.toLowerCase(); + const codeHash = hashCode(code); + const now = Date.now(); + + const db = admin.database(); + const codesRef = db.ref('/signInCodes'); + + const snapshot = await codesRef + .orderByChild('email') + .equalTo(normalizedEmail) + .once('value'); + + if (!snapshot.exists()) { + return res.status(400).json({ error: 'Invalid or expired code' }); + } + + let validKey = null; + const attemptsUpdates = {}; + + snapshot.forEach(child => { + const data = child.val(); + if (data.expiry <= now) { + return; + } + if (data.attempts >= MAX_ATTEMPTS) { + return; + } + if (validKey === null && data.codeHash === codeHash) { + validKey = child.key; + } else { + attemptsUpdates[`${child.key}/attempts`] = (data.attempts || 0) + 1; + } + }); + + if (!validKey) { + if (Object.keys(attemptsUpdates).length > 0) { + await codesRef.update(attemptsUpdates); + } else { + // Increment attempts on unexpired, non-exhausted entries + const fallbackUpdates = {}; + snapshot.forEach(child => { + const data = child.val(); + if (data.expiry > now && data.attempts < MAX_ATTEMPTS) { + fallbackUpdates[`${child.key}/attempts`] = (data.attempts || 0) + 1; + } + }); + if (Object.keys(fallbackUpdates).length > 0) { + await codesRef.update(fallbackUpdates); + } + } + return res.status(400).json({ error: 'Invalid or expired code' }); + } + + // Consume the code (delete it so it can only be used once) + await codesRef.child(validKey).remove(); + + // Get or create the Firebase Auth user + let uid; + try { + const userRecord = await admin.auth().getUserByEmail(normalizedEmail); + uid = userRecord.uid; + } catch (e) { + if (e.code === 'auth/user-not-found') { + const newUser = await admin.auth().createUser({ email: normalizedEmail }); + uid = newUser.uid; + } else { + throw e; + } + } + + const customToken = await admin.auth().createCustomToken(uid); + res.status(200).json({ token: customToken }); + } catch (error) { + console.error('Error verifying sign-in code:', error); + res.status(500).json({ + error: 'Failed to verify sign-in code', + details: error.message, + }); + } + }); +}); diff --git a/functions/index.js b/functions/index.js index bed98513..a3f7ad31 100644 --- a/functions/index.js +++ b/functions/index.js @@ -18,6 +18,8 @@ const enrichMovements = require('./enrichMovements'); const auth = require('./auth'); const { generateSignInLink } = require('./auth/generateSignInLink'); +const { generateSignInCode } = require('./auth/generateSignInCode'); +const { verifySignInCode } = require('./auth/verifySignInCode'); const { sendSignInEmail } = require('./auth/sendSignInEmail'); const api = require('./api'); const webhook = require('./webhook'); @@ -27,6 +29,8 @@ const updateArrivalPaymentStatus = require('./updateArrivalPaymentStatus'); exports.auth = auth; exports.generateSignInLink = generateSignInLink; +exports.generateSignInCode = generateSignInCode; +exports.verifySignInCode = verifySignInCode; exports.sendSignInEmail = sendSignInEmail; exports.api = api; exports.webhook = webhook; diff --git a/src/components/LoginPage/EmailLoginForm.tsx b/src/components/LoginPage/EmailLoginForm.tsx index 4a4a301a..0a69ef78 100644 --- a/src/components/LoginPage/EmailLoginForm.tsx +++ b/src/components/LoginPage/EmailLoginForm.tsx @@ -6,6 +6,7 @@ import LabeledComponent from '../LabeledComponent'; import Failure from './Failure'; import Button from '../Button'; import GuestTokenLogin from '../../containers/GuestTokenLoginContainer' +import OtpCodeForm from './OtpCodeForm'; const handleSubmit = (authenticate, email, local, e) => { e.preventDefault(); @@ -61,20 +62,24 @@ const EmailLoginForm = props => { queryToken, guestOnly, sendAuthenticationEmail, - completeEmailAuthentication, + verifyOtpCode, email, submitting, failure, emailSent, - emailLoginParamsPresent, - emailLoginCompletionFailure, + otpVerificationFailure, updateEmail } = props; if (emailSent) { - return
{t('login.emailSentPre')} {email} {t('login.emailSentPost')} -
+ return ( + verifyOtpCode(email, code)} + /> + ); } const emailInput = ( @@ -90,31 +95,6 @@ const EmailLoginForm = props => { /> ); - if (emailLoginParamsPresent) { - return ( -
-
{t('login.completeLogin')}
- - - {emailLoginCompletionFailure && } - - -
- ) - } - return (
{!guestOnly && ( @@ -159,7 +139,7 @@ const EmailLoginForm = props => { queryToken: PropTypes.string, guestOnly: PropTypes.bool, sendAuthenticationEmail: PropTypes.func.isRequired, - completeEmailAuthentication: PropTypes.func.isRequired, + verifyOtpCode: PropTypes.func.isRequired, updateEmail: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, showCancel: PropTypes.bool.isRequired, @@ -167,8 +147,7 @@ const EmailLoginForm = props => { submitting: PropTypes.bool.isRequired, failure: PropTypes.bool.isRequired, emailSent: PropTypes.bool.isRequired, - emailLoginParamsPresent: PropTypes.bool.isRequired, - emailLoginCompletionFailure: PropTypes.bool.isRequired, + otpVerificationFailure: PropTypes.bool.isRequired, }; export default EmailLoginForm; diff --git a/src/components/LoginPage/OtpCodeForm.tsx b/src/components/LoginPage/OtpCodeForm.tsx new file mode 100644 index 00000000..64d62cd2 --- /dev/null +++ b/src/components/LoginPage/OtpCodeForm.tsx @@ -0,0 +1,198 @@ +import React, { useRef, useState, useCallback } from 'react'; +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; +import Button from '../Button'; +import Failure from './Failure'; + +const CODE_LENGTH = 6; + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; +`; + +const Instruction = styled.p` + margin: 0; + font-size: 0.95rem; + text-align: center; + color: #444; +`; + +const OtpInputsRow = styled.div` + display: flex; + gap: 0.5rem; +`; + +interface DigitInputProps { + $filled: boolean; +} + +const DigitInput = styled.input` + width: 2.8rem; + height: 3.2rem; + text-align: center; + font-size: 1.5rem; + font-weight: 700; + border: 2px solid ${({ $filled }) => ($filled ? '#aaa' : '#ddd')}; + border-radius: 8px; + outline: none; + caret-color: transparent; + background-color: ${({ $filled }) => ($filled ? '#f8f9fa' : '#fff')}; + transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease; + color: #222; + + &:focus { + border-color: ${({ theme }) => theme.colors.main}; + box-shadow: 0 0 0 3px ${({ theme }) => theme.colors.main}33; + background-color: #fff; + } + + @media (max-width: 360px) { + width: 2.2rem; + height: 2.8rem; + font-size: 1.2rem; + } +`; + +const Actions = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 0.75rem; + width: 100%; +`; + +interface OtpCodeFormProps { + email: string; + submitting: boolean; + failure: boolean; + onSubmit: (code: string) => void; +} + +const OtpCodeForm: React.FC = ({ email, submitting, failure, onSubmit }) => { + const { t } = useTranslation(); + const [digits, setDigits] = useState(Array(CODE_LENGTH).fill('')); + const inputRefs = useRef<(HTMLInputElement | null)[]>([]); + + const focusInput = useCallback((index: number) => { + const el = inputRefs.current[index]; + if (el) { + el.focus(); + el.select(); + } + }, []); + + const submitCode = useCallback((code: string) => { + if (code.length === CODE_LENGTH && !submitting) { + onSubmit(code); + } + }, [onSubmit, submitting]); + + const handleChange = useCallback((index: number, value: string) => { + const digit = value.replace(/\D/g, '').slice(-1); + setDigits(prev => { + const next = [...prev]; + next[index] = digit; + if (digit && index < CODE_LENGTH - 1) { + setTimeout(() => focusInput(index + 1), 0); + } + const code = next.join(''); + if (code.length === CODE_LENGTH && next.every(d => d !== '')) { + setTimeout(() => submitCode(code), 0); + } + return next; + }); + }, [focusInput, submitCode]); + + const handleKeyDown = useCallback((index: number, e: React.KeyboardEvent) => { + if (e.key === 'Backspace') { + e.preventDefault(); + if (digits[index]) { + setDigits(prev => { + const next = [...prev]; + next[index] = ''; + return next; + }); + } else if (index > 0) { + setDigits(prev => { + const next = [...prev]; + next[index - 1] = ''; + return next; + }); + focusInput(index - 1); + } + } else if (e.key === 'ArrowLeft' && index > 0) { + focusInput(index - 1); + } else if (e.key === 'ArrowRight' && index < CODE_LENGTH - 1) { + focusInput(index + 1); + } + }, [digits, focusInput]); + + const handlePaste = useCallback((e: React.ClipboardEvent) => { + e.preventDefault(); + const pasted = e.clipboardData.getData('text').replace(/\D/g, '').slice(0, CODE_LENGTH); + if (!pasted) return; + + const next = Array(CODE_LENGTH).fill(''); + for (let i = 0; i < pasted.length; i++) { + next[i] = pasted[i]; + } + setDigits(next); + + const focusIndex = Math.min(pasted.length, CODE_LENGTH - 1); + setTimeout(() => focusInput(focusIndex), 0); + + if (pasted.length === CODE_LENGTH) { + setTimeout(() => submitCode(pasted), 0); + } + }, [focusInput, submitCode]); + + const code = digits.join(''); + const isComplete = code.length === CODE_LENGTH && digits.every(d => d !== ''); + + return ( + + + {t('login.otpInstruction')} {email} + + + {digits.map((digit, index) => ( + { inputRefs.current[index] = el; }} + type="text" + inputMode="numeric" + pattern="[0-9]*" + maxLength={1} + value={digit} + autoComplete={index === 0 ? 'one-time-code' : 'off'} + autoFocus={index === 0} + disabled={submitting} + $filled={digit !== ''} + onChange={e => handleChange(index, e.target.value)} + onKeyDown={e => handleKeyDown(index, e)} + aria-label={`${t('login.otpDigitLabel')} ${index + 1}`} + data-cy={`otp-digit-${index}`} + /> + ))} + + {failure && } + + +
+); + +export default ErrorFallback; diff --git a/src/components/ErrorFallback/index.ts b/src/components/ErrorFallback/index.ts new file mode 100644 index 00000000..5f6c0e7f --- /dev/null +++ b/src/components/ErrorFallback/index.ts @@ -0,0 +1 @@ +export { default } from './ErrorFallback'; From 44df106b888904e0db1e6dadabed983a7cc14a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Thu, 19 Mar 2026 08:36:18 +0000 Subject: [PATCH 29/62] fix: handle auth channel creation failure gracefully Return a fallback channel with null instead of undefined when watchAuthState throws. This triggers the normal unauthenticated flow instead of crashing the entire saga tree in a restart loop. --- src/modules/auth/sagas.spec.ts | 22 +++++++++++++++++++++- src/modules/auth/sagas.ts | 5 ++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/modules/auth/sagas.spec.ts b/src/modules/auth/sagas.spec.ts index 23705f91..a9f68fdb 100644 --- a/src/modules/auth/sagas.spec.ts +++ b/src/modules/auth/sagas.spec.ts @@ -3,7 +3,7 @@ import * as actions from './actions'; import * as sagas from './sagas'; import {loadCredentialsToken, loadGuestToken, loadIpToken, loadKioskToken} from '../../util/auth'; import {expectDoneWithoutReturn, expectDoneWithReturn} from '../../../test/sagaUtils'; -import firebase, {authenticate as fbAuth, requestSignInCode as fbRequestSignInCode, verifyOtpCode as fbVerifyOtpCode, unauth as fbUnauth} from '../../util/firebase'; +import firebase, {authenticate as fbAuth, requestSignInCode as fbRequestSignInCode, verifyOtpCode as fbVerifyOtpCode, unauth as fbUnauth, watchAuthState} from '../../util/firebase'; import {get} from 'firebase/database'; jest.mock('../../util/firebase'); @@ -529,6 +529,26 @@ describe('modules', () => { }); }); + describe('createFbAuthenticationChannel', () => { + it('should return a channel when watchAuthState succeeds', () => { + (watchAuthState as jest.Mock).mockImplementation(() => {}); + const channel = sagas.createFbAuthenticationChannel(); + expect(channel).toBeDefined(); + expect(channel.take).toBeDefined(); + expect(channel.put).toBeDefined(); + }); + + it('should return a fallback channel with null when watchAuthState throws', async () => { + (watchAuthState as jest.Mock).mockImplementation(() => { + throw new Error('Firebase not initialized'); + }); + const channel = sagas.createFbAuthenticationChannel(); + expect(channel).toBeDefined(); + const value = await channel.take(); + expect(value).toBeNull(); + }); + }); + describe('doUsernamePasswordAuthentication', () => { it('should put failure action if exception is thrown', () => { const generator = sagas.doUsernamePasswordAuthentication( diff --git a/src/modules/auth/sagas.ts b/src/modules/auth/sagas.ts index b5588df8..f46a244f 100644 --- a/src/modules/auth/sagas.ts +++ b/src/modules/auth/sagas.ts @@ -225,13 +225,16 @@ function* monitorFirebaseAuthentication(channel: any) { } } -function createFbAuthenticationChannel() { +export function createFbAuthenticationChannel() { try { const channel = createChannel(); watchAuthState(channel.put); return channel; } catch(e) { logError('Failed to create authentication watch channel', e); + const channel = createChannel(); + channel.put(null); + return channel; } } From bab82e52e1f7e500a8365ce731d2539b289a51a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Thu, 19 Mar 2026 08:36:51 +0000 Subject: [PATCH 30/62] fix: guard against service worker reload loops on iOS Add a 5-second cooldown between SW controller-change reloads using sessionStorage. Prevents infinite reload loops when iOS kills and resumes a PWA process, triggering controllerchange during init. --- src/app.tsx | 4 +- .../shouldReloadOnControllerChange.spec.ts | 41 +++++++++++++++++++ src/util/shouldReloadOnControllerChange.ts | 19 +++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/util/shouldReloadOnControllerChange.spec.ts create mode 100644 src/util/shouldReloadOnControllerChange.ts diff --git a/src/app.tsx b/src/app.tsx index 3dc9c39d..533fdf89 100755 --- a/src/app.tsx +++ b/src/app.tsx @@ -25,6 +25,7 @@ import * as Sentry from "@sentry/react"; import ErrorFallback from './components/ErrorFallback'; import { getMidnightDelayMs } from './util/getMidnightDelay'; +import { shouldReloadOnControllerChange, markReload } from './util/shouldReloadOnControllerChange'; Sentry.init({ dsn: "https://8a606d82aa68850021fbfac2ffda30b5@o4509293310967808.ingest.de.sentry.io/4509293314113617", @@ -73,7 +74,8 @@ if (!__DEV__ && 'serviceWorker' in navigator) { let hasController = !!navigator.serviceWorker.controller; navigator.serviceWorker.addEventListener('controllerchange', () => { - if (hasController) { + if (hasController && shouldReloadOnControllerChange()) { + markReload(); window.location.reload(); } hasController = true; diff --git a/src/util/shouldReloadOnControllerChange.spec.ts b/src/util/shouldReloadOnControllerChange.spec.ts new file mode 100644 index 00000000..1c54ed3d --- /dev/null +++ b/src/util/shouldReloadOnControllerChange.spec.ts @@ -0,0 +1,41 @@ +import { shouldReloadOnControllerChange, markReload } from './shouldReloadOnControllerChange'; + +describe('shouldReloadOnControllerChange', () => { + beforeEach(() => { + sessionStorage.clear(); + }); + + it('should return true when no previous reload timestamp', () => { + expect(shouldReloadOnControllerChange()).toBe(true); + }); + + it('should return false when last reload was < 5s ago', () => { + sessionStorage.setItem('sw-reload-ts', String(Date.now())); + expect(shouldReloadOnControllerChange()).toBe(false); + }); + + it('should return true when last reload was > 5s ago', () => { + sessionStorage.setItem('sw-reload-ts', String(Date.now() - 6000)); + expect(shouldReloadOnControllerChange()).toBe(true); + }); + + it('should return true when sessionStorage throws', () => { + const originalGetItem = sessionStorage.getItem; + sessionStorage.getItem = () => { throw new Error('denied'); }; + expect(shouldReloadOnControllerChange()).toBe(true); + sessionStorage.getItem = originalGetItem; + }); +}); + +describe('markReload', () => { + beforeEach(() => { + sessionStorage.clear(); + }); + + it('should store timestamp in sessionStorage', () => { + markReload(); + const stored = Number(sessionStorage.getItem('sw-reload-ts')); + expect(stored).toBeGreaterThan(0); + expect(Date.now() - stored).toBeLessThan(1000); + }); +}); diff --git a/src/util/shouldReloadOnControllerChange.ts b/src/util/shouldReloadOnControllerChange.ts new file mode 100644 index 00000000..45e2b2f5 --- /dev/null +++ b/src/util/shouldReloadOnControllerChange.ts @@ -0,0 +1,19 @@ +const RELOAD_COOLDOWN_MS = 5000; +const STORAGE_KEY = 'sw-reload-ts'; + +export function shouldReloadOnControllerChange(): boolean { + try { + const lastReload = Number(sessionStorage.getItem(STORAGE_KEY) || 0); + return Date.now() - lastReload > RELOAD_COOLDOWN_MS; + } catch { + return true; + } +} + +export function markReload(): void { + try { + sessionStorage.setItem(STORAGE_KEY, String(Date.now())); + } catch { + // ignore + } +} From 35a52a8a838be5f5d535ab78999bf2194b9648b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Thu, 19 Mar 2026 10:39:44 +0100 Subject: [PATCH 31/62] fix: prevent OTP input focus ring from overflowing viewport Replace box-shadow with outline for the focus indicator. Unlike box-shadow, outline never causes overflow or scrolling, fixing the left viewport overflow on mobile Safari and PWA. --- src/components/LoginPage/OtpCodeForm.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/LoginPage/OtpCodeForm.tsx b/src/components/LoginPage/OtpCodeForm.tsx index d4ef4d28..1770dc17 100644 --- a/src/components/LoginPage/OtpCodeForm.tsx +++ b/src/components/LoginPage/OtpCodeForm.tsx @@ -41,12 +41,13 @@ const DigitInput = styled.input` outline: none; caret-color: transparent; background-color: ${({ $filled }) => ($filled ? '#f8f9fa' : '#fff')}; - transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease; + transition: border-color 0.15s ease, background-color 0.15s ease; color: #222; &:focus { border-color: ${({ theme }) => theme.colors.main}; - box-shadow: 0 0 0 3px ${({ theme }) => theme.colors.main}33; + outline: 3px solid ${({ theme }) => theme.colors.main}33; + outline-offset: -1px; background-color: #fff; } From a2cc65ea848504192d88cbf5485ca0de67a9bcdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Fri, 20 Mar 2026 08:36:11 +0100 Subject: [PATCH 32/62] chore: update GitHub Actions to v4 and remove duplication Update checkout and setup-node actions from v2/v3 to v4 in deployment and test workflows. Remove redundant second setup-node step in build_and_deploy jobs. --- .github/workflows/firebase-hosting-dev.yml | 7 ++----- .github/workflows/firebase-hosting-prod.yml | 7 ++----- .github/workflows/test-pull-request.yml | 4 ++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/firebase-hosting-dev.yml b/.github/workflows/firebase-hosting-dev.yml index 6a9b873b..7b93e750 100644 --- a/.github/workflows/firebase-hosting-dev.yml +++ b/.github/workflows/firebase-hosting-dev.yml @@ -17,15 +17,12 @@ jobs: environment: ${{ matrix.environment }} steps: - run: echo 'Running deplyoment for project ${{ vars.PROJECT }}' - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npm run build --project=${{ vars.PROJECT }} - - uses: actions/setup-node@v4 - with: - node-version: 22 - uses: w9jds/setup-firebase@main with: project_id: ${{ vars.FIREBASE_PROJECT }} diff --git a/.github/workflows/firebase-hosting-prod.yml b/.github/workflows/firebase-hosting-prod.yml index 553c6ae9..6ff17b1f 100644 --- a/.github/workflows/firebase-hosting-prod.yml +++ b/.github/workflows/firebase-hosting-prod.yml @@ -17,15 +17,12 @@ jobs: environment: ${{ matrix.environment }} steps: - run: echo 'Running deplyoment for project ${{ vars.PROJECT }}' - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npm run build:prod --project=${{ vars.PROJECT }} - - uses: actions/setup-node@v4 - with: - node-version: 22 - uses: w9jds/setup-firebase@main with: project_id: ${{ vars.FIREBASE_PROJECT }} diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 9d87eb97..fa28ce7f 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -5,8 +5,8 @@ jobs: if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci From 9a1004701bdf0af7a7b41f1f4b0b27be479e0a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Fri, 20 Mar 2026 12:16:53 +0000 Subject: [PATCH 33/62] feat: add privacy policy link for DSGVO compliance Display a configurable privacy policy link on the login page and in the authenticated layout footer. URL is loaded in real-time from Firebase RTDB at /settings/privacyPolicyUrl. Only http(s) URLs are rendered. Firebase rules grant public read access so the link is visible before authentication. --- firebase-rules-template.json | 5 ++ src/components/App/App.tsx | 2 +- src/components/LoginPage/LoginPage.tsx | 17 +++++- .../VerticalHeaderLayout.tsx | 25 +++++++++ src/containers/LoginPageContainer.tsx | 9 ++++ src/locales/de.json | 3 ++ src/modules/settings/index.ts | 3 ++ .../settings/privacyPolicyUrl/actions.ts | 13 +++++ .../settings/privacyPolicyUrl/index.ts | 6 +++ .../settings/privacyPolicyUrl/reducer.spec.ts | 53 +++++++++++++++++++ .../settings/privacyPolicyUrl/reducer.ts | 25 +++++++++ .../settings/privacyPolicyUrl/sagas.spec.ts | 43 +++++++++++++++ .../settings/privacyPolicyUrl/sagas.ts | 19 +++++++ 13 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 src/containers/LoginPageContainer.tsx create mode 100644 src/modules/settings/privacyPolicyUrl/actions.ts create mode 100644 src/modules/settings/privacyPolicyUrl/index.ts create mode 100644 src/modules/settings/privacyPolicyUrl/reducer.spec.ts create mode 100644 src/modules/settings/privacyPolicyUrl/reducer.ts create mode 100644 src/modules/settings/privacyPolicyUrl/sagas.spec.ts create mode 100644 src/modules/settings/privacyPolicyUrl/sagas.ts diff --git a/firebase-rules-template.json b/firebase-rules-template.json index 399b7920..1669168d 100644 --- a/firebase-rules-template.json +++ b/firebase-rules-template.json @@ -289,6 +289,11 @@ ".read": "auth !== null && root.child('admins/' + auth.uid).exists()", ".write": "auth !== null && root.child('admins/' + auth.uid).exists()" }, + "privacyPolicyUrl": { + ".read": true, + ".write": "auth !== null && root.child('admins/' + auth.uid).exists()", + ".validate": "newData.isString()" + }, "$other": { ".validate": false } diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 668787af..b54362bf 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { withTranslation } from 'react-i18next'; import {Redirect, Route, Switch} from 'react-router-dom'; -import LoginPage from '../../components/LoginPage'; +import LoginPage from '../../containers/LoginPageContainer'; import Centered from '../Centered'; import MaterialIcon from '../MaterialIcon'; import MessagePage from "../../containers/MessagePageContainer"; diff --git a/src/components/LoginPage/LoginPage.tsx b/src/components/LoginPage/LoginPage.tsx index eec4c0ae..ef1f8595 100644 --- a/src/components/LoginPage/LoginPage.tsx +++ b/src/components/LoginPage/LoginPage.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import Header from './Header'; import EmailLoginForm from '../../containers/EmailLoginFormContainer' import UsernamePasswordLoginForm from '../../containers/UsernamePasswordLoginFormContainer' @@ -43,7 +44,16 @@ const LoginInnerWrapper = styled.div` } ` -const LoginPage = ({location}) => { +const PrivacyLink = styled.a` + display: block; + margin-top: 2em; + font-size: 0.85em; + color: #666; + text-decoration: underline; +` + +const LoginPage = ({location, privacyPolicyUrl}: {location: any, privacyPolicyUrl?: string | null}) => { + const { t } = useTranslation(); const queryToken = getAuthQueryToken(location) const guestOnly = getGuestOnly(location) return ( @@ -55,6 +65,11 @@ const LoginPage = ({location}) => { ? : } + {privacyPolicyUrl && /^https?:\/\//.test(privacyPolicyUrl) && ( + + {t('legal.privacyPolicy')} + + )} diff --git a/src/components/VerticalHeaderLayout/VerticalHeaderLayout.tsx b/src/components/VerticalHeaderLayout/VerticalHeaderLayout.tsx index 3bce9773..2af4ae45 100644 --- a/src/components/VerticalHeaderLayout/VerticalHeaderLayout.tsx +++ b/src/components/VerticalHeaderLayout/VerticalHeaderLayout.tsx @@ -1,12 +1,37 @@ import React from 'react'; +import { useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; import Header from './Header'; import Content from './Content'; +import { RootState } from '../../modules'; + +const Footer = styled.footer` + padding: 1em; + text-align: center; +` + +const FooterLink = styled.a` + font-size: 0.85em; + color: #666; + text-decoration: underline; +` function VerticalHeaderLayout({ children }: { children: React.ReactNode }) { + const { t } = useTranslation(); + const privacyPolicyUrl = useSelector((state: RootState) => state.settings.privacyPolicyUrl.url); + return (
{children} + {privacyPolicyUrl && /^https?:\/\//.test(privacyPolicyUrl) && ( +
+ + {t('legal.privacyPolicy')} + +
+ )}
); } diff --git a/src/containers/LoginPageContainer.tsx b/src/containers/LoginPageContainer.tsx new file mode 100644 index 00000000..4a120c46 --- /dev/null +++ b/src/containers/LoginPageContainer.tsx @@ -0,0 +1,9 @@ +import {connect} from 'react-redux'; +import LoginPage from '../components/LoginPage'; +import {RootState} from '../modules'; + +const mapStateToProps = (state: RootState) => ({ + privacyPolicyUrl: state.settings.privacyPolicyUrl.url, +}); + +export default connect(mapStateToProps)(LoginPage); diff --git a/src/locales/de.json b/src/locales/de.json index b882efef..c89b8b28 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -521,6 +521,9 @@ "macosInstructionsPost": " und wählen Sie \"Zum Dock hinzufügen\".", "dismiss": "Nicht jetzt" }, + "legal": { + "privacyPolicy": "Datenschutzerklärung" + }, "maskedInput": { "tooltip": "Um dieses Feld zu bearbeiten, leeren Sie es und setzen Sie anschliessend den gewünschten Wert." }, diff --git a/src/modules/settings/index.ts b/src/modules/settings/index.ts index 00258b00..657f884d 100644 --- a/src/modules/settings/index.ts +++ b/src/modules/settings/index.ts @@ -7,6 +7,7 @@ import lockDate, {sagas as lockDateSagas} from './lockDate'; import guestAccessToken, {sagas as guestAccessTokenSagas} from './guestAccessToken'; import kioskAccessToken, {sagas as kioskAccessTokenSagas} from './kioskAccessToken'; import invoiceRecipients, {sagas as invoiceRecipientsSagas} from './invoiceRecipients'; +import privacyPolicyUrl, {sagas as privacyPolicyUrlSagas} from './privacyPolicyUrl'; const reducer = combineReducers({ aerodromeStatus, @@ -15,6 +16,7 @@ const reducer = combineReducers({ guestAccessToken, kioskAccessToken, invoiceRecipients, + privacyPolicyUrl, }); export function* sagas() { @@ -25,6 +27,7 @@ export function* sagas() { fork(guestAccessTokenSagas), fork(kioskAccessTokenSagas), fork(invoiceRecipientsSagas), + fork(privacyPolicyUrlSagas), ]) } diff --git a/src/modules/settings/privacyPolicyUrl/actions.ts b/src/modules/settings/privacyPolicyUrl/actions.ts new file mode 100644 index 00000000..10ae8334 --- /dev/null +++ b/src/modules/settings/privacyPolicyUrl/actions.ts @@ -0,0 +1,13 @@ +export const PRIVACY_POLICY_URL_LOADED = 'PRIVACY_POLICY_URL_LOADED' as const; + +export type PrivacyPolicyUrlAction = + | { type: typeof PRIVACY_POLICY_URL_LOADED; payload: { url: string | null } }; + +export function privacyPolicyUrlLoaded(url: string | null) { + return { + type: PRIVACY_POLICY_URL_LOADED, + payload: { + url + } + }; +} diff --git a/src/modules/settings/privacyPolicyUrl/index.ts b/src/modules/settings/privacyPolicyUrl/index.ts new file mode 100644 index 00000000..229aa725 --- /dev/null +++ b/src/modules/settings/privacyPolicyUrl/index.ts @@ -0,0 +1,6 @@ +import reducer from './reducer'; +import sagas from './sagas'; + +export { sagas }; + +export default reducer; diff --git a/src/modules/settings/privacyPolicyUrl/reducer.spec.ts b/src/modules/settings/privacyPolicyUrl/reducer.spec.ts new file mode 100644 index 00000000..bff49bea --- /dev/null +++ b/src/modules/settings/privacyPolicyUrl/reducer.spec.ts @@ -0,0 +1,53 @@ +import reducer from './reducer'; +import * as actions from './actions'; + +const INITIAL_STATE = { + url: null, +}; + +describe('modules', () => { + describe('settings', () => { + describe('privacyPolicyUrl', () => { + describe('reducer', () => { + it('should handle initial state', () => { + expect( + reducer(undefined, {} as any) + ).toEqual(INITIAL_STATE); + }); + + describe('PRIVACY_POLICY_URL_LOADED', () => { + it('should set the url', () => { + const url = 'https://example.com/privacy'; + expect( + reducer({ + url: null, + }, actions.privacyPolicyUrlLoaded(url)) + ).toEqual({ + url, + }); + }); + + it('should override an existing url', () => { + expect( + reducer({ + url: 'https://old.example.com/privacy', + }, actions.privacyPolicyUrlLoaded('https://new.example.com/privacy')) + ).toEqual({ + url: 'https://new.example.com/privacy', + }); + }); + + it('should set the url to null', () => { + expect( + reducer({ + url: 'https://example.com/privacy', + }, actions.privacyPolicyUrlLoaded(null)) + ).toEqual({ + url: null, + }); + }); + }); + }); + }); + }); +}); diff --git a/src/modules/settings/privacyPolicyUrl/reducer.ts b/src/modules/settings/privacyPolicyUrl/reducer.ts new file mode 100644 index 00000000..b8db77f2 --- /dev/null +++ b/src/modules/settings/privacyPolicyUrl/reducer.ts @@ -0,0 +1,25 @@ +import * as actions from './actions'; +import { PrivacyPolicyUrlAction } from './actions'; +import reducer from '../../../util/reducer'; + +interface PrivacyPolicyUrlState { + url: string | null; +} + +function urlLoaded(state: PrivacyPolicyUrlState, action: PrivacyPolicyUrlAction & { type: typeof actions.PRIVACY_POLICY_URL_LOADED }) { + return { + ...state, + url: action.payload.url + } +} + +const ACTION_HANDLERS = { + [actions.PRIVACY_POLICY_URL_LOADED]: urlLoaded, +}; + +const INITIAL_STATE: PrivacyPolicyUrlState = { + url: null, +}; + +export type { PrivacyPolicyUrlState }; +export default reducer(INITIAL_STATE, ACTION_HANDLERS); diff --git a/src/modules/settings/privacyPolicyUrl/sagas.spec.ts b/src/modules/settings/privacyPolicyUrl/sagas.spec.ts new file mode 100644 index 00000000..f51600e6 --- /dev/null +++ b/src/modules/settings/privacyPolicyUrl/sagas.spec.ts @@ -0,0 +1,43 @@ +import * as actions from './actions'; +import * as sagas from './sagas'; +import firebase from '../../../util/firebase'; +import {onValue} from 'firebase/database'; +import FakeFirebaseSnapshot from '../../../../test/FakeFirebaseSnapshot'; + +jest.mock('../../../util/firebase'); +jest.mock('firebase/database', () => ({ + onValue: jest.fn(), +})); + +describe('modules', () => { + describe('settings', () => { + describe('privacyPolicyUrl', () => { + describe('sagas', () => { + beforeEach(() => { + jest.clearAllMocks(); + (firebase as jest.Mock).mockReturnValue({}); + }); + + describe('loadPrivacyPolicyUrl', () => { + it('should register onValue listener', () => { + const channel = {put: jest.fn()}; + sagas.loadPrivacyPolicyUrl(channel); + expect(onValue).toHaveBeenCalledWith(expect.anything(), expect.any(Function)); + }); + + it('should put privacyPolicyUrlLoaded when firebase value changes', () => { + const channel = {put: jest.fn()}; + sagas.loadPrivacyPolicyUrl(channel); + + const callback = (onValue as jest.Mock).mock.calls[0][1]; + const url = 'https://example.com/privacy'; + const snapshot = new FakeFirebaseSnapshot('privacyPolicyUrl', url); + callback(snapshot); + + expect(channel.put).toHaveBeenCalledWith(actions.privacyPolicyUrlLoaded(url)); + }); + }); + }); + }); + }); +}); diff --git a/src/modules/settings/privacyPolicyUrl/sagas.ts b/src/modules/settings/privacyPolicyUrl/sagas.ts new file mode 100644 index 00000000..bf0d8f91 --- /dev/null +++ b/src/modules/settings/privacyPolicyUrl/sagas.ts @@ -0,0 +1,19 @@ +import {all, fork} from 'redux-saga/effects'; +import {onValue} from 'firebase/database'; +import * as actions from './actions'; +import createChannel, {monitor} from '../../../util/createChannel'; +import firebase from '../../../util/firebase'; + +export function loadPrivacyPolicyUrl(channel: any) { + onValue(firebase('/settings/privacyPolicyUrl'), (snapshot) => { + channel.put(actions.privacyPolicyUrlLoaded(snapshot.val())); + }); +} + +export default function* sagas() { + const channel = createChannel(); + yield all([ + fork(monitor, channel), + fork(loadPrivacyPolicyUrl, channel), + ]) +} From efdd6ada326003d3fb3b97f0a7961602ae6dd3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Fri, 20 Mar 2026 14:18:19 +0000 Subject: [PATCH 34/62] feat: add scheduled data retention cleanup jobs Add two Cloud Functions for DSGVO-compliant data retention: - scheduledAnonymizeMovements: strips PII from movements older than the configured retention period, preserving statistical data for BAZL reporting - scheduledCleanupMessages: deletes contact messages older than the configured retention period Both functions are opt-in: they read retention days from Firebase settings and skip entirely if no value is configured. Guard existing onWrite triggers (associated movements, enrich) against anonymized records to prevent cascading writes. --- firebase-rules-template.json | 10 ++ functions/anonymizeMovements.js | 94 ++++++++++++ functions/anonymizeMovements.spec.js | 137 ++++++++++++++++++ .../setAssociatedMovementsTriggers.js | 3 + functions/cleanupMessages.js | 50 +++++++ functions/cleanupMessages.spec.js | 109 ++++++++++++++ functions/enrichMovements.js | 4 + functions/index.js | 5 + 8 files changed, 412 insertions(+) create mode 100644 functions/anonymizeMovements.js create mode 100644 functions/anonymizeMovements.spec.js create mode 100644 functions/cleanupMessages.js create mode 100644 functions/cleanupMessages.spec.js diff --git a/firebase-rules-template.json b/firebase-rules-template.json index 1669168d..50a33a31 100644 --- a/firebase-rules-template.json +++ b/firebase-rules-template.json @@ -294,6 +294,16 @@ ".write": "auth !== null && root.child('admins/' + auth.uid).exists()", ".validate": "newData.isString()" }, + "movementRetentionDays": { + ".read": "auth !== null && root.child('admins/' + auth.uid).exists()", + ".write": "auth !== null && root.child('admins/' + auth.uid).exists()", + ".validate": "newData.isNumber() && newData.val() > 0" + }, + "messageRetentionDays": { + ".read": "auth !== null && root.child('admins/' + auth.uid).exists()", + ".write": "auth !== null && root.child('admins/' + auth.uid).exists()", + ".validate": "newData.isNumber() && newData.val() > 0" + }, "$other": { ".validate": false } diff --git a/functions/anonymizeMovements.js b/functions/anonymizeMovements.js new file mode 100644 index 00000000..a57bd600 --- /dev/null +++ b/functions/anonymizeMovements.js @@ -0,0 +1,94 @@ +'use strict'; + +const functions = require('firebase-functions'); +const admin = require('firebase-admin'); + +const SCHEDULE = '0 2 * * *'; // Every day at 2 AM +const TIMEZONE = 'Europe/Zurich'; + +const PII_FIELDS = [ + 'firstname', + 'lastname', + 'email', + 'phone', + 'memberNr', + 'immatriculation', + 'aircraftType', + 'mtow', + 'remarks', + 'carriageVoucher', + 'createdBy', + 'createdBy_orderKey', + 'customsFormId', + 'customsFormUrl', +]; + +function getAnonymizationUpdates(snapshot, cutoffIso) { + const updates = {}; + + snapshot.forEach(child => { + const val = child.val(); + + if (val.dateTime < cutoffIso && !val.anonymized) { + PII_FIELDS.forEach(field => { + if (val[field] !== undefined) { + updates[`${child.key}/${field}`] = null; + } + }); + updates[`${child.key}/anonymized`] = true; + } + }); + + return updates; +} + +exports.scheduledAnonymizeMovements = functions + .region('europe-west1') + .pubsub + .schedule(SCHEDULE) + .timeZone(TIMEZONE) + .onRun(async () => { + const db = admin.database(); + + const retentionSnap = await db.ref('/settings/movementRetentionDays').once('value'); + const retentionDays = retentionSnap.val(); + + if (!retentionDays) { + console.log('No movementRetentionDays configured, skipping'); + return; + } + + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - retentionDays); + const cutoffIso = cutoff.toISOString(); + + console.log(`Anonymizing movements older than ${cutoffIso} (retention: ${retentionDays} days)`); + + const departuresSnap = await db.ref('/departures') + .orderByChild('dateTime') + .endAt(cutoffIso) + .once('value'); + + const departureUpdates = getAnonymizationUpdates(departuresSnap, cutoffIso); + + if (Object.keys(departureUpdates).length > 0) { + await db.ref('/departures').update(departureUpdates); + console.log(`Anonymized ${Object.keys(departureUpdates).filter(k => k.endsWith('/anonymized')).length} departures`); + } + + const arrivalsSnap = await db.ref('/arrivals') + .orderByChild('dateTime') + .endAt(cutoffIso) + .once('value'); + + const arrivalUpdates = getAnonymizationUpdates(arrivalsSnap, cutoffIso); + + if (Object.keys(arrivalUpdates).length > 0) { + await db.ref('/arrivals').update(arrivalUpdates); + console.log(`Anonymized ${Object.keys(arrivalUpdates).filter(k => k.endsWith('/anonymized')).length} arrivals`); + } + + if (Object.keys(departureUpdates).length === 0 && Object.keys(arrivalUpdates).length === 0) { + console.log('No movements to anonymize'); + } + }); diff --git a/functions/anonymizeMovements.spec.js b/functions/anonymizeMovements.spec.js new file mode 100644 index 00000000..81d14ad5 --- /dev/null +++ b/functions/anonymizeMovements.spec.js @@ -0,0 +1,137 @@ +'use strict'; + +let mockRefData = {}; +let mockCurrentRefPath = ''; + +const mockUpdate = jest.fn().mockResolvedValue(); +const mockOnce = jest.fn().mockImplementation(() => { + return Promise.resolve(mockRefData[mockCurrentRefPath] || { forEach: () => {} }); +}); +const mockEndAt = jest.fn().mockReturnValue({ once: mockOnce }); +const mockOrderByChild = jest.fn().mockReturnValue({ endAt: mockEndAt }); +const mockRef = jest.fn().mockImplementation(path => { + mockCurrentRefPath = path; + return { orderByChild: mockOrderByChild, update: mockUpdate, once: mockOnce }; +}); + +jest.mock('firebase-admin', () => ({ + database: jest.fn().mockReturnValue({ ref: mockRef }), + initializeApp: jest.fn(), +})); + +jest.mock('firebase-functions', () => ({ + region: jest.fn().mockReturnThis(), + pubsub: { + schedule: jest.fn().mockReturnValue({ + timeZone: jest.fn().mockReturnValue({ + onRun: jest.fn(fn => fn), + }), + }), + }, +})); + +function createSnapshot(data) { + const entries = Object.entries(data); + return { + forEach(cb) { + entries.forEach(([key, val]) => { + cb({ key, val: () => val }); + }); + }, + }; +} + +function createValueSnapshot(val) { + return { val: () => val }; +} + +describe('anonymizeMovements', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + mockRefData = {}; + mockCurrentRefPath = ''; + }); + + it('should skip when movementRetentionDays is not set', async () => { + mockRefData['/settings/movementRetentionDays'] = createValueSnapshot(null); + + const { scheduledAnonymizeMovements } = require('./anonymizeMovements'); + await scheduledAnonymizeMovements(); + + expect(mockOrderByChild).not.toHaveBeenCalled(); + expect(mockUpdate).not.toHaveBeenCalled(); + }); + + it('should anonymize movements older than configured retention', async () => { + mockRefData['/settings/movementRetentionDays'] = createValueSnapshot(730); + + const oldDate = '2023-01-15T10:00:00.000Z'; + const movementSnapshot = createSnapshot({ + 'mov1': { + dateTime: oldDate, + firstname: 'Hans', + lastname: 'Muster', + email: 'hans@example.com', + phone: '+41791234567', + memberNr: '123', + immatriculation: 'HB-ABC', + aircraftType: 'C172', + mtow: 1111, + remarks: 'test', + createdBy: 'uid1', + createdBy_orderKey: 'uid1_123', + location: 'LSZT', + flightType: 'private', + negativeTimestamp: -1673776800000, + }, + }); + + mockRefData['/departures'] = movementSnapshot; + mockRefData['/arrivals'] = movementSnapshot; + + const { scheduledAnonymizeMovements } = require('./anonymizeMovements'); + await scheduledAnonymizeMovements(); + + expect(mockUpdate).toHaveBeenCalled(); + + const updates = mockUpdate.mock.calls[0][0]; + + expect(updates['mov1/firstname']).toBeNull(); + expect(updates['mov1/lastname']).toBeNull(); + expect(updates['mov1/email']).toBeNull(); + expect(updates['mov1/phone']).toBeNull(); + expect(updates['mov1/memberNr']).toBeNull(); + expect(updates['mov1/immatriculation']).toBeNull(); + expect(updates['mov1/aircraftType']).toBeNull(); + expect(updates['mov1/mtow']).toBeNull(); + expect(updates['mov1/remarks']).toBeNull(); + expect(updates['mov1/createdBy']).toBeNull(); + expect(updates['mov1/createdBy_orderKey']).toBeNull(); + expect(updates['mov1/anonymized']).toBe(true); + + expect(updates['mov1/location']).toBeUndefined(); + expect(updates['mov1/flightType']).toBeUndefined(); + expect(updates['mov1/dateTime']).toBeUndefined(); + }); + + it('should skip already anonymized movements', async () => { + mockRefData['/settings/movementRetentionDays'] = createValueSnapshot(730); + + const snapshot = createSnapshot({ + 'mov1': { + dateTime: '2023-01-15T10:00:00.000Z', + anonymized: true, + location: 'LSZT', + }, + }); + + mockRefData['/departures'] = snapshot; + mockRefData['/arrivals'] = snapshot; + + const { scheduledAnonymizeMovements } = require('./anonymizeMovements'); + await scheduledAnonymizeMovements(); + + expect(mockUpdate).not.toHaveBeenCalled(); + }); +}); diff --git a/functions/associatedMovements/setAssociatedMovementsTriggers.js b/functions/associatedMovements/setAssociatedMovementsTriggers.js index 7fbdc82b..0765632c 100644 --- a/functions/associatedMovements/setAssociatedMovementsTriggers.js +++ b/functions/associatedMovements/setAssociatedMovementsTriggers.js @@ -132,6 +132,9 @@ const updateOnWrite = async (change, type) => { // — those are handled by the dedicated onCreate and onDelete triggers. if (!change.before.val() || !change.after.val()) return + // Skip anonymized movements (PII has been stripped by the retention job) + if (change.after.val().anonymized) return + const snap = change.after functions.logger.log(`Setting associated movement for updated ${type} ${snap.ref.key}`) diff --git a/functions/cleanupMessages.js b/functions/cleanupMessages.js new file mode 100644 index 00000000..2fab43ba --- /dev/null +++ b/functions/cleanupMessages.js @@ -0,0 +1,50 @@ +'use strict'; + +const functions = require('firebase-functions'); +const admin = require('firebase-admin'); + +const SCHEDULE = '0 2 * * *'; // Every day at 2 AM +const TIMEZONE = 'Europe/Zurich'; + +exports.scheduledCleanupMessages = functions + .region('europe-west1') + .pubsub + .schedule(SCHEDULE) + .timeZone(TIMEZONE) + .onRun(async () => { + const db = admin.database(); + + const retentionSnap = await db.ref('/settings/messageRetentionDays').once('value'); + const retentionDays = retentionSnap.val(); + + if (!retentionDays) { + console.log('No messageRetentionDays configured, skipping'); + return; + } + + const cutoff = Date.now() - (retentionDays * 24 * 60 * 60 * 1000); + console.log(`Deleting messages older than ${new Date(cutoff).toISOString()} (retention: ${retentionDays} days)`); + + const snapshot = await db.ref('/messages').once('value'); + + if (!snapshot.exists()) { + console.log('No messages to delete'); + return; + } + + const updates = {}; + + snapshot.forEach(child => { + const val = child.val(); + if (val.timestamp <= cutoff) { + updates[child.key] = null; + } + }); + + const count = Object.keys(updates).length; + + if (count > 0) { + await db.ref('/messages').update(updates); + console.log(`Deleted ${count} messages`); + } + }); diff --git a/functions/cleanupMessages.spec.js b/functions/cleanupMessages.spec.js new file mode 100644 index 00000000..81dff1da --- /dev/null +++ b/functions/cleanupMessages.spec.js @@ -0,0 +1,109 @@ +'use strict'; + +let mockRefData = {}; +let mockCurrentRefPath = ''; + +const mockUpdate = jest.fn().mockResolvedValue(); +const mockOnce = jest.fn().mockImplementation(() => { + return Promise.resolve(mockRefData[mockCurrentRefPath] || { exists: () => false, forEach: () => {} }); +}); +const mockRef = jest.fn().mockImplementation(path => { + mockCurrentRefPath = path; + return { once: mockOnce, update: mockUpdate }; +}); + +jest.mock('firebase-admin', () => ({ + database: jest.fn().mockReturnValue({ ref: mockRef }), + initializeApp: jest.fn(), +})); + +jest.mock('firebase-functions', () => ({ + region: jest.fn().mockReturnThis(), + pubsub: { + schedule: jest.fn().mockReturnValue({ + timeZone: jest.fn().mockReturnValue({ + onRun: jest.fn(fn => fn), + }), + }), + }, +})); + +function createSnapshot(data) { + const entries = Object.entries(data); + return { + exists: () => entries.length > 0, + forEach(cb) { + entries.forEach(([key, val]) => { + cb({ key, val: () => val }); + }); + }, + }; +} + +function createValueSnapshot(val) { + return { val: () => val }; +} + +describe('cleanupMessages', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + mockRefData = {}; + mockCurrentRefPath = ''; + }); + + it('should skip when messageRetentionDays is not set', async () => { + mockRefData['/settings/messageRetentionDays'] = createValueSnapshot(null); + + const { scheduledCleanupMessages } = require('./cleanupMessages'); + await scheduledCleanupMessages(); + + expect(mockRef).not.toHaveBeenCalledWith('/messages'); + expect(mockUpdate).not.toHaveBeenCalled(); + }); + + it('should delete messages older than configured retention', async () => { + mockRefData['/settings/messageRetentionDays'] = createValueSnapshot(90); + + const oldTimestamp = Date.now() - (91 * 24 * 60 * 60 * 1000); + const recentTimestamp = Date.now() - (10 * 24 * 60 * 60 * 1000); + + mockRefData['/messages'] = createSnapshot({ + 'msg1': { name: 'Old', timestamp: oldTimestamp }, + 'msg2': { name: 'Recent', timestamp: recentTimestamp }, + }); + + const { scheduledCleanupMessages } = require('./cleanupMessages'); + await scheduledCleanupMessages(); + + expect(mockUpdate).toHaveBeenCalled(); + + const updates = mockUpdate.mock.calls[0][0]; + expect(updates['msg1']).toBeNull(); + expect(updates['msg2']).toBeUndefined(); + }); + + it('should not call update when no messages exist', async () => { + mockRefData['/settings/messageRetentionDays'] = createValueSnapshot(90); + mockRefData['/messages'] = { exists: () => false, forEach: () => {} }; + + const { scheduledCleanupMessages } = require('./cleanupMessages'); + await scheduledCleanupMessages(); + + expect(mockUpdate).not.toHaveBeenCalled(); + }); + + it('should not call update when all messages are recent', async () => { + mockRefData['/settings/messageRetentionDays'] = createValueSnapshot(90); + + const recentTimestamp = Date.now() - (10 * 24 * 60 * 60 * 1000); + mockRefData['/messages'] = createSnapshot({ + 'msg1': { name: 'Recent', timestamp: recentTimestamp }, + }); + + const { scheduledCleanupMessages } = require('./cleanupMessages'); + await scheduledCleanupMessages(); + + expect(mockUpdate).not.toHaveBeenCalled(); + }); +}); diff --git a/functions/enrichMovements.js b/functions/enrichMovements.js index f6490304..dbb9f5d2 100644 --- a/functions/enrichMovements.js +++ b/functions/enrichMovements.js @@ -99,6 +99,10 @@ const handleUpdate = (change, movementType) => { if (!change.before.exists() || !change.after.exists()) { return null; } + // Skip anonymized movements (PII has been stripped by the retention job) + if (change.after.val().anonymized) { + return null; + } return enrichOnUpdate(change, movementType); }; diff --git a/functions/index.js b/functions/index.js index 75757238..4330f692 100644 --- a/functions/index.js +++ b/functions/index.js @@ -27,6 +27,8 @@ const webhook = require('./webhook'); const associatedMovementsTriggers = require('./associatedMovements/setAssociatedMovementsTriggers'); const invoiceRecipientsTrigger = require('./invoiceRecipients/invoiceRecipientsTrigger'); const updateArrivalPaymentStatus = require('./updateArrivalPaymentStatus'); +const { scheduledAnonymizeMovements } = require('./anonymizeMovements'); +const { scheduledCleanupMessages } = require('./cleanupMessages'); exports.auth = auth; exports.generateSignInLink = generateSignInLink; @@ -53,3 +55,6 @@ exports.enrichArrivalOnCreate = enrichMovements.enrichArrivalOnCreate; exports.enrichArrivalOnUpdate = enrichMovements.enrichArrivalOnUpdate; exports.updateArrivalPaymentStatusOnCardPaymentUpdate = updateArrivalPaymentStatus.updateArrivalPaymentStatusOnCardPaymentUpdate; + +exports.scheduledAnonymizeMovements = scheduledAnonymizeMovements; +exports.scheduledCleanupMessages = scheduledCleanupMessages; From 143a686efcc28f5521de75ebacb13e9bd46541eb Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:01:01 +0000 Subject: [PATCH 35/62] fix: address review findings in anonymizeMovements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix endAt/< boundary mismatch: use <= for dateTime comparison to be consistent with the inclusive Firebase endAt query - Add comment explaining client-side filtering of already-anonymized records fetched by the endAt query - Assert arrival updates independently in anonymization test - Add test for partial PII fields: verify absent fields are not included in the anonymization update object Co-authored-by: Roli Züger --- functions/anonymizeMovements.js | 6 ++- functions/anonymizeMovements.spec.js | 67 ++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/functions/anonymizeMovements.js b/functions/anonymizeMovements.js index a57bd600..528b740f 100644 --- a/functions/anonymizeMovements.js +++ b/functions/anonymizeMovements.js @@ -29,7 +29,7 @@ function getAnonymizationUpdates(snapshot, cutoffIso) { snapshot.forEach(child => { const val = child.val(); - if (val.dateTime < cutoffIso && !val.anonymized) { + if (val.dateTime <= cutoffIso && !val.anonymized) { PII_FIELDS.forEach(field => { if (val[field] !== undefined) { updates[`${child.key}/${field}`] = null; @@ -64,6 +64,10 @@ exports.scheduledAnonymizeMovements = functions console.log(`Anonymizing movements older than ${cutoffIso} (retention: ${retentionDays} days)`); + // Note: endAt fetches all records up to cutoffIso, including already-anonymized ones. + // These are filtered out client-side via the !val.anonymized check below. + // Over time this may transfer growing amounts of anonymized data on each run, + // but is acceptable given bounded aerodrome data volumes. const departuresSnap = await db.ref('/departures') .orderByChild('dateTime') .endAt(cutoffIso) diff --git a/functions/anonymizeMovements.spec.js b/functions/anonymizeMovements.spec.js index 81d14ad5..e5de6f88 100644 --- a/functions/anonymizeMovements.spec.js +++ b/functions/anonymizeMovements.spec.js @@ -93,26 +93,65 @@ describe('anonymizeMovements', () => { const { scheduledAnonymizeMovements } = require('./anonymizeMovements'); await scheduledAnonymizeMovements(); - expect(mockUpdate).toHaveBeenCalled(); + expect(mockUpdate).toHaveBeenCalledTimes(2); + + const departureUpdates = mockUpdate.mock.calls[0][0]; + + expect(departureUpdates['mov1/firstname']).toBeNull(); + expect(departureUpdates['mov1/lastname']).toBeNull(); + expect(departureUpdates['mov1/email']).toBeNull(); + expect(departureUpdates['mov1/phone']).toBeNull(); + expect(departureUpdates['mov1/memberNr']).toBeNull(); + expect(departureUpdates['mov1/immatriculation']).toBeNull(); + expect(departureUpdates['mov1/aircraftType']).toBeNull(); + expect(departureUpdates['mov1/mtow']).toBeNull(); + expect(departureUpdates['mov1/remarks']).toBeNull(); + expect(departureUpdates['mov1/createdBy']).toBeNull(); + expect(departureUpdates['mov1/createdBy_orderKey']).toBeNull(); + expect(departureUpdates['mov1/anonymized']).toBe(true); + + expect(departureUpdates['mov1/location']).toBeUndefined(); + expect(departureUpdates['mov1/flightType']).toBeUndefined(); + expect(departureUpdates['mov1/dateTime']).toBeUndefined(); + + const arrivalUpdates = mockUpdate.mock.calls[1][0]; + expect(arrivalUpdates['mov1/firstname']).toBeNull(); + expect(arrivalUpdates['mov1/anonymized']).toBe(true); + expect(arrivalUpdates['mov1/location']).toBeUndefined(); + }); - const updates = mockUpdate.mock.calls[0][0]; + it('should only null out PII fields that are present on the record', async () => { + mockRefData['/settings/movementRetentionDays'] = createValueSnapshot(730); + // Movement without carriageVoucher, customsFormId, customsFormUrl + const snapshot = createSnapshot({ + 'mov1': { + dateTime: '2023-01-15T10:00:00.000Z', + firstname: 'Hans', + lastname: 'Muster', + location: 'LSZT', + }, + }); + + mockRefData['/departures'] = snapshot; + mockRefData['/arrivals'] = { forEach: () => {} }; + + const { scheduledAnonymizeMovements } = require('./anonymizeMovements'); + await scheduledAnonymizeMovements(); + + expect(mockUpdate).toHaveBeenCalledTimes(1); + + const updates = mockUpdate.mock.calls[0][0]; expect(updates['mov1/firstname']).toBeNull(); expect(updates['mov1/lastname']).toBeNull(); - expect(updates['mov1/email']).toBeNull(); - expect(updates['mov1/phone']).toBeNull(); - expect(updates['mov1/memberNr']).toBeNull(); - expect(updates['mov1/immatriculation']).toBeNull(); - expect(updates['mov1/aircraftType']).toBeNull(); - expect(updates['mov1/mtow']).toBeNull(); - expect(updates['mov1/remarks']).toBeNull(); - expect(updates['mov1/createdBy']).toBeNull(); - expect(updates['mov1/createdBy_orderKey']).toBeNull(); expect(updates['mov1/anonymized']).toBe(true); - expect(updates['mov1/location']).toBeUndefined(); - expect(updates['mov1/flightType']).toBeUndefined(); - expect(updates['mov1/dateTime']).toBeUndefined(); + // Fields absent from the record must not appear in the update object + expect(updates['mov1/carriageVoucher']).toBeUndefined(); + expect(updates['mov1/customsFormId']).toBeUndefined(); + expect(updates['mov1/customsFormUrl']).toBeUndefined(); + expect(updates['mov1/email']).toBeUndefined(); + expect(updates['mov1/phone']).toBeUndefined(); }); it('should skip already anonymized movements', async () => { From bc91835f43dda02168093dde5cefd664bd7c2972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Z=C3=BCger?= Date: Fri, 20 Mar 2026 18:36:09 +0000 Subject: [PATCH 36/62] feat: add privacy settings admin section Add admin UI for managing DSGVO-related settings: - Privacy policy URL (writable, was read-only) - Movement retention days (new) - Message retention days (new) Gated behind `privacySettings` project config flag. --- projects/default.json | 3 +- src/components/AdminPage/AdminNavigation.tsx | 1 + src/components/AdminPage/AdminPage.tsx | 6 + .../subpages/AdminPrivacySettingsPage.tsx | 10 ++ .../PrivacySettingsForm.tsx | 114 ++++++++++++++++++ src/components/PrivacySettingsForm/index.tsx | 3 + .../PrivacySettingsFormContainer.tsx | 20 +++ src/locales/de.json | 14 ++- src/modules/settings/index.ts | 6 + .../settings/messageRetentionDays/actions.ts | 40 ++++++ .../settings/messageRetentionDays/index.ts | 9 ++ .../messageRetentionDays/reducer.spec.ts | 75 ++++++++++++ .../settings/messageRetentionDays/reducer.ts | 43 +++++++ .../messageRetentionDays/sagas.spec.ts | 68 +++++++++++ .../settings/messageRetentionDays/sagas.ts | 30 +++++ .../settings/movementRetentionDays/actions.ts | 40 ++++++ .../settings/movementRetentionDays/index.ts | 9 ++ .../movementRetentionDays/reducer.spec.ts | 75 ++++++++++++ .../settings/movementRetentionDays/reducer.ts | 43 +++++++ .../movementRetentionDays/sagas.spec.ts | 68 +++++++++++ .../settings/movementRetentionDays/sagas.ts | 30 +++++ .../settings/privacyPolicyUrl/actions.ts | 29 ++++- .../settings/privacyPolicyUrl/index.ts | 3 + .../settings/privacyPolicyUrl/reducer.spec.ts | 35 ++++++ .../settings/privacyPolicyUrl/reducer.ts | 18 +++ .../settings/privacyPolicyUrl/sagas.spec.ts | 40 +++++- .../settings/privacyPolicyUrl/sagas.ts | 15 ++- 27 files changed, 841 insertions(+), 6 deletions(-) create mode 100644 src/components/AdminPage/subpages/AdminPrivacySettingsPage.tsx create mode 100644 src/components/PrivacySettingsForm/PrivacySettingsForm.tsx create mode 100644 src/components/PrivacySettingsForm/index.tsx create mode 100644 src/containers/PrivacySettingsFormContainer.tsx create mode 100644 src/modules/settings/messageRetentionDays/actions.ts create mode 100644 src/modules/settings/messageRetentionDays/index.ts create mode 100644 src/modules/settings/messageRetentionDays/reducer.spec.ts create mode 100644 src/modules/settings/messageRetentionDays/reducer.ts create mode 100644 src/modules/settings/messageRetentionDays/sagas.spec.ts create mode 100644 src/modules/settings/messageRetentionDays/sagas.ts create mode 100644 src/modules/settings/movementRetentionDays/actions.ts create mode 100644 src/modules/settings/movementRetentionDays/index.ts create mode 100644 src/modules/settings/movementRetentionDays/reducer.spec.ts create mode 100644 src/modules/settings/movementRetentionDays/reducer.ts create mode 100644 src/modules/settings/movementRetentionDays/sagas.spec.ts create mode 100644 src/modules/settings/movementRetentionDays/sagas.ts diff --git a/projects/default.json b/projects/default.json index 14420df7..9914c43f 100644 --- a/projects/default.json +++ b/projects/default.json @@ -30,5 +30,6 @@ "homebasePayment": false, "loginForm": "usernamePassword", "maskContactInformation": true, - "memberManagement": false + "memberManagement": false, + "privacySettings": false } diff --git a/src/components/AdminPage/AdminNavigation.tsx b/src/components/AdminPage/AdminNavigation.tsx index 88de439a..0e9049e0 100644 --- a/src/components/AdminPage/AdminNavigation.tsx +++ b/src/components/AdminPage/AdminNavigation.tsx @@ -117,6 +117,7 @@ const AdminNavigation = ({ activeTab, hiddenTabs, onTabChange }) => { { key: 'kiosk-access', label: t('admin.kioskAccess'), icon: 'person_add' }, { key: 'guest-access', label: t('admin.guestAccess'), icon: 'person_add' }, { key: 'import', label: t('admin.import'), icon: 'file_upload' }, + { key: 'privacy', label: t('admin.privacy'), icon: 'shield' }, ].filter(item => !hiddenTabs.includes(item.key)); return ( diff --git a/src/components/AdminPage/AdminPage.tsx b/src/components/AdminPage/AdminPage.tsx index 2678f943..ddfd5c80 100644 --- a/src/components/AdminPage/AdminPage.tsx +++ b/src/components/AdminPage/AdminPage.tsx @@ -14,6 +14,7 @@ import AdminAircraftPage from './subpages/AdminAircraftPage'; import AdminInvoiceRecipientsPage from './subpages/AdminInvoiceRecipientsPage'; import AdminGuestAccessPage from './subpages/AdminGuestAccessPage'; import AdminKioskAccessPage from './subpages/AdminKioskAccessPage'; +import AdminPrivacySettingsPage from './subpages/AdminPrivacySettingsPage'; import Content from './Content'; import objectToArray from '../../util/objectToArray'; @@ -77,6 +78,8 @@ class AdminPage extends Component { return ; case 'kiosk-access': return ; + case 'privacy': + return ; default: return ; } @@ -102,6 +105,9 @@ class AdminPage extends Component { if (!memberManagementEnabled) { hiddenTabs.push('import') } + if (__CONF__.privacySettings !== true) { + hiddenTabs.push('privacy') + } return ( diff --git a/src/components/AdminPage/subpages/AdminPrivacySettingsPage.tsx b/src/components/AdminPage/subpages/AdminPrivacySettingsPage.tsx new file mode 100644 index 00000000..4c0d6d6d --- /dev/null +++ b/src/components/AdminPage/subpages/AdminPrivacySettingsPage.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import PrivacySettingsForm from '../../../containers/PrivacySettingsFormContainer'; + +const AdminPrivacySettingsPage = () => { + return ( + + ); +}; + +export default AdminPrivacySettingsPage; diff --git a/src/components/PrivacySettingsForm/PrivacySettingsForm.tsx b/src/components/PrivacySettingsForm/PrivacySettingsForm.tsx new file mode 100644 index 00000000..0f682390 --- /dev/null +++ b/src/components/PrivacySettingsForm/PrivacySettingsForm.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Field, Form } from 'react-final-form'; +import { renderInputField } from '../wizards/renderField'; +import FieldSet from '../wizards/FieldSet'; +import LabeledBox from '../LabeledBox'; +import Button from '../Button'; +import styled from 'styled-components'; + +const HelpText = styled.p` + color: #666; + font-size: 0.9em; + margin: 0.25em 0 1em 0; +`; + +interface PrivacySettingsFormProps { + privacyPolicyUrl: { url: string | null; saving: boolean }; + movementRetentionDays: { days: number | null; saving: boolean }; + messageRetentionDays: { days: number | null; saving: boolean }; + setPrivacyPolicyUrl: (url: string | null) => void; + setMovementRetentionDays: (days: number | null) => void; + setMessageRetentionDays: (days: number | null) => void; +} + +function PrivacySettingsForm(props: PrivacySettingsFormProps) { + const { t } = useTranslation(); + + const initialValues = { + privacyPolicyUrl: props.privacyPolicyUrl.url || '', + movementRetentionDays: props.movementRetentionDays.days != null ? props.movementRetentionDays.days : '', + messageRetentionDays: props.messageRetentionDays.days != null ? props.messageRetentionDays.days : '', + }; + + const saving = + props.privacyPolicyUrl.saving || + props.movementRetentionDays.saving || + props.messageRetentionDays.saving; + + const handleSubmit = (values: any) => { + const url = values.privacyPolicyUrl ? values.privacyPolicyUrl.trim() : null; + props.setPrivacyPolicyUrl(url || null); + + const movementDays = values.movementRetentionDays !== '' && values.movementRetentionDays != null + ? parseInt(values.movementRetentionDays, 10) + : null; + props.setMovementRetentionDays(isNaN(movementDays as number) ? null : movementDays); + + const messageDays = values.messageRetentionDays !== '' && values.messageRetentionDays != null + ? parseInt(values.messageRetentionDays, 10) + : null; + props.setMessageRetentionDays(isNaN(messageDays as number) ? null : messageDays); + }; + + return ( + +
+ {({ handleSubmit, dirty }) => ( + +
+ +
+ {t('privacy.privacyPolicyUrlHelp')} + +
+ { + if (value === '') return ''; + const num = parseInt(value, 10); + return isNaN(num) ? '' : num; + }} + /> +
+ {t('privacy.movementRetentionDaysHelp')} + +
+ { + if (value === '') return ''; + const num = parseInt(value, 10); + return isNaN(num) ? '' : num; + }} + /> +
+ {t('privacy.messageRetentionDaysHelp')} + +