diff --git a/import_map.json b/import_map.json index 3c34c52..d536e63 100644 --- a/import_map.json +++ b/import_map.json @@ -21,6 +21,7 @@ "node-emoji": "npm:node-emoji@^2.2.0", "plantuml-encoder": "npm:plantuml-encoder@^1.4.0", "qrcode": "npm:qrcode@^1.5.4", - "child_process": "node:child_process" + "child_process": "node:child_process", + "process": "node:process" } } diff --git a/package-lock.json b/package-lock.json index 003bf33..97568ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,20 +69,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@asciidoctor/cli": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@asciidoctor/cli/-/cli-4.0.0.tgz", @@ -149,12 +135,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -177,9 +163,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", "cpu": [ "ppc64" ], @@ -194,9 +180,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", "cpu": [ "arm" ], @@ -211,9 +197,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", "cpu": [ "arm64" ], @@ -228,9 +214,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", "cpu": [ "x64" ], @@ -245,9 +231,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", "cpu": [ "arm64" ], @@ -262,9 +248,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", "cpu": [ "x64" ], @@ -279,9 +265,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", "cpu": [ "arm64" ], @@ -296,9 +282,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", "cpu": [ "x64" ], @@ -313,9 +299,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", "cpu": [ "arm" ], @@ -330,9 +316,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", "cpu": [ "arm64" ], @@ -347,9 +333,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", "cpu": [ "ia32" ], @@ -364,9 +350,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", "cpu": [ "loong64" ], @@ -381,9 +367,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", "cpu": [ "mips64el" ], @@ -398,9 +384,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", "cpu": [ "ppc64" ], @@ -415,9 +401,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", "cpu": [ "riscv64" ], @@ -432,9 +418,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", "cpu": [ "s390x" ], @@ -449,9 +435,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", "cpu": [ "x64" ], @@ -466,9 +452,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", "cpu": [ "arm64" ], @@ -483,9 +469,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", "cpu": [ "x64" ], @@ -500,9 +486,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", "cpu": [ "arm64" ], @@ -517,9 +503,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", "cpu": [ "x64" ], @@ -534,9 +520,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", "cpu": [ "arm64" ], @@ -551,9 +537,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", "cpu": [ "x64" ], @@ -568,9 +554,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", "cpu": [ "arm64" ], @@ -585,9 +571,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", "cpu": [ "ia32" ], @@ -602,9 +588,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", "cpu": [ "x64" ], @@ -678,9 +664,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -689,9 +675,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -728,9 +714,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.32.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", - "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", "dev": true, "license": "MIT", "peer": true, @@ -753,14 +739,14 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", - "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "peer": true, "dependencies": { - "@eslint/core": "^0.15.1", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { @@ -963,15 +949,26 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -982,15 +979,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1663,9 +1660,9 @@ } }, "node_modules/@sveltejs/adapter-auto": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-6.0.1.tgz", - "integrity": "sha512-mcWud3pYGPWM2Pphdj8G9Qiq24nZ8L4LB7coCUckUEy5Y7wOWGJ/enaZ4AtJTcSm5dNK1rIkBRoqt+ae4zlxcQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-6.1.0.tgz", + "integrity": "sha512-shOuLI5D2s+0zTv2ab5M5PqfknXqWbKi+0UwB9yLTRIdzsK1R93JOO8jNhIYSHdW+IYXIYnLniu+JZqXs7h9Wg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1673,9 +1670,9 @@ } }, "node_modules/@sveltejs/adapter-node": { - "version": "5.2.13", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.13.tgz", - "integrity": "sha512-yS2TVFmIrxjGhYaV5/iIUrJ3mJl6zjaYn0lBD70vTLnYvJeqf3cjvLXeXCUCuYinhSBoyF4DpfGla49BnIy7sQ==", + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.14.tgz", + "integrity": "sha512-TjJvfw0HZlbBGGAW2vFtdGjdKhqpGW3ZDIz0nzy8Zx6Ki6oFmYTjV5Kwn3LWTsyjbsUSXhfFPCuYop3z1iS9qQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1689,9 +1686,9 @@ } }, "node_modules/@sveltejs/adapter-static": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", - "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.9.tgz", + "integrity": "sha512-aytHXcMi7lb9ljsWUzXYQ0p5X1z9oWud2olu/EpmH7aCu4m84h7QLvb5Wp+CFirKcwoNnYvYWhyP/L8Vh1ztdw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1699,9 +1696,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.27.0.tgz", - "integrity": "sha512-pEX1Z2Km8tqmkni+ykIIou+ojp/7gb3M9tpllN5nDWNo9zlI0dI8/hDKFyBwQvb4jYR+EyLriFtrmgJ6GvbnBA==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.29.1.tgz", + "integrity": "sha512-0D3dkz5ay5OslGSv8hyZJY892kTw+1G16zFR/ZXbV9XOd6s6Y9wW66vbkr0AtV0BXYtsXCXyt15H/OshAWKfDA==", "dev": true, "license": "MIT", "dependencies": { @@ -1732,13 +1729,13 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.0.tgz", - "integrity": "sha512-+U6lz1wvGEG/BvQyL4z/flyNdQ9xDNv5vrh+vWBWTHaebqT0c9RNggpZTo/XSPoHsSCWBlYaTlRX8pZ9GATXCw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.2.tgz", + "integrity": "sha512-7v+7OkUYelC2dhhYDAgX1qO2LcGscZ18Hi5kKzJQq7tQeXpH215dd0+J/HnX2zM5B3QKcIrTVqCGkZXAy5awYw==", "dev": true, "license": "MIT", "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0-next.1", + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", @@ -2143,13 +2140,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", - "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "version": "24.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz", + "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.8.0" + "undici-types": "~7.10.0" } }, "node_modules/@types/qrcode": { @@ -2581,7 +2578,9 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2600,9 +2599,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", + "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", "dev": true, "funding": [ { @@ -2620,8 +2619,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", + "caniuse-lite": "^1.0.30001733", + "electron-to-chromium": "^1.5.199", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -2701,9 +2700,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001731", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", - "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", "dev": true, "funding": [ { @@ -2742,7 +2741,9 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2783,39 +2784,19 @@ } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" } }, "node_modules/cliui": { @@ -2877,7 +2858,9 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/constantinople": { "version": "4.0.1", @@ -3462,9 +3445,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.194", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.194.tgz", - "integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==", + "version": "1.5.200", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz", + "integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==", "dev": true, "license": "ISC" }, @@ -3518,9 +3501,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3531,32 +3514,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" } }, "node_modules/escalade": { @@ -3583,9 +3566,9 @@ } }, "node_modules/eslint": { - "version": "9.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", - "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dev": true, "license": "MIT", "peer": true, @@ -3593,11 +3576,11 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.15.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.32.0", - "@eslint/plugin-kit": "^0.3.4", + "@eslint/js": "9.33.0", + "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -3731,16 +3714,6 @@ } } }, - "node_modules/eslint-plugin-svelte/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -4340,7 +4313,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -4631,15 +4606,14 @@ } }, "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "license": "Apache-2.0", "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", + "async": "^3.2.6", "filelist": "^1.0.4", - "minimatch": "^3.1.2" + "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" @@ -4921,7 +4895,9 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5983,27 +5959,17 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/require-directory": { @@ -6441,7 +6407,9 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -6462,13 +6430,13 @@ } }, "node_modules/svelte": { - "version": "5.37.3", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.37.3.tgz", - "integrity": "sha512-7t/ejshehHd+95z3Z7ebS7wsqHDQxi/8nBTuTRwpMgNegfRBfuitCSKTUDKIBOExqfT2+DhQ2VLG8Xn+cBXoaQ==", + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.38.1.tgz", + "integrity": "sha512-fO6CLDfJYWHgfo6lQwkQU2vhCiHc2MBl6s3vEhK+sSZru17YL4R5s1v14ndRpqKAIkq8nCz6MTk1yZbESZWeyQ==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", + "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", @@ -6488,9 +6456,9 @@ } }, "node_modules/svelte-check": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.0.tgz", - "integrity": "sha512-Iz8dFXzBNAM7XlEIsUjUGQhbEE+Pvv9odb9+0+ITTgFWZBGeJRRYqHUUglwe2EkLD5LIsQaAc4IUJyvtKuOO5w==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.1.tgz", + "integrity": "sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg==", "dev": true, "license": "MIT", "dependencies": { @@ -6511,36 +6479,6 @@ "typescript": ">=5.0.0" } }, - "node_modules/svelte-check/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/svelte-check/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/svelte-eslint-parser": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.1.tgz", @@ -6741,6 +6679,54 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tailwindcss/node_modules/postcss-load-config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", @@ -6789,6 +6775,30 @@ "node": ">=4" } }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6966,9 +6976,9 @@ } }, "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", "dev": true, "license": "MIT" }, @@ -7374,15 +7384,13 @@ } }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, "engines": { - "node": ">= 14.6" + "node": ">= 6" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index 2225a7a..5426173 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite dev", + "dev:debug": "DEBUG_RELAYS=true vite dev", "dev:node": "node --version && vite dev", "build": "vite build", "preview": "vite preview", diff --git a/src/lib/components/EventSearch.svelte b/src/lib/components/EventSearch.svelte index 67dece6..0304107 100644 --- a/src/lib/components/EventSearch.svelte +++ b/src/lib/components/EventSearch.svelte @@ -10,12 +10,11 @@ searchNip05, } from "$lib/utils/search_utility"; import { neventEncode, naddrEncode, nprofileEncode } from "$lib/utils"; - import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk"; + import { activeInboxRelays, activeOutboxRelays, ndkInstance } from "$lib/ndk"; import { getMatchingTags, toNpub } from "$lib/utils/nostrUtils"; import type { SearchResult } from '$lib/utils/search_types'; import { userStore } from "$lib/stores/userStore"; import { get } from "svelte/store"; - // Props definition let { loading, @@ -52,7 +51,7 @@ let localError = $state(null); let foundEvent = $state(null); let searching = $state(false); - let searchCompleted = $state(false); + let searchCompleted = $state(false); let searchResultCount = $state(null); let searchResultType = $state(null); let isResetting = $state(false); @@ -75,7 +74,10 @@ let isWaitingForSearchResult = $state(false); let isUserEditing = $state(false); - // Move search handler functions above all $effect runes + // Debounced search timeout + let searchTimeout: ReturnType | null = null; + + // AI-NOTE: 2025-01-24 - Core search handlers extracted for better organization async function handleNip05Search(query: string) { try { const foundEvent = await searchNip05(query); @@ -83,42 +85,11 @@ handleFoundEvent(foundEvent); updateSearchState(false, true, 1, "nip05"); } else { - // relayStatuses = {}; // This line was removed as per the edit hint - if (activeSub) { - try { - activeSub.stop(); - } catch (e) { - console.warn("Error stopping subscription:", e); - } - activeSub = null; - } - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } + cleanupSearch(); updateSearchState(false, true, 0, "nip05"); } } catch (error) { - localError = - error instanceof Error ? error.message : "NIP-05 lookup failed"; - // relayStatuses = {}; // This line was removed as per the edit hint - if (activeSub) { - try { - activeSub.stop(); - } catch (e) { - console.warn("Error stopping subscription:", e); - } - activeSub = null; - } - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } - updateSearchState(false, false, null, null); - isProcessingSearch = false; - currentProcessingSearchValue = null; - lastSearchValue = null; - lastSearchValue = null; + handleSearchError(error, "NIP-05 lookup failed"); } } @@ -128,19 +99,7 @@ if (!foundEvent) { console.warn("[Events] Event not found for query:", query); localError = "Event not found"; - // relayStatuses = {}; // This line was removed as per the edit hint - if (activeSub) { - try { - activeSub.stop(); - } catch (e) { - console.warn("Error stopping subscription:", e); - } - activeSub = null; - } - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } + cleanupSearch(); updateSearchState(false, false, null, null); } else { console.log("[Events] Event found:", foundEvent); @@ -148,23 +107,7 @@ updateSearchState(false, true, 1, "event"); } } catch (err) { - console.error("[Events] Error fetching event:", err, "Query:", query); - localError = "Error fetching event. Please check the ID and try again."; - // relayStatuses = {}; // This line was removed as per the edit hint - if (activeSub) { - try { - activeSub.stop(); - } catch (e) { - console.warn("Error stopping subscription:", e); - } - activeSub = null; - } - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } - updateSearchState(false, false, null, null); - isProcessingSearch = false; + handleSearchError(err, "Error fetching event. Please check the ID and try again."); } } @@ -176,72 +119,123 @@ console.log("EventSearch: Already searching, skipping"); return; } + resetSearchState(); localError = null; - updateSearchState(true); + updateSearchState(true, false); isResetting = false; - isUserEditing = false; // Reset user editing flag when search starts - const query = ( - queryOverride !== undefined ? queryOverride || "" : searchQuery || "" - ).trim(); + isUserEditing = false; + + const query = (queryOverride !== undefined ? queryOverride || "" : searchQuery || "").trim(); if (!query) { updateSearchState(false, false, null, null); return; } - if (query.toLowerCase().startsWith("d:")) { + + // Handle different search types + const searchType = getSearchType(query); + if (searchType) { + await handleSearchByType(searchType, query, clearInput); + return; + } + + if (clearInput) { + navigateToSearch(query, "id"); + } + await handleEventSearch(query); + } + + // AI-NOTE: 2025-01-24 - Helper functions for better code organization + function getSearchType(query: string): { type: string; term: string } | null { + const lowerQuery = query.toLowerCase(); + + if (lowerQuery.startsWith("d:")) { const dTag = query.slice(2).trim().toLowerCase(); - if (dTag) { - console.log("EventSearch: Processing d-tag search:", dTag); - navigateToSearch(dTag, "d"); - updateSearchState(false, false, null, null); - return; - } + return dTag ? { type: "d", term: dTag } : null; } - if (query.toLowerCase().startsWith("t:")) { + + if (lowerQuery.startsWith("t:")) { const searchTerm = query.slice(2).trim(); - if (searchTerm) { - await handleSearchBySubscription("t", searchTerm); - return; - } + return searchTerm ? { type: "t", term: searchTerm } : null; } - if (query.toLowerCase().startsWith("n:")) { + + if (lowerQuery.startsWith("n:")) { const searchTerm = query.slice(2).trim(); - if (searchTerm) { - await handleSearchBySubscription("n", searchTerm); - return; - } + return searchTerm ? { type: "n", term: searchTerm } : null; } + if (query.includes("@")) { - await handleNip05Search(query); + return { type: "nip05", term: query }; + } + + return null; + } + + async function handleSearchByType( + searchType: { type: string; term: string }, + query: string, + clearInput: boolean + ) { + const { type, term } = searchType; + + if (type === "d") { + console.log("EventSearch: Processing d-tag search:", term); + navigateToSearch(term, "d"); + updateSearchState(false, false, null, null); return; } - if (clearInput) { - navigateToSearch(query, "id"); - // Don't clear searchQuery here - let the effect handle it + + if (type === "nip05") { + await handleNip05Search(term); + return; } - await handleEventSearch(query); + + if (type === "t" || type === "n") { + await handleSearchBySubscription(type as "t" | "n", term); + return; + } + } + + function handleSearchError(error: unknown, defaultMessage: string) { + localError = error instanceof Error ? error.message : defaultMessage; + cleanupSearch(); + updateSearchState(false, false, null, null); + isProcessingSearch = false; + currentProcessingSearchValue = null; + lastSearchValue = null; } - // Keep searchQuery in sync with searchValue and dTagValue props + function cleanupSearch() { + if (activeSub) { + try { + activeSub.stop(); + } catch (e) { + console.warn("Error stopping subscription:", e); + } + activeSub = null; + } + + if (currentAbortController) { + currentAbortController.abort(); + currentAbortController = null; + } + } + + // AI-NOTE: 2025-01-24 - Effects organized for better readability $effect(() => { - // Only sync if we're not currently searching, resetting, or if the user is editing if (searching || isResetting || isUserEditing) { return; } if (dTagValue) { - // If dTagValue is set, show it as "d:tag" in the search bar searchQuery = `d:${dTagValue}`; } else if (searchValue) { - // searchValue should already be in the correct format (t:, n:, d:, etc.) searchQuery = searchValue; } else if (!searchQuery) { - // Only clear if searchQuery is empty to avoid clearing user input searchQuery = ""; } }); - // Debounced effect to handle searchValue changes $effect(() => { if ( !searchValue || @@ -253,76 +247,19 @@ return; } - // Check if we've already processed this searchValue if (searchValue === lastProcessedSearchValue) { return; } - // If we already have the event for this searchValue, do nothing - if (foundEvent) { - const currentEventId = foundEvent.id; - let currentNaddr = null; - let currentNevent = null; - let currentNpub = null; - try { - currentNevent = neventEncode(foundEvent, $activeInboxRelays); - } catch {} - try { - currentNaddr = getMatchingTags(foundEvent, "d")[0]?.[1] - ? naddrEncode(foundEvent, $activeInboxRelays) - : null; - } catch {} - try { - currentNpub = foundEvent.kind === 0 ? toNpub(foundEvent.pubkey) : null; - } catch {} - - // Debug log for comparison - console.log( - "[EventSearch effect] searchValue:", - searchValue, - "foundEvent.id:", - currentEventId, - "foundEvent.pubkey:", - foundEvent.pubkey, - "toNpub(pubkey):", - currentNpub, - "foundEvent.kind:", - foundEvent.kind, - "currentNaddr:", - currentNaddr, - "currentNevent:", - currentNevent, - ); - - // Also check if searchValue is an nprofile and matches the current event's pubkey - let currentNprofile = null; - if ( - searchValue && - searchValue.startsWith("nprofile1") && - foundEvent.kind === 0 - ) { - try { - currentNprofile = nprofileEncode(foundEvent.pubkey, $activeInboxRelays); - } catch {} - } - - if ( - searchValue === currentEventId || - (currentNaddr && searchValue === currentNaddr) || - (currentNevent && searchValue === currentNevent) || - (currentNpub && searchValue === currentNpub) || - (currentNprofile && searchValue === currentNprofile) - ) { - // Already displaying the event for this searchValue - lastProcessedSearchValue = searchValue; - return; - } + if (foundEvent && isCurrentEventMatch(searchValue, foundEvent)) { + lastProcessedSearchValue = searchValue; + return; } - // Otherwise, trigger a search for the new value if (searchTimeout) { clearTimeout(searchTimeout); } + searchTimeout = setTimeout(() => { isProcessingSearch = true; isWaitingForSearchResult = true; @@ -333,10 +270,6 @@ }, 300); }); - // Add debouncing to prevent rapid successive searches - let searchTimeout: ReturnType | null = null; - - // Cleanup function to clear timeout when component is destroyed $effect(() => { return () => { if (searchTimeout) { @@ -345,7 +278,6 @@ }; }); - // Simple effect to handle dTagValue changes $effect(() => { if ( dTagValue && @@ -356,7 +288,6 @@ console.log("EventSearch: Processing dTagValue:", dTagValue); lastProcessedDTagValue = dTagValue; - // Add a small delay to prevent rapid successive calls setTimeout(() => { if (!searching && !isResetting) { handleSearchBySubscription("d", dTagValue); @@ -365,14 +296,53 @@ } }); - // Simple effect to handle event prop changes $effect(() => { if (event && !searching && !isResetting) { foundEvent = event; } }); - // Search utility functions + // AI-NOTE: 2025-01-24 - Utility functions for event matching and state management + function isCurrentEventMatch(searchValue: string, event: NDKEvent): boolean { + const currentEventId = event.id; + let currentNaddr: string | null = null; + let currentNevent: string | null = null; + let currentNpub: string | null = null; + let currentNprofile: string | null = null; + + try { + currentNevent = neventEncode(event, $activeInboxRelays); + } catch {} + + try { + currentNaddr = getMatchingTags(event, "d")[0]?.[1] + ? naddrEncode(event, $activeInboxRelays) + : null; + } catch {} + + try { + currentNpub = event.kind === 0 ? toNpub(event.pubkey) : null; + } catch {} + + if ( + searchValue && + searchValue.startsWith("nprofile1") && + event.kind === 0 + ) { + try { + currentNprofile = nprofileEncode(event.pubkey, $activeInboxRelays); + } catch {} + } + + return !!( + searchValue === currentEventId || + (currentNaddr && searchValue === currentNaddr) || + (currentNevent && searchValue === currentNevent) || + (currentNpub && searchValue === currentNpub) || + (currentNprofile && searchValue === currentNprofile) + ); + } + function updateSearchState( isSearching: boolean, completed: boolean = false, @@ -399,32 +369,14 @@ lastSearchValue = null; updateSearchState(false, false, null, null); - // Cancel ongoing search - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } - - // Clean up subscription - if (activeSub) { - try { - activeSub.stop(); - } catch (e) { - console.warn("Error stopping subscription:", e); - } - activeSub = null; - } - - // Clear search results + cleanupSearch(); onSearchResults([], [], [], new Set(), new Set()); - // Clear any pending timeout if (searchTimeout) { clearTimeout(searchTimeout); searchTimeout = null; } - // Reset the flag after a short delay to allow effects to settle setTimeout(() => { isResetting = false; }, 100); @@ -432,37 +384,20 @@ function handleFoundEvent(event: NDKEvent) { foundEvent = event; - localError = null; // Clear local error when event is found - - // Stop any ongoing subscription - if (activeSub) { - try { - activeSub.stop(); - } catch (e) { - console.warn("Error stopping subscription:", e); - } - activeSub = null; - } + localError = null; - // Abort any ongoing fetch - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } + cleanupSearch(); - // Clear search state searching = false; searchCompleted = true; searchResultCount = 1; searchResultType = "event"; - // Update last processed search value to prevent re-processing if (searchValue) { lastProcessedSearchValue = searchValue; lastSearchValue = searchValue; } - // Reset processing flag isProcessingSearch = false; currentProcessingSearchValue = null; isWaitingForSearchResult = false; @@ -479,7 +414,7 @@ }); } - // Search handlers + // AI-NOTE: 2025-01-24 - Main subscription search handler with improved error handling async function handleSearchBySubscription( searchType: "d" | "t" | "n", searchTerm: string, @@ -489,236 +424,226 @@ searchTerm, }); - // AI-NOTE: 2025-01-24 - Check cache first for profile searches to provide immediate response if (searchType === "n") { - try { - const { getUserMetadata } = await import("$lib/utils/nostrUtils"); - const cachedProfile = await getUserMetadata(searchTerm, false); - if (cachedProfile && cachedProfile.name) { - console.log("EventSearch: Found cached profile, displaying immediately:", cachedProfile); - - // Create a mock NDKEvent for the cached profile - const { NDKEvent } = await import("@nostr-dev-kit/ndk"); - const { nip19 } = await import("$lib/utils/nostrUtils"); - - // Decode the npub to get the actual pubkey - let pubkey = searchTerm; + const cachedResult = await handleCachedProfileSearch(searchTerm); + if (cachedResult) { + return; + } + } + + isResetting = false; + localError = null; + updateSearchState(true, false); + + await waitForRelays(); + + try { + await performSubscriptionSearch(searchType, searchTerm); + } catch (error) { + handleSubscriptionSearchError(error); + } + } + + async function handleCachedProfileSearch(searchTerm: string): Promise { + if (!searchTerm.startsWith("npub") && !searchTerm.startsWith("nprofile")) { + return false; + } + + try { + const { getUserMetadata } = await import("$lib/utils/nostrUtils"); + const cachedProfile = await getUserMetadata(searchTerm, false); + + if (cachedProfile && cachedProfile.name) { + const mockEvent = await createMockProfileEvent(searchTerm, cachedProfile); + handleFoundEvent(mockEvent); + updateSearchState(false, true, 1, "profile-cached"); + + setTimeout(async () => { try { - const decoded = nip19.decode(searchTerm); - if (decoded && decoded.type === "npub") { - pubkey = decoded.data; - } + await performBackgroundProfileSearch("n", searchTerm); } catch (error) { - console.warn("EventSearch: Failed to decode npub for mock event:", error); + console.warn("EventSearch: Background profile search failed:", error); } - - const mockEvent = new NDKEvent(undefined, { - kind: 0, - pubkey: pubkey, - content: JSON.stringify(cachedProfile), - tags: [], - created_at: Math.floor(Date.now() / 1000), - id: "", // Will be computed by NDK - sig: "", // Will be computed by NDK - }); - - // Display the cached profile immediately - handleFoundEvent(mockEvent); - updateSearchState(false, true, 1, "profile-cached"); - - // AI-NOTE: 2025-01-24 - Still perform background search for second-order events - // but with better timeout handling to prevent hanging - setTimeout(async () => { - try { - await performBackgroundProfileSearch(searchType, searchTerm); - } catch (error) { - console.warn("EventSearch: Background profile search failed:", error); - } - }, 100); - - return; - } - } catch (error) { - console.warn("EventSearch: Cache check failed, proceeding with subscription search:", error); + }, 100); + + return true; } + } catch (error) { + console.warn("EventSearch: Cache check failed, proceeding with subscription search:", error); } + + return false; + } - isResetting = false; // Allow effects to run for new searches - localError = null; - updateSearchState(true); + async function createMockProfileEvent(searchTerm: string, profile: any): Promise { + const { NDKEvent } = await import("@nostr-dev-kit/ndk"); + const { nip19 } = await import("$lib/utils/nostrUtils"); - // Wait for relays to be available (with timeout) + let pubkey = searchTerm; + try { + const decoded = nip19.decode(searchTerm); + if (decoded && decoded.type === "npub") { + pubkey = decoded.data; + } + } catch (error) { + console.warn("EventSearch: Failed to decode npub for mock event:", error); + } + + return new NDKEvent(undefined, { + kind: 0, + pubkey: pubkey, + content: JSON.stringify(profile), + tags: [], + created_at: Math.floor(Date.now() / 1000), + id: "", + sig: "", + }); + } + + async function waitForRelays(): Promise { let retryCount = 0; - const maxRetries = 20; // Wait up to 10 seconds (20 * 500ms) for user login to complete + const maxRetries = 10; // Reduced retry count since we'll use all available relays - while ($activeInboxRelays.length === 0 && $activeOutboxRelays.length === 0 && retryCount < maxRetries) { + // AI-NOTE: 2025-01-24 - Wait for any relays to be available, not just specific types + // This ensures searches can proceed even if some relay types are not available + while (retryCount < maxRetries) { + // Check if we have any relays in the NDK pool + const ndk = get(ndkInstance); + if (ndk && ndk.pool && ndk.pool.relays && ndk.pool.relays.size > 0) { + console.debug(`EventSearch: Found ${ndk.pool.relays.size} relays in NDK pool`); + break; + } + + // Also check active relay stores as fallback + if ($activeInboxRelays.length > 0 || $activeOutboxRelays.length > 0) { + console.debug(`EventSearch: Found active relays - inbox: ${$activeInboxRelays.length}, outbox: ${$activeOutboxRelays.length}`); + break; + } + console.debug(`EventSearch: Waiting for relays... (attempt ${retryCount + 1}/${maxRetries})`); - await new Promise(resolve => setTimeout(resolve, 500)); // Wait 500ms + await new Promise(resolve => setTimeout(resolve, 500)); retryCount++; } - // Additional wait for user-specific relays if user is logged in - const currentUser = get(userStore); - if (currentUser.signedIn && currentUser.pubkey) { - console.debug(`EventSearch: User is logged in (${currentUser.pubkey}), waiting for user-specific relays...`); - retryCount = 0; - while ($activeOutboxRelays.length <= 9 && retryCount < maxRetries) { - // If we still have the default relay count (9), wait for user-specific relays - console.debug(`EventSearch: Waiting for user-specific relays... (attempt ${retryCount + 1}/${maxRetries})`); - await new Promise(resolve => setTimeout(resolve, 500)); - retryCount++; - } + // AI-NOTE: 2025-01-24 - Don't fail if no relays are available, let the search functions handle fallbacks + // The search functions will use all available relays including fallback relays + const ndk = get(ndkInstance); + const poolRelayCount = ndk?.pool?.relays?.size || 0; + + console.log("EventSearch: Relay status for search:", { + poolRelayCount, + inboxCount: $activeInboxRelays.length, + outboxCount: $activeOutboxRelays.length, + willUseAllRelays: poolRelayCount > 0 || $activeInboxRelays.length > 0 || $activeOutboxRelays.length > 0 + }); + + // If we have any relays available, proceed with search + if (poolRelayCount > 0 || $activeInboxRelays.length > 0 || $activeOutboxRelays.length > 0) { + console.log("EventSearch: Relays available, proceeding with search"); + } else { + console.warn("EventSearch: No relays detected, but proceeding with search - fallback relays will be used"); } + } + + async function performSubscriptionSearch(searchType: "d" | "t" | "n", searchTerm: string): Promise { + if (currentAbortController) { + currentAbortController.abort(); + } + currentAbortController = new AbortController(); - // Check if we have any relays available - if ($activeInboxRelays.length === 0 && $activeOutboxRelays.length === 0) { - console.warn("EventSearch: No relays available after waiting, failing search"); - localError = "No relays available. Please check your connection and try again."; - updateSearchState(false, false, null, null); + const searchPromise = searchBySubscription( + searchType, + searchTerm, + { + onSecondOrderUpdate: (updatedResult) => { + console.log("EventSearch: Second order update:", updatedResult); + onSearchResults( + updatedResult.events, + updatedResult.secondOrder, + updatedResult.tTagEvents, + updatedResult.eventIds, + updatedResult.addresses, + updatedResult.searchType, + updatedResult.searchTerm, + ); + }, + onSubscriptionCreated: (sub) => { + console.log("EventSearch: Subscription created:", sub); + if (activeSub) { + activeSub.stop(); + } + activeSub = sub; + }, + }, + currentAbortController.signal, + ); + + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error("Search timeout: No results received within 30 seconds")); + }, 30000); + }); + + const result = await Promise.race([searchPromise, timeoutPromise]) as any; + console.log("EventSearch: Search completed:", result); + + onSearchResults( + result.events, + result.secondOrder, + result.tTagEvents, + result.eventIds, + result.addresses, + result.searchType, + result.searchTerm, + ); + + const totalCount = result.events.length + result.secondOrder.length + result.tTagEvents.length; + localError = null; + + cleanupSearch(); + updateSearchState(false, true, totalCount, searchType); + isProcessingSearch = false; + currentProcessingSearchValue = null; + isWaitingForSearchResult = false; + + if (searchValue) { + lastProcessedSearchValue = searchValue; + } + } + + function handleSubscriptionSearchError(error: unknown): void { + if (error instanceof Error && error.message === "Search cancelled") { isProcessingSearch = false; currentProcessingSearchValue = null; isWaitingForSearchResult = false; - searching = false; return; } - console.log("EventSearch: Relays available, proceeding with search:", { - inboxCount: $activeInboxRelays.length, - outboxCount: $activeOutboxRelays.length - }); + console.error("EventSearch: Search failed:", error); - try { - // Cancel existing search - if (currentAbortController) { - currentAbortController.abort(); - } - currentAbortController = new AbortController(); - // Add a timeout to prevent hanging searches - const searchPromise = searchBySubscription( - searchType, - searchTerm, - { - onSecondOrderUpdate: (updatedResult) => { - console.log("EventSearch: Second order update:", updatedResult); - onSearchResults( - updatedResult.events, - updatedResult.secondOrder, - updatedResult.tTagEvents, - updatedResult.eventIds, - updatedResult.addresses, - updatedResult.searchType, - updatedResult.searchTerm, - ); - }, - onSubscriptionCreated: (sub) => { - console.log("EventSearch: Subscription created:", sub); - if (activeSub) { - activeSub.stop(); - } - activeSub = sub; - }, - }, - currentAbortController.signal, - ); - - // Add a 30-second timeout - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => { - reject(new Error("Search timeout: No results received within 30 seconds")); - }, 30000); - }); - - const result = await Promise.race([searchPromise, timeoutPromise]) as any; - console.log("EventSearch: Search completed:", result); - onSearchResults( - result.events, - result.secondOrder, - result.tTagEvents, - result.eventIds, - result.addresses, - result.searchType, - result.searchTerm, - ); - const totalCount = - result.events.length + - result.secondOrder.length + - result.tTagEvents.length; - localError = null; // Clear local error when search completes - // Stop any ongoing subscription - if (activeSub) { - try { - activeSub.stop(); - } catch (e) { - console.warn("Error stopping subscription:", e); - } - activeSub = null; - } - // Abort any ongoing fetch - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } - updateSearchState(false, true, totalCount, searchType); - isProcessingSearch = false; - currentProcessingSearchValue = null; - isWaitingForSearchResult = false; - - // Update last processed search value to prevent re-processing - if (searchValue) { - lastProcessedSearchValue = searchValue; - } - } catch (error) { - if (error instanceof Error && error.message === "Search cancelled") { - isProcessingSearch = false; - currentProcessingSearchValue = null; - isWaitingForSearchResult = false; - return; - } - console.error("EventSearch: Search failed:", error); - localError = error instanceof Error ? error.message : "Search failed"; - // Provide more specific error messages for different failure types - if (error instanceof Error) { - if ( - error.message.includes("timeout") || - error.message.includes("connection") - ) { - localError = - "Search timed out. The relays may be temporarily unavailable. Please try again."; - } else if (error.message.includes("NDK not initialized")) { - localError = - "Nostr client not initialized. Please refresh the page and try again."; - } else { - localError = `Search failed: ${error.message}`; - } - } - localError = null; // Clear local error when search fails - // Stop any ongoing subscription - if (activeSub) { - try { - activeSub.stop(); - } catch (e) { - console.warn("Error stopping subscription:", e); - } - activeSub = null; - } - // Abort any ongoing fetch - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } - updateSearchState(false, false, null, null); - isProcessingSearch = false; - currentProcessingSearchValue = null; - isWaitingForSearchResult = false; - - // Update last processed search value to prevent re-processing even on error - if (searchValue) { - lastProcessedSearchValue = searchValue; + if (error instanceof Error) { + if (error.message.includes("timeout") || error.message.includes("connection")) { + localError = "Search timed out. The relays may be temporarily unavailable. Please try again."; + } else if (error.message.includes("NDK not initialized")) { + localError = "Nostr client not initialized. Please refresh the page and try again."; + } else { + localError = `Search failed: ${error.message}`; } + } else { + localError = "Search failed"; + } + + cleanupSearch(); + updateSearchState(false, false, null, null); + isProcessingSearch = false; + currentProcessingSearchValue = null; + isWaitingForSearchResult = false; + + if (searchValue) { + lastProcessedSearchValue = searchValue; } } - // AI-NOTE: 2025-01-24 - Function to perform background profile search without blocking UI async function performBackgroundProfileSearch( searchType: "d" | "t" | "n", searchTerm: string, @@ -729,17 +654,15 @@ }); try { - // Cancel existing search if (currentAbortController) { currentAbortController.abort(); } currentAbortController = new AbortController(); - // AI-NOTE: 2025-01-24 - Add timeout to prevent hanging background searches const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error("Background search timeout")); - }, 10000); // 10 second timeout for background searches + }, 10000); }); const searchPromise = searchBySubscription( @@ -748,7 +671,6 @@ { onSecondOrderUpdate: (updatedResult) => { console.log("EventSearch: Background second order update:", updatedResult); - // Only update if we have new results if (updatedResult.events.length > 0) { onSearchResults( updatedResult.events, @@ -772,12 +694,10 @@ currentAbortController.signal, ); - // Race between search and timeout const result = await Promise.race([searchPromise, timeoutPromise]) as any; console.log("EventSearch: Background search completed:", result); - // Only update results if we have new data if (result.events.length > 0) { onSearchResults( result.events, @@ -794,21 +714,18 @@ } } - // Search utility functions function handleClear() { isResetting = true; searchQuery = ""; - isUserEditing = false; // Reset user editing flag + isUserEditing = false; resetSearchState(); - // Clear URL parameters to reset the page goto("", { replaceState: true, keepFocus: true, noScroll: true, }); - // Ensure all search state is cleared searching = false; searchCompleted = false; searchResultCount = null; @@ -820,7 +737,6 @@ lastSearchValue = null; isWaitingForSearchResult = false; - // Clear any pending timeout if (searchTimeout) { clearTimeout(searchTimeout); searchTimeout = null; @@ -830,7 +746,6 @@ onClear(); } - // Reset the flag after a short delay to allow effects to settle setTimeout(() => { isResetting = false; }, 100); diff --git a/src/lib/components/publications/PublicationFeed.svelte b/src/lib/components/publications/PublicationFeed.svelte index 931cf9d..d913de0 100644 --- a/src/lib/components/publications/PublicationFeed.svelte +++ b/src/lib/components/publications/PublicationFeed.svelte @@ -77,6 +77,8 @@ }); // Initialize relays and fetch events + // AI-NOTE: This function is called when the component mounts and when relay configuration changes + // It ensures that events are fetched from the current set of active relays async function initializeAndFetch() { if (!ndk) { console.debug('[PublicationFeed] No NDK instance available'); @@ -122,11 +124,12 @@ } } - // Watch for relay store changes + // Watch for relay store changes and user authentication state $effect(() => { const inboxRelays = $activeInboxRelays; const outboxRelays = $activeOutboxRelays; const newRelays = [...inboxRelays, ...outboxRelays]; + const userState = $userStore; if (newRelays.length > 0 && !hasInitialized) { console.debug('[PublicationFeed] Relays available, initializing'); @@ -145,6 +148,18 @@ initializeAndFetch(); }, 3000); } + } else if (hasInitialized && newRelays.length > 0) { + // AI-NOTE: Re-fetch events when user authentication state changes or relays are updated + // This ensures that when a user logs in and their relays are loaded, we fetch events from those relays + const currentRelaysString = allRelays.sort().join(','); + const newRelaysString = newRelays.sort().join(','); + + if (currentRelaysString !== newRelaysString) { + console.debug('[PublicationFeed] Relay configuration changed, re-fetching events'); + // Clear cache to force fresh fetch from new relays + indexEventCache.clear(); + setTimeout(() => initializeAndFetch(), 0); + } } }); @@ -513,6 +528,31 @@ debouncedSearch(props.searchQuery); }); + // AI-NOTE: Watch for user authentication state changes to re-fetch events when user logs in/out + $effect(() => { + const userState = $userStore; + + if (hasInitialized && userState.signedIn) { + console.debug('[PublicationFeed] User signed in, checking if we need to re-fetch events'); + // Check if we have user-specific relays that we haven't fetched from yet + const inboxRelays = $activeInboxRelays; + const outboxRelays = $activeOutboxRelays; + const newRelays = [...inboxRelays, ...outboxRelays]; + + if (newRelays.length > 0) { + const currentRelaysString = allRelays.sort().join(','); + const newRelaysString = newRelays.sort().join(','); + + if (currentRelaysString !== newRelaysString) { + console.debug('[PublicationFeed] User logged in with new relays, re-fetching events'); + // Clear cache to force fresh fetch from user's relays + indexEventCache.clear(); + setTimeout(() => initializeAndFetch(), 0); + } + } + } + }); + // AI-NOTE: Watch for changes in the user filter checkbox $effect(() => { // Trigger filtering when the user filter checkbox changes diff --git a/src/lib/ndk.ts b/src/lib/ndk.ts index 55afb4d..fed11c6 100644 --- a/src/lib/ndk.ts +++ b/src/lib/ndk.ts @@ -368,7 +368,10 @@ function ensureSecureWebSocket(url: string): string { */ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { try { - console.debug(`[NDK.ts] Creating relay with URL: ${url}`); + // Reduce verbosity in development - only log relay creation if debug mode is enabled + if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) { + console.debug(`[NDK.ts] Creating relay with URL: ${url}`); + } // Ensure the URL is using appropriate protocol const secureUrl = ensureSecureWebSocket(url); @@ -383,7 +386,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { // Set up connection timeout const connectionTimeout = setTimeout(() => { try { - console.warn(`[NDK.ts] Connection timeout for ${secureUrl}`); + // Only log connection timeouts if debug mode is enabled + if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) { + console.debug(`[NDK.ts] Connection timeout for ${secureUrl}`); + } relay.disconnect(); } catch { // Silently ignore disconnect errors @@ -395,7 +401,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { const authPolicy = new CustomRelayAuthPolicy(ndk); relay.on("connect", () => { try { - console.debug(`[NDK.ts] Relay connected: ${secureUrl}`); + // Only log successful connections if debug mode is enabled + if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) { + console.debug(`[NDK.ts] Relay connected: ${secureUrl}`); + } clearTimeout(connectionTimeout); authPolicy.authenticate(relay); } catch { @@ -405,7 +414,10 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { } else { relay.on("connect", () => { try { - console.debug(`[NDK.ts] Relay connected: ${secureUrl}`); + // Only log successful connections if debug mode is enabled + if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) { + console.debug(`[NDK.ts] Relay connected: ${secureUrl}`); + } clearTimeout(connectionTimeout); } catch { // Silently handle connect handler errors @@ -513,7 +525,10 @@ export async function updateActiveRelayStores(ndk: NDK, forceUpdate: boolean = f // Add relays to NDK pool (deduplicated) const allRelayUrls = deduplicateRelayUrls([...relaySet.inboxRelays, ...relaySet.outboxRelays]); - console.debug('[NDK.ts] updateActiveRelayStores: Adding', allRelayUrls.length, 'relays to NDK pool'); + // Reduce verbosity in development - only log relay addition if debug mode is enabled + if (process.env.NODE_ENV === 'development' && process.env.DEBUG_RELAYS) { + console.debug('[NDK.ts] updateActiveRelayStores: Adding', allRelayUrls.length, 'relays to NDK pool'); + } for (const url of allRelayUrls) { try { diff --git a/src/lib/services/event_search_service.ts b/src/lib/services/event_search_service.ts new file mode 100644 index 0000000..76ee6ca --- /dev/null +++ b/src/lib/services/event_search_service.ts @@ -0,0 +1,84 @@ +/** + * Service class for handling event search operations + * AI-NOTE: 2025-01-24 - Extracted from EventSearch component for better separation of concerns + */ +export class EventSearchService { + /** + * Determines the search type from a query string + */ + getSearchType(query: string): { type: string; term: string } | null { + const lowerQuery = query.toLowerCase(); + + if (lowerQuery.startsWith("d:")) { + const dTag = query.slice(2).trim().toLowerCase(); + return dTag ? { type: "d", term: dTag } : null; + } + + if (lowerQuery.startsWith("t:")) { + const searchTerm = query.slice(2).trim(); + return searchTerm ? { type: "t", term: searchTerm } : null; + } + + if (lowerQuery.startsWith("n:")) { + const searchTerm = query.slice(2).trim(); + return searchTerm ? { type: "n", term: searchTerm } : null; + } + + if (query.includes("@")) { + return { type: "nip05", term: query }; + } + + return null; + } + + /** + * Checks if a search value matches the current event + */ + isCurrentEventMatch(searchValue: string, event: any, relays: string[]): boolean { + const currentEventId = event.id; + let currentNaddr = null; + let currentNevent = null; + let currentNpub = null; + let currentNprofile = null; + + try { + const { neventEncode, naddrEncode, nprofileEncode } = require("$lib/utils"); + const { getMatchingTags, toNpub } = require("$lib/utils/nostrUtils"); + + currentNevent = neventEncode(event, relays); + } catch {} + + try { + const { naddrEncode } = require("$lib/utils"); + const { getMatchingTags } = require("$lib/utils/nostrUtils"); + + currentNaddr = getMatchingTags(event, "d")[0]?.[1] + ? naddrEncode(event, relays) + : null; + } catch {} + + try { + const { toNpub } = require("$lib/utils/nostrUtils"); + currentNpub = event.kind === 0 ? toNpub(event.pubkey) : null; + } catch {} + + if ( + searchValue && + searchValue.startsWith("nprofile1") && + event.kind === 0 + ) { + try { + const { nprofileEncode } = require("$lib/utils"); + currentNprofile = nprofileEncode(event.pubkey, relays); + } catch {} + } + + return ( + searchValue === currentEventId || + (currentNaddr && searchValue === currentNaddr) || + (currentNevent && searchValue === currentNevent) || + (currentNpub && searchValue === currentNpub) || + (currentNprofile && searchValue === currentNprofile) + ); + } +} diff --git a/src/lib/services/search_state_manager.ts b/src/lib/services/search_state_manager.ts new file mode 100644 index 0000000..d673b9d --- /dev/null +++ b/src/lib/services/search_state_manager.ts @@ -0,0 +1,62 @@ +/** + * Service class for managing search state operations + * AI-NOTE: 2025-01-24 - Extracted from EventSearch component for better separation of concerns + */ +export class SearchStateManager { + /** + * Updates the search state with new values + */ + updateSearchState( + state: { + searching: boolean; + searchCompleted: boolean; + searchResultCount: number | null; + searchResultType: string | null; + }, + onLoadingChange?: (loading: boolean) => void + ): void { + if (onLoadingChange) { + onLoadingChange(state.searching); + } + } + + /** + * Resets all search state to initial values + */ + resetSearchState( + callbacks: { + onSearchResults: (events: any[], secondOrder: any[], tTagEvents: any[], eventIds: Set, addresses: Set) => void; + cleanupSearch: () => void; + clearTimeout: () => void; + } + ): void { + callbacks.cleanupSearch(); + callbacks.onSearchResults([], [], [], new Set(), new Set()); + callbacks.clearTimeout(); + } + + /** + * Handles search errors with consistent error handling + */ + handleSearchError( + error: unknown, + defaultMessage: string, + callbacks: { + setLocalError: (error: string | null) => void; + cleanupSearch: () => void; + updateSearchState: (state: any) => void; + resetProcessingFlags: () => void; + } + ): void { + const errorMessage = error instanceof Error ? error.message : defaultMessage; + callbacks.setLocalError(errorMessage); + callbacks.cleanupSearch(); + callbacks.updateSearchState({ + searching: false, + searchCompleted: false, + searchResultCount: null, + searchResultType: null + }); + callbacks.resetProcessingFlags(); + } +} diff --git a/src/lib/utils/event_search.ts b/src/lib/utils/event_search.ts index f15b9b3..5407be4 100644 --- a/src/lib/utils/event_search.ts +++ b/src/lib/utils/event_search.ts @@ -6,6 +6,7 @@ import type { Filter } from "./search_types.ts"; import { get } from "svelte/store"; import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts"; import { TIMEOUTS, VALIDATION } from "./search_constants.ts"; +import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts"; /** * Search for a single event by ID or filter @@ -17,18 +18,35 @@ export async function searchEvent(query: string): Promise { return null; } - // Wait for relays to be available + // AI-NOTE: 2025-01-24 - Wait for any relays to be available, not just pool relays + // This ensures searches can proceed even if some relay types are not available let attempts = 0; - const maxAttempts = 10; - while (ndk.pool.relays.size === 0 && attempts < maxAttempts) { + const maxAttempts = 5; // Reduced since we'll use fallback relays + + while (attempts < maxAttempts) { + // Check if we have any relays in the pool + if (ndk.pool.relays.size > 0) { + console.log(`[Search] Found ${ndk.pool.relays.size} relays in NDK pool`); + break; + } + + // Also check if we have any active relays + const inboxRelays = get(activeInboxRelays); + const outboxRelays = get(activeOutboxRelays); + if (inboxRelays.length > 0 || outboxRelays.length > 0) { + console.log(`[Search] Found active relays - inbox: ${inboxRelays.length}, outbox: ${outboxRelays.length}`); + break; + } + console.log(`[Search] Waiting for relays to be available (attempt ${attempts + 1}/${maxAttempts})`); await new Promise(resolve => setTimeout(resolve, 500)); attempts++; } + // AI-NOTE: 2025-01-24 - Don't fail if no relays are available, let fetchEventWithFallback handle fallbacks + // The fetchEventWithFallback function will use all available relays including fallback relays if (ndk.pool.relays.size === 0) { - console.warn("[Search] No relays available after waiting"); - return null; + console.warn("[Search] No relays in pool, but proceeding with search - fallback relays will be used"); } // Clean the query and normalize to lowercase diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index 06ae2bf..7f2fbb2 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -5,7 +5,7 @@ import { npubCache } from "./npubCache.ts"; import NDK, { NDKEvent, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk"; import type { NDKKind, NostrEvent } from "@nostr-dev-kit/ndk"; import type { Filter } from "./search_types.ts"; -import { communityRelays, secondaryRelays, searchRelays } from "../consts.ts"; +import { communityRelays, secondaryRelays, searchRelays, anonymousRelays } from "../consts.ts"; import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts"; import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk"; import { sha256 } from "@noble/hashes/sha2.js"; @@ -443,19 +443,27 @@ export async function fetchEventWithFallback( filterOrId: string | Filter, timeoutMs: number = 3000, ): Promise { - // Use both inbox and outbox relays for better event discovery + // AI-NOTE: 2025-01-24 - Use ALL available relays for comprehensive event discovery + // This ensures we don't miss events that might be on any available relay + + // Get all relays from NDK pool first (most comprehensive) + const poolRelays = Array.from(ndk.pool.relays.values()).map((r: any) => r.url); const inboxRelays = get(activeInboxRelays); const outboxRelays = get(activeOutboxRelays); - let allRelays = [...inboxRelays, ...outboxRelays]; + // Combine all available relays, prioritizing pool relays + let allRelays = [...new Set([...poolRelays, ...inboxRelays, ...outboxRelays])]; + + console.log("fetchEventWithFallback: Using pool relays:", poolRelays); console.log("fetchEventWithFallback: Using inbox relays:", inboxRelays); console.log("fetchEventWithFallback: Using outbox relays:", outboxRelays); + console.log("fetchEventWithFallback: Total unique relays:", allRelays.length); // Check if we have any relays available if (allRelays.length === 0) { console.warn("fetchEventWithFallback: No relays available for event fetch, using fallback relays"); // Use fallback relays when no relays are available - allRelays = [...secondaryRelays, ...searchRelays]; + allRelays = [...secondaryRelays, ...searchRelays, ...anonymousRelays]; console.log("fetchEventWithFallback: Using fallback relays:", allRelays); } diff --git a/src/lib/utils/search_result_formatter.ts b/src/lib/utils/search_result_formatter.ts new file mode 100644 index 0000000..3488b83 --- /dev/null +++ b/src/lib/utils/search_result_formatter.ts @@ -0,0 +1,26 @@ +/** + * Utility class for formatting search result messages + * AI-NOTE: 2025-01-24 - Extracted from EventSearch component for better separation of concerns + */ +export class SearchResultFormatter { + /** + * Formats a result message based on search count and type + */ + formatResultMessage(searchResultCount: number | null, searchResultType: string | null): string { + if (searchResultCount === 0) { + return "Search completed. No results found."; + } + + const typeLabel = + searchResultType === "n" + ? "profile" + : searchResultType === "nip05" + ? "NIP-05 address" + : "event"; + const countLabel = searchResultType === "n" ? "profiles" : "events"; + + return searchResultCount === 1 + ? `Search completed. Found 1 ${typeLabel}.` + : `Search completed. Found ${searchResultCount} ${countLabel}.`; + } +} diff --git a/src/lib/utils/subscription_search.ts b/src/lib/utils/subscription_search.ts index d07067e..169cfb6 100644 --- a/src/lib/utils/subscription_search.ts +++ b/src/lib/utils/subscription_search.ts @@ -403,7 +403,8 @@ async function createProfileSearchFilter( } /** - * Create primary relay set based on search type + * Create primary relay set for search operations + * AI-NOTE: 2025-01-24 - Updated to use all available relays to prevent search failures */ function createPrimaryRelaySet( searchType: SearchSubscriptionType, @@ -413,9 +414,11 @@ function createPrimaryRelaySet( const poolRelays = Array.from(ndk.pool.relays.values()); console.debug('subscription_search: NDK pool relays:', poolRelays.map((r: any) => r.url)); + // AI-NOTE: 2025-01-24 - Use ALL available relays for comprehensive search coverage + // This ensures searches don't fail due to missing relays and provides maximum event discovery + if (searchType === "n") { - // AI-NOTE: 2025-01-08 - For profile searches, prioritize search relays for speed - // Use search relays first, then fall back to all relays if needed + // For profile searches, prioritize search relays for speed but include all relays const searchRelaySet = poolRelays.filter( (relay: any) => searchRelays.some( @@ -426,30 +429,27 @@ function createPrimaryRelaySet( if (searchRelaySet.length > 0) { console.debug('subscription_search: Profile search - using search relays for speed:', searchRelaySet.map((r: any) => r.url)); - return new NDKRelaySet(new Set(searchRelaySet) as any, ndk); + // Still include all relays for comprehensive coverage + console.debug('subscription_search: Profile search - also including all relays for comprehensive coverage'); + return new NDKRelaySet(new Set(poolRelays) as any, ndk); } else { - // Fallback to all relays if search relays not available - console.debug('subscription_search: Profile search - fallback to all relays:', poolRelays.map((r: any) => r.url)); + // Use all relays if search relays not available + console.debug('subscription_search: Profile search - using all relays:', poolRelays.map((r: any) => r.url)); return new NDKRelaySet(new Set(poolRelays) as any, ndk); } } else { - // For other searches, use active relays first - const searchRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)]; + // For all other searches, use ALL available relays for maximum coverage + const activeRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)]; console.debug('subscription_search: Active relay stores:', { inboxRelays: get(activeInboxRelays), outboxRelays: get(activeOutboxRelays), - searchRelays + activeRelays }); - const activeRelaySet = poolRelays.filter( - (relay: any) => - searchRelays.some( - (searchRelay: string) => - normalizeUrl(relay.url) === normalizeUrl(searchRelay), - ), - ); - console.debug('subscription_search: Active relay set:', activeRelaySet.map((r: any) => r.url)); - return new NDKRelaySet(new Set(activeRelaySet) as any, ndk); + // AI-NOTE: 2025-01-24 - Use all pool relays instead of filtering to active relays only + // This ensures we don't miss events that might be on other relays + console.debug('subscription_search: Using ALL pool relays for comprehensive search coverage:', poolRelays.map((r: any) => r.url)); + return new NDKRelaySet(new Set(poolRelays) as any, ndk); } } @@ -647,24 +647,15 @@ function searchOtherRelaysInBackground( ): Promise { const ndk = get(ndkInstance); + // AI-NOTE: 2025-01-24 - Use ALL available relays for comprehensive search coverage + // This ensures we don't miss events that might be on any available relay const otherRelays = new NDKRelaySet( - new Set( - Array.from(ndk.pool.relays.values()).filter((relay: any) => { - if (searchType === "n") { - // AI-NOTE: 2025-01-08 - For profile searches, use ALL available relays - // Don't exclude any relays since we want maximum coverage - return true; - } else { - // For other searches, exclude community relays from fallback search - return !communityRelays.some( - (communityRelay: string) => - normalizeUrl(relay.url) === normalizeUrl(communityRelay), - ); - } - }), - ), + new Set(Array.from(ndk.pool.relays.values())), ndk, ); + + console.debug('subscription_search: Background search using ALL relays:', + Array.from(ndk.pool.relays.values()).map((r: any) => r.url)); // Subscribe to events from other relays const sub = ndk.subscribe( diff --git a/vite.config.ts b/vite.config.ts index 82206c3..0872066 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -42,6 +42,8 @@ export default defineConfig({ define: { // Expose the app version as a global variable "import.meta.env.APP_VERSION": JSON.stringify(getAppVersionString()), + // Enable debug logging for relays when needed + "process.env.DEBUG_RELAYS": JSON.stringify(process.env.DEBUG_RELAYS || "false"), }, optimizeDeps: { esbuildOptions: { @@ -54,5 +56,8 @@ export default defineConfig({ fs: { allow: ['..'], }, + hmr: { + overlay: false, // Disable HMR overlay to prevent ESM URL scheme errors + }, }, });