diff --git a/package-lock.json b/package-lock.json index 62d2696..1920e29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,10 +7,10 @@ "": { "name": "oikos", "version": "1.0.0", + "license": "MIT", "dependencies": { "bcrypt": "^5.1.1", "better-sqlite3": "^9.6.0", - "connect-sqlite3": "^0.9.15", "dotenv": "^16.4.7", "express": "^4.21.2", "express-rate-limit": "^7.5.0", @@ -18,6 +18,9 @@ "helmet": "^8.0.0", "node-fetch": "^3.3.2" }, + "devDependencies": { + "sharp": "^0.34.5" + }, "engines": { "node": ">=20.0.0" }, @@ -26,12 +29,506 @@ "tsdav": "^2.0.10" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, "license": "MIT", - "optional": true + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", @@ -73,42 +570,6 @@ } } }, - "node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "license": "MIT", - "optional": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -163,33 +624,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "optional": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -387,36 +821,6 @@ "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -455,16 +859,6 @@ "node": ">=10" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -480,17 +874,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/connect-sqlite3": { - "version": "0.9.16", - "resolved": "https://registry.npmjs.org/connect-sqlite3/-/connect-sqlite3-0.9.16.tgz", - "integrity": "sha512-2gqo0QmcBBL8p8+eqpBETn7RgM/PaoKvpQGl8PfjEgwlr0VuMYNMxRJRrRCo3KR3fxMYeSsCw2tGNG0JKN9Nvg==", - "dependencies": { - "sqlite3": "^5.0.2" - }, - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -703,6 +1086,7 @@ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -713,6 +1097,7 @@ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -729,23 +1114,6 @@ "once": "^1.4.0" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "license": "MIT", - "optional": true - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1260,13 +1628,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC", - "optional": true - }, "node_modules/gtoken": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", @@ -1320,13 +1681,6 @@ "node": ">=18.0.0" } }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause", - "optional": true - }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -1347,46 +1701,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1423,16 +1737,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1465,33 +1769,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "license": "ISC", - "optional": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1515,16 +1792,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1543,13 +1810,6 @@ "node": ">=8" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "license": "MIT", - "optional": true - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -1563,13 +1823,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC", - "optional": true - }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -1603,19 +1856,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -1640,34 +1880,6 @@ "semver": "bin/semver.js" } }, - "node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "license": "ISC", - "optional": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1782,76 +1994,6 @@ "node": ">=8" } }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "license": "MIT", - "optional": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -1960,84 +2102,6 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "license": "MIT", - "optional": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -2117,22 +2181,6 @@ "wrappy": "1" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2184,27 +2232,6 @@ "node": ">=10" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "license": "ISC", - "optional": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "license": "MIT", - "optional": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2305,16 +2332,6 @@ "node": ">= 6" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -2436,6 +2453,51 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -2559,115 +2621,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", - "optional": true, - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/socks-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socks-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/sqlite3": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", - "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.1", - "tar": "^6.1.11" - }, - "optionalDependencies": { - "node-gyp": "8.x" - }, - "peerDependencies": { - "node-gyp": "8.x" - }, - "peerDependenciesMeta": { - "node-gyp": { - "optional": true - } - } - }, - "node_modules/sqlite3/node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" - }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -2838,6 +2791,14 @@ "license": "MIT", "optional": true }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2875,26 +2836,6 @@ "node": ">= 0.8" } }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "license": "ISC", - "optional": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2974,22 +2915,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "optional": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index 1eaea85..6a47635 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "bcrypt": "^5.1.1", "better-sqlite3": "^9.6.0", -"dotenv": "^16.4.7", + "dotenv": "^16.4.7", "express": "^4.21.2", "express-rate-limit": "^7.5.0", "express-session": "^1.18.1", @@ -33,5 +33,8 @@ "license": "MIT", "engines": { "node": ">=20.0.0" + }, + "devDependencies": { + "sharp": "^0.34.5" } } diff --git a/public/components/modal.js b/public/components/modal.js index 44f87bf..7be0742 100644 --- a/public/components/modal.js +++ b/public/components/modal.js @@ -13,6 +13,9 @@ let activeOverlay = null; let previouslyFocused = null; let focusTrapHandler = null; +// Overlay-Dimming: theme-color abdunkeln im Standalone-Modus +const OVERLAY_THEME_COLOR = '#1A1A1A'; + const FOCUSABLE = [ 'a[href]', 'button:not([disabled])', @@ -126,6 +129,11 @@ export function openModal({ title, content, onSave, onDelete, size = 'md' } = {} // Callback für Aufrufer (Form-Events binden etc.) if (typeof onSave === 'function') onSave(panel); + + // Standalone: Statusbar abdunkeln (Overlay-Effekt) + if (window.oikos?.setThemeColor) { + window.oikos.setThemeColor(OVERLAY_THEME_COLOR, OVERLAY_THEME_COLOR); + } } // -------------------------------------------------------- @@ -155,4 +163,9 @@ export function closeModal() { previouslyFocused.focus(); previouslyFocused = null; } + + // Standalone: Statusbar-Farbe zur aktuellen Route wiederherstellen + if (window.oikos?.restoreThemeColor) { + window.oikos.restoreThemeColor(); + } } diff --git a/public/components/oikos-install-prompt.js b/public/components/oikos-install-prompt.js new file mode 100644 index 0000000..c55efb8 --- /dev/null +++ b/public/components/oikos-install-prompt.js @@ -0,0 +1,331 @@ +/** + * Modul: Install-Prompt Web Component + * Zweck: Dezentes Banner für PWA-Installation (Chrome/Android) und iOS-Anleitung + * Abhängigkeiten: Design Tokens aus tokens.css (via CSS custom properties) + * + * Verhalten: + * - Chrome/Android: Fängt beforeinstallprompt ab, zeigt Install-Banner + * - iOS (Safari): Zeigt Anleitung "Zum Home-Bildschirm" + * - Standalone-Modus: Zeigt nichts an + * - Dismiss: 30 Tage via localStorage gespeichert + */ + +const DISMISS_KEY = 'oikos-install-dismissed'; +const DISMISS_DURATION_MS = 30 * 24 * 60 * 60 * 1000; // 30 Tage + +class OikosInstallPrompt extends HTMLElement { + constructor() { + super(); + this._deferredPrompt = null; + this._shadow = this.attachShadow({ mode: 'open' }); + } + + connectedCallback() { + // Bereits im Standalone-Modus — nichts anzeigen + if ( + window.matchMedia('(display-mode: standalone)').matches || + navigator.standalone === true + ) { + return; + } + + // Dismiss noch aktiv? + const dismissed = localStorage.getItem(DISMISS_KEY); + if (dismissed && Date.now() - Number(dismissed) < DISMISS_DURATION_MS) { + return; + } + + if (this._isIOS()) { + this._showIOSPrompt(); + } else { + this._listenForInstallPrompt(); + } + } + + disconnectedCallback() { + window.removeEventListener('beforeinstallprompt', this._onBeforeInstall); + } + + /** iOS Safari erkennen (kein beforeinstallprompt-Support) */ + _isIOS() { + return ( + navigator.standalone === undefined && + /iPhone|iPad/.test(navigator.userAgent) && + !window.MSStream + ); + } + + /** Chrome/Android: beforeinstallprompt abfangen */ + _listenForInstallPrompt() { + this._onBeforeInstall = (e) => { + e.preventDefault(); + this._deferredPrompt = e; + this._showBanner(false); + }; + window.addEventListener('beforeinstallprompt', this._onBeforeInstall); + } + + /** Banner rendern */ + _showBanner(isIOS) { + this._shadow.innerHTML = ''; + + const style = document.createElement('style'); + style.textContent = ` + :host { + display: block; + position: fixed; + bottom: calc(var(--nav-height-mobile, 56px) + env(safe-area-inset-bottom, 0px) + 8px); + left: var(--space-3, 12px); + right: var(--space-3, 12px); + z-index: var(--z-toast, 300); + pointer-events: none; + } + + .banner { + display: flex; + align-items: center; + gap: var(--space-3, 12px); + padding: var(--space-3, 12px) var(--space-4, 16px); + background: var(--color-surface, #fff); + border: 1px solid var(--color-border, #e8e7e2); + border-radius: var(--radius-md, 12px); + box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08)); + pointer-events: auto; + transform: translateY(calc(100% + 20px)); + transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); + } + + .banner--visible { + transform: translateY(0); + } + + .icon { + width: 40px; + height: 40px; + border-radius: var(--radius-sm, 8px); + flex-shrink: 0; + } + + .text { + flex: 1; + min-width: 0; + } + + .title { + font-family: var(--font-sans, system-ui); + font-size: var(--text-base, 0.875rem); + font-weight: var(--font-weight-semibold, 600); + color: var(--color-text-primary, #1c1c1a); + line-height: var(--line-height-tight, 1.25); + } + + .subtitle { + font-family: var(--font-sans, system-ui); + font-size: var(--text-sm, 0.8125rem); + color: var(--color-text-secondary, #6c6b67); + line-height: var(--line-height-base, 1.5); + margin-top: 2px; + } + + .btn-install { + flex-shrink: 0; + padding: var(--space-2, 8px) var(--space-4, 16px); + background: var(--color-btn-primary, #2554C7); + color: #fff; + border: none; + border-radius: var(--radius-sm, 8px); + font-family: var(--font-sans, system-ui); + font-size: var(--text-sm, 0.8125rem); + font-weight: var(--font-weight-semibold, 600); + cursor: pointer; + min-height: 36px; + min-width: 36px; + transition: background 0.15s ease; + } + + .btn-install:hover { + background: var(--color-btn-primary-hover, #1E429A); + } + + .btn-dismiss { + flex-shrink: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: none; + border: none; + border-radius: var(--radius-xs, 4px); + cursor: pointer; + color: var(--color-text-tertiary, #737370); + padding: 0; + min-height: 32px; + min-width: 32px; + transition: background 0.15s ease; + } + + .btn-dismiss:hover { + background: var(--color-surface-3, #efeee9); + } + + .btn-dismiss svg { + width: 18px; + height: 18px; + } + + /* iOS share icon inline */ + .share-icon { + display: inline-block; + width: 1em; + height: 1em; + vertical-align: -0.1em; + } + + @media (min-width: 1024px) { + :host { + /* Desktop: Sidebar statt Bottom-Nav, Banner unten rechts */ + bottom: calc(var(--space-4, 16px) + env(safe-area-inset-bottom, 0px)); + left: auto; + right: var(--space-4, 16px); + max-width: 380px; + } + } + `; + + const banner = document.createElement('div'); + banner.className = 'banner'; + banner.setAttribute('role', 'alert'); + + // App-Icon + const icon = document.createElement('img'); + icon.className = 'icon'; + icon.src = '/icons/icon-192.png'; + icon.alt = 'Oikos'; + icon.width = 40; + icon.height = 40; + banner.appendChild(icon); + + // Text + const text = document.createElement('div'); + text.className = 'text'; + + const title = document.createElement('div'); + title.className = 'title'; + title.textContent = 'Oikos installieren'; + + const subtitle = document.createElement('div'); + subtitle.className = 'subtitle'; + + if (isIOS) { + // iOS: Teilen-Icon als SVG inline + subtitle.innerHTML = ''; + subtitle.append( + document.createTextNode('Tippe auf '), + this._createShareIcon(), + document.createTextNode(' → „Zum Home-Bildschirm"') + ); + } else { + subtitle.textContent = 'Zur App hinzufügen'; + } + + text.appendChild(title); + text.appendChild(subtitle); + banner.appendChild(text); + + // Install-Button (nur Chrome/Android) + if (!isIOS) { + const btn = document.createElement('button'); + btn.className = 'btn-install'; + btn.textContent = 'Installieren'; + btn.addEventListener('click', () => this._onInstallClick()); + banner.appendChild(btn); + } + + // Dismiss-Button + const dismiss = document.createElement('button'); + dismiss.className = 'btn-dismiss'; + dismiss.setAttribute('aria-label', 'Schließen'); + dismiss.innerHTML = ``; + dismiss.addEventListener('click', () => this._dismiss()); + banner.appendChild(dismiss); + + this._shadow.appendChild(style); + this._shadow.appendChild(banner); + + // Slide-in Animation nach nächstem Frame + requestAnimationFrame(() => { + requestAnimationFrame(() => { + banner.classList.add('banner--visible'); + }); + }); + } + + /** iOS Teilen-Icon (Box mit Pfeil nach oben) */ + _createShareIcon() { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.setAttribute('viewBox', '0 0 24 24'); + svg.setAttribute('fill', 'none'); + svg.setAttribute('stroke', 'currentColor'); + svg.setAttribute('stroke-width', '2'); + svg.setAttribute('stroke-linecap', 'round'); + svg.setAttribute('stroke-linejoin', 'round'); + svg.classList.add('share-icon'); + + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path.setAttribute('d', 'M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8'); + const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline'); + polyline.setAttribute('points', '16 6 12 2 8 6'); + const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); + line.setAttribute('x1', '12'); + line.setAttribute('y1', '2'); + line.setAttribute('x2', '12'); + line.setAttribute('y2', '15'); + + svg.appendChild(path); + svg.appendChild(polyline); + svg.appendChild(line); + return svg; + } + + /** Install-Button geklickt */ + async _onInstallClick() { + if (!this._deferredPrompt) return; + + try { + this._deferredPrompt.prompt(); + const result = await this._deferredPrompt.userChoice; + console.log('[oikos-install-prompt] Ergebnis:', result.outcome); + + if (result.outcome === 'accepted') { + this._remove(); + } + } catch (err) { + console.error('[oikos-install-prompt] Fehler:', err); + } + + this._deferredPrompt = null; + } + + /** Dismiss: 30 Tage merken, Banner entfernen */ + _dismiss() { + localStorage.setItem(DISMISS_KEY, String(Date.now())); + this._remove(); + } + + /** Banner mit Slide-out entfernen */ + _remove() { + const banner = this._shadow.querySelector('.banner'); + if (!banner) return; + + banner.classList.remove('banner--visible'); + banner.addEventListener('transitionend', () => this.remove(), { once: true }); + } + + /** iOS: Banner direkt anzeigen */ + _showIOSPrompt() { + this._showBanner(true); + } +} + +customElements.define('oikos-install-prompt', OikosInstallPrompt); diff --git a/public/icons/apple-touch-icon.png b/public/icons/apple-touch-icon.png index a26d81f..9b596c1 100644 Binary files a/public/icons/apple-touch-icon.png and b/public/icons/apple-touch-icon.png differ diff --git a/public/icons/favicon-32.png b/public/icons/favicon-32.png index 22c2853..78bfcd4 100644 Binary files a/public/icons/favicon-32.png and b/public/icons/favicon-32.png differ diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png index 01e8ae3..43c0c44 100644 Binary files a/public/icons/icon-192.png and b/public/icons/icon-192.png differ diff --git a/public/icons/icon-512.png b/public/icons/icon-512.png index 99f8a4a..9433779 100644 Binary files a/public/icons/icon-512.png and b/public/icons/icon-512.png differ diff --git a/public/icons/icon-maskable-192.png b/public/icons/icon-maskable-192.png new file mode 100644 index 0000000..d99355a Binary files /dev/null and b/public/icons/icon-maskable-192.png differ diff --git a/public/icons/icon-maskable-512.png b/public/icons/icon-maskable-512.png new file mode 100644 index 0000000..3f41865 Binary files /dev/null and b/public/icons/icon-maskable-512.png differ diff --git a/public/index.html b/public/index.html index d8eb213..40cefba 100644 --- a/public/index.html +++ b/public/index.html @@ -2,14 +2,24 @@ - - + + + + + + + + + + + + Oikos - + @@ -24,6 +34,7 @@ + @@ -60,6 +71,10 @@ + + + + diff --git a/public/manifest.json b/public/manifest.json index 8ebb9fd..b3006bc 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,43 +1,20 @@ { "name": "Oikos Familienplaner", "short_name": "Oikos", - "description": "Selbstgehosteter Familienplaner für Kalender, Aufgaben, Einkauf und mehr.", + "description": "Selbstgehosteter Familienplaner", "start_url": "/", + "scope": "/", "display": "standalone", - "background_color": "#F5F4F1", - "theme_color": "#2563EB", "orientation": "portrait-primary", - "lang": "de", + "theme_color": "#007AFF", + "background_color": "#F5F5F7", + "lang": "de-DE", + "categories": ["productivity", "lifestyle"], "icons": [ - { - "src": "/icons/icon-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "any" - }, - { - "src": "/icons/icon-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "/icons/icon-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "any" - }, - { - "src": "/icons/icon-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "/icons/apple-touch-icon.png", - "sizes": "180x180", - "type": "image/png", - "purpose": "any" - } - ] + { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, + { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, + { "src": "/icons/icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, + { "src": "/icons/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } + ], + "screenshots": [] } diff --git a/public/router.js b/public/router.js index 2e199fb..1c4bf49 100644 --- a/public/router.js +++ b/public/router.js @@ -8,21 +8,61 @@ import { auth } from '/api.js'; // -------------------------------------------------------- // Routen-Definitionen -// Jede Route hat: path, page (dynamisch geladen), requiresAuth +// Jede Route hat: path, page (dynamisch geladen), requiresAuth, module (für theme-color) // -------------------------------------------------------- const ROUTES = [ - { path: '/login', page: '/pages/login.js', requiresAuth: false }, - { path: '/', page: '/pages/dashboard.js', requiresAuth: true }, - { path: '/tasks', page: '/pages/tasks.js', requiresAuth: true }, - { path: '/shopping', page: '/pages/shopping.js', requiresAuth: true }, - { path: '/meals', page: '/pages/meals.js', requiresAuth: true }, - { path: '/calendar', page: '/pages/calendar.js', requiresAuth: true }, - { path: '/notes', page: '/pages/notes.js', requiresAuth: true }, - { path: '/contacts', page: '/pages/contacts.js', requiresAuth: true }, - { path: '/budget', page: '/pages/budget.js', requiresAuth: true }, - { path: '/settings', page: '/pages/settings.js', requiresAuth: true }, + { path: '/login', page: '/pages/login.js', requiresAuth: false, module: null }, + { path: '/', page: '/pages/dashboard.js', requiresAuth: true, module: 'dashboard' }, + { path: '/tasks', page: '/pages/tasks.js', requiresAuth: true, module: 'tasks' }, + { path: '/shopping', page: '/pages/shopping.js', requiresAuth: true, module: 'shopping' }, + { path: '/meals', page: '/pages/meals.js', requiresAuth: true, module: 'meals' }, + { path: '/calendar', page: '/pages/calendar.js', requiresAuth: true, module: 'calendar' }, + { path: '/notes', page: '/pages/notes.js', requiresAuth: true, module: 'notes' }, + { path: '/contacts', page: '/pages/contacts.js', requiresAuth: true, module: 'contacts' }, + { path: '/budget', page: '/pages/budget.js', requiresAuth: true, module: 'budget' }, + { path: '/settings', page: '/pages/settings.js', requiresAuth: true, module: 'settings' }, ]; +// -------------------------------------------------------- +// Standalone-Modus: Dynamische theme-color Anpassung +// Statusbar-Farbe spiegelt aktuelle Seite / Modal-State wider +// -------------------------------------------------------- +const isStandalone = window.matchMedia('(display-mode: standalone)').matches + || navigator.standalone === true; + +/** + * Setzt die theme-color Meta-Tags (Light + Dark Variante). + * @param {string} lightColor + * @param {string} [darkColor] — Falls nicht angegeben, wird lightColor für beide gesetzt + */ +function setThemeColor(lightColor, darkColor) { + if (!isStandalone) return; + const metas = document.querySelectorAll('meta[name="theme-color"]'); + if (metas.length >= 2) { + metas[0].setAttribute('content', lightColor); + metas[1].setAttribute('content', darkColor || lightColor); + } else if (metas.length === 1) { + metas[0].setAttribute('content', lightColor); + } +} + +/** Liest eine CSS Custom Property vom :root */ +function getCSSToken(name) { + return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); +} + +/** Setzt theme-color passend zum aktuellen Modul */ +function updateThemeColorForRoute(route) { + if (!route?.module) { + setThemeColor('#007AFF', '#1C1C1E'); + return; + } + const color = getCSSToken(`--module-${route.module}`); + if (color) { + setThemeColor(color, color); + } +} + // -------------------------------------------------------- // Modul-Cache: verhindert redundante dynamic imports bei Navigation // -------------------------------------------------------- @@ -88,6 +128,7 @@ async function navigate(path, userOrPushState = true, pushState = true) { await renderPage(route); updateNav(path); + updateThemeColorForRoute(route); } /** @@ -348,4 +389,12 @@ window.addEventListener('auth:expired', () => { navigate(location.pathname, false); // Globale Exporte -window.oikos = { navigate, showToast }; +window.oikos = { + navigate, + showToast, + setThemeColor, + restoreThemeColor: () => { + const route = ROUTES.find((r) => r.path === currentPath); + updateThemeColorForRoute(route); + }, +}; diff --git a/public/styles/pwa.css b/public/styles/pwa.css new file mode 100644 index 0000000..751b397 --- /dev/null +++ b/public/styles/pwa.css @@ -0,0 +1,65 @@ +/** + * Modul: PWA Native Feel + * Zweck: Natives Touch- und Scrollverhalten, Safe Areas, Touch-Targets + * Abhängigkeiten: tokens.css, layout.css + */ + +/* ── Kein Rubber-Banding / Pull-to-Refresh des Browsers ── */ +html, body { + overscroll-behavior: none; +} + +/* ── Kein Tap-Highlight auf allen Elementen (Android Chrome) ── + * reset.css setzt es nur auf html; hier global für alle Elemente */ +* { + -webkit-tap-highlight-color: transparent; +} + +/* ── Safe Area Insets (Notch, Dynamic Island, Gesture Bar) ── */ +body { + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); +} + +/* ── Bottom Nav über der Gesture Bar ── + * layout.css nutzt --safe-area-inset-bottom Token; + * hier als Fallback direkt via env() */ +.nav-bottom { + padding-bottom: env(safe-area-inset-bottom); +} + +/* ── Touch-Targets: min 44×44px (Apple HIG / WCAG 2.5.5) ── */ +button, a, [role="button"], input[type="checkbox"], input[type="radio"] { + min-height: 44px; + min-width: 44px; +} + +/* ── Smooth Momentum-Scrolling in scrollbaren Containern ── */ +.scroll-container, +.nav-bottom__scroll { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; +} + +/* ── Kein Text-Selection in UI-Elementen (nur in Content-Bereichen) ── */ +nav, +.nav-bottom, +.nav-sidebar, +.cal-toolbar, +.tasks-toolbar, +.notes-toolbar, +.contacts-toolbar, +.modal-panel__header { + -webkit-user-select: none; + user-select: none; +} + +/* ── Standalone-Modus: Status-Bar-Bereich berücksichtigen ── */ +@media (display-mode: standalone) { + body { + padding-top: env(safe-area-inset-top); + } +} diff --git a/public/sw-register.js b/public/sw-register.js index d198577..e0ade6c 100644 --- a/public/sw-register.js +++ b/public/sw-register.js @@ -1,6 +1,7 @@ /** * Modul: Service Worker Registrierung - * Zweck: Ausgelagert aus index.html um CSP-Inline-Script-Verletzung zu vermeiden + * Zweck: Ausgelagert aus index.html um CSP-Inline-Script-Verletzung zu vermeiden. + * Handhabt nahtlose Updates via controllerchange. * Abhängigkeiten: keine */ @@ -10,4 +11,13 @@ if ('serviceWorker' in navigator) { console.warn('[SW] Registrierung fehlgeschlagen:', err); }); }); + + // Nahtloses Update: Neuer SW hat skipWaiting() + clients.claim() aufgerufen + // → Controller wechselt → Seite neu laden für konsistenten Stand + let refreshing = false; + navigator.serviceWorker.addEventListener('controllerchange', () => { + if (refreshing) return; + refreshing = true; + window.location.reload(); + }); } diff --git a/public/sw.js b/public/sw.js index 68b36d6..7768c22 100644 --- a/public/sw.js +++ b/public/sw.js @@ -12,9 +12,9 @@ * API: Immer Netzwerk (kein Caching von Nutzerdaten) */ -const SHELL_CACHE = 'oikos-shell-v18'; -const PAGES_CACHE = 'oikos-pages-v18'; -const ASSETS_CACHE = 'oikos-assets-v18'; +const SHELL_CACHE = 'oikos-shell-v19'; +const PAGES_CACHE = 'oikos-pages-v19'; +const ASSETS_CACHE = 'oikos-assets-v19'; const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE]; // App-Shell: sofort benötigt für ersten Render @@ -28,6 +28,7 @@ const APP_SHELL = [ '/lucide.min.js', '/styles/tokens.css', '/styles/reset.css', + '/styles/pwa.css', '/styles/layout.css', '/styles/login.css', '/styles/dashboard.css', @@ -39,10 +40,15 @@ const APP_SHELL = [ '/styles/contacts.css', '/styles/budget.css', '/styles/settings.css', + '/components/oikos-install-prompt.js', '/manifest.json', '/favicon.ico', '/icons/favicon-32.png', '/icons/apple-touch-icon.png', + '/icons/icon-192.png', + '/icons/icon-512.png', + '/icons/icon-maskable-192.png', + '/icons/icon-maskable-512.png', ]; // Seiten-Module: lazy geladen, aber vorab gecacht für Offline @@ -107,6 +113,12 @@ self.addEventListener('fetch', (event) => { // Nur GET cachen if (request.method !== 'GET') return; + // Navigation Requests: Network-first, Fallback auf gecachte Shell + if (request.mode === 'navigate') { + event.respondWith(networkFirst(request, SHELL_CACHE)); + return; + } + // Bilder + Fonts: Cache-First, langer TTL if (isAsset(url.pathname)) { event.respondWith(cacheFirst(request, ASSETS_CACHE)); @@ -119,10 +131,39 @@ self.addEventListener('fetch', (event) => { return; } - // App-Shell (HTML, JS, CSS): Stale-While-Revalidate + // App-Shell (JS, CSS): Stale-While-Revalidate event.respondWith(staleWhileRevalidate(request, SHELL_CACHE)); }); +// -------------------------------------------------------- +// Strategie: Network-First (für Navigation Requests) +// Versucht Netzwerk, fällt auf gecachte Shell zurück (Offline). +// -------------------------------------------------------- +async function networkFirst(request, cacheName) { + const cache = await caches.open(cacheName); + + try { + const response = await fetch(request); + if (response.ok && response.type === 'basic') { + cache.put(request, response.clone()); + } + return response; + } catch { + // Offline: gecachte Shell liefern + const cached = await cache.match(request); + if (cached) return cached; + + // Fallback auf index.html (SPA-Routing) + const shell = await cache.match('/index.html'); + if (shell) return shell; + + return new Response('Keine Verbindung', { + status: 503, + headers: { 'Content-Type': 'text/plain; charset=utf-8' }, + }); + } +} + // -------------------------------------------------------- // Strategie: Stale-While-Revalidate // Liefert sofort aus Cache, aktualisiert im Hintergrund. diff --git a/scripts/generate-icons.js b/scripts/generate-icons.js new file mode 100644 index 0000000..e95386f --- /dev/null +++ b/scripts/generate-icons.js @@ -0,0 +1,88 @@ +/** + * Icon Generator for Oikos PWA + * Generates placeholder icons (accent color #007AFF with white "O") + * Sizes: 192px and 512px, both "any" and "maskable" variants + * Maskable icons include safe zone padding (min 10%) + * + * Usage: node scripts/generate-icons.js + * Dependencies: sharp (devDependency) + */ + +import sharp from 'sharp'; +import { mkdirSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ICONS_DIR = join(__dirname, '..', 'public', 'icons'); +const ACCENT = '#007AFF'; +const BG_LIGHT = '#F5F5F7'; + +mkdirSync(ICONS_DIR, { recursive: true }); + +/** + * Create an SVG with a centered "O" on accent background. + * @param {number} size - Icon dimension in px + * @param {boolean} maskable - If true, add 20% padding for safe zone + */ +function createSvg(size, maskable) { + const fontSize = maskable ? size * 0.4 : size * 0.55; + const bgRadius = maskable ? 0 : size * 0.18; + + return ` + + O +`; +} + +/** + * Create Apple Touch Icon (180x180) with slight rounding + */ +function createAppleTouchSvg() { + const size = 180; + const fontSize = size * 0.55; + return ` + + O +`; +} + +/** + * Create favicon (32x32) + */ +function createFaviconSvg() { + const size = 32; + const fontSize = size * 0.6; + return ` + + O +`; +} + +const icons = [ + { name: 'icon-192.png', size: 192, maskable: false }, + { name: 'icon-512.png', size: 512, maskable: false }, + { name: 'icon-maskable-192.png', size: 192, maskable: true }, + { name: 'icon-maskable-512.png', size: 512, maskable: true }, + { name: 'apple-touch-icon.png', size: 180, svg: createAppleTouchSvg() }, + { name: 'favicon-32.png', size: 32, svg: createFaviconSvg() }, +]; + +for (const icon of icons) { + const svg = icon.svg || createSvg(icon.size, icon.maskable); + const outputPath = join(ICONS_DIR, icon.name); + + await sharp(Buffer.from(svg)) + .png() + .toFile(outputPath); + + console.log(` ✓ ${icon.name} (${icon.size}x${icon.size})`); +} + +console.log('\nIcons generated in public/icons/');