Remove legacy relay connection pool

This commit is contained in:
hzrd149 2025-02-05 12:08:11 -06:00
parent d44e989195
commit 1d04e20110
21 changed files with 377 additions and 833 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Remove legacy relay connection pool

300
pnpm-lock.yaml generated
View File

@ -104,28 +104,28 @@ importers:
version: 0.7.2
applesauce-accounts:
specifier: next
version: 0.0.0-next-20250204170704(typescript@5.7.3)
version: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-content:
specifier: next
version: 0.0.0-next-20250204170704(typescript@5.7.3)
version: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-core:
specifier: next
version: 0.0.0-next-20250204170704(typescript@5.7.3)
version: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-factory:
specifier: next
version: 0.0.0-next-20250204170704(typescript@5.7.3)
version: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-loaders:
specifier: next
version: 0.0.0-next-20250204170704(typescript@5.7.3)
version: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-net:
specifier: ^0.10.0
version: 0.10.0(typescript@5.7.3)
applesauce-react:
specifier: next
version: 0.0.0-next-20250204170704(typescript@5.7.3)
version: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-signers:
specifier: next
version: 0.0.0-next-20250204170704(typescript@5.7.3)
version: 0.0.0-next-20250204175150(typescript@5.7.3)
bech32:
specifier: ^2.0.0
version: 2.0.0
@ -426,7 +426,7 @@ importers:
version: 4.7.5
'@vitejs/plugin-react':
specifier: ^4.3.4
version: 4.3.4(vite@5.4.14(@types/node@22.13.1)(terser@5.37.0))
version: 4.3.4(vite@5.4.14(@types/node@22.13.1)(terser@5.38.0))
camelcase:
specifier: ^8.0.0
version: 8.0.0
@ -441,13 +441,13 @@ importers:
version: 5.7.3
vite:
specifier: ^5.4.14
version: 5.4.14(@types/node@22.13.1)(terser@5.37.0)
version: 5.4.14(@types/node@22.13.1)(terser@5.38.0)
vite-plugin-pwa:
specifier: ^0.21.1
version: 0.21.1(vite@5.4.14(@types/node@22.13.1)(terser@5.37.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)
version: 0.21.1(vite@5.4.14(@types/node@22.13.1)(terser@5.38.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)
vite-tsconfig-paths:
specifier: ^5.1.4
version: 5.1.4(typescript@5.7.3)(vite@5.4.14(@types/node@22.13.1)(terser@5.37.0))
version: 5.1.4(typescript@5.7.3)(vite@5.4.14(@types/node@22.13.1)(terser@5.38.0))
workbox-build:
specifier: ^7.3.0
version: 7.3.0(@types/babel__core@7.20.5)
@ -1720,98 +1720,98 @@ packages:
rollup:
optional: true
'@rollup/rollup-android-arm-eabi@4.34.2':
resolution: {integrity: sha512-6Fyg9yQbwJR+ykVdT9sid1oc2ewejS6h4wzQltmJfSW53N60G/ah9pngXGANdy9/aaE/TcUFpWosdm7JXS1WTQ==}
'@rollup/rollup-android-arm-eabi@4.34.3':
resolution: {integrity: sha512-8kq/NjMKkMTGKMPldWihncOl62kgnLYk7cW+/4NCUWfS70/wz4+gQ7rMxMMpZ3dIOP/xw7wKNzIuUnN/H2GfUg==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.34.2':
resolution: {integrity: sha512-K5GfWe+vtQ3kyEbihrimM38UgX57UqHp+oME7X/EX9Im6suwZfa7Hsr8AtzbJvukTpwMGs+4s29YMSO3rwWtsw==}
'@rollup/rollup-android-arm64@4.34.3':
resolution: {integrity: sha512-1PqMHiuRochQ6++SDI7SaRDWJKr/NgAlezBi5nOne6Da6IWJo3hK0TdECBDwd92IUDPG4j/bZmWuwOnomNT8wA==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.34.2':
resolution: {integrity: sha512-PSN58XG/V/tzqDb9kDGutUruycgylMlUE59f40ny6QIRNsTEIZsrNQTJKUN2keMMSmlzgunMFqyaGLmly39sug==}
'@rollup/rollup-darwin-arm64@4.34.3':
resolution: {integrity: sha512-fqbrykX4mGV3DlCDXhF4OaMGcchd2tmLYxVt3On5oOZWVDFfdEoYAV2alzNChl8OzNaeMAGqm1f7gk7eIw/uDg==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.34.2':
resolution: {integrity: sha512-gQhK788rQJm9pzmXyfBB84VHViDERhAhzGafw+E5mUpnGKuxZGkMVDa3wgDFKT6ukLC5V7QTifzsUKdNVxp5qQ==}
'@rollup/rollup-darwin-x64@4.34.3':
resolution: {integrity: sha512-8Wxrx/KRvMsTyLTbdrMXcVKfpW51cCNW8x7iQD72xSEbjvhCY3b+w83Bea3nQfysTMR7K28esc+ZFITThXm+1w==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.34.2':
resolution: {integrity: sha512-eiaHgQwGPpxLC3+zTAcdKl4VsBl3r0AiJOd1Um/ArEzAjN/dbPK1nROHrVkdnoE6p7Svvn04w3f/jEZSTVHunA==}
'@rollup/rollup-freebsd-arm64@4.34.3':
resolution: {integrity: sha512-lpBmV2qSiELh+ATQPTjQczt5hvbTLsE0c43Rx4bGxN2VpnAZWy77we7OO62LyOSZNY7CzjMoceRPc+Lt4e9J6A==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.34.2':
resolution: {integrity: sha512-lhdiwQ+jf8pewYOTG4bag0Qd68Jn1v2gO1i0mTuiD+Qkt5vNfHVK/jrT7uVvycV8ZchlzXp5HDVmhpzjC6mh0g==}
'@rollup/rollup-freebsd-x64@4.34.3':
resolution: {integrity: sha512-sNPvBIXpgaYcI6mAeH13GZMXFrrw5mdZVI1M9YQPRG2LpjwL8DSxSIflZoh/B5NEuOi53kxsR/S2GKozK1vDXA==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.34.2':
resolution: {integrity: sha512-lfqTpWjSvbgQP1vqGTXdv+/kxIznKXZlI109WkIFPbud41bjigjNmOAAKoazmRGx+k9e3rtIdbq2pQZPV1pMig==}
'@rollup/rollup-linux-arm-gnueabihf@4.34.3':
resolution: {integrity: sha512-MW6N3AoC61OfE1VgnN5O1OW0gt8VTbhx9s/ZEPLBM11wEdHjeilPzOxVmmsrx5YmejpGPvez8QwGGvMU+pGxpw==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.34.2':
resolution: {integrity: sha512-RGjqULqIurqqv+NJTyuPgdZhka8ImMLB32YwUle2BPTDqDoXNgwFjdjQC59FbSk08z0IqlRJjrJ0AvDQ5W5lpw==}
'@rollup/rollup-linux-arm-musleabihf@4.34.3':
resolution: {integrity: sha512-2SQkhr5xvatYq0/+H6qyW0zvrQz9LM4lxGkpWURLoQX5+yP8MsERh4uWmxFohOvwCP6l/+wgiHZ1qVwLDc7Qmw==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.34.2':
resolution: {integrity: sha512-ZvkPiheyXtXlFqHpsdgscx+tZ7hoR59vOettvArinEspq5fxSDSgfF+L5wqqJ9R4t+n53nyn0sKxeXlik7AY9Q==}
'@rollup/rollup-linux-arm64-gnu@4.34.3':
resolution: {integrity: sha512-R3JLYt8YoRwKI5shJsovLpcR6pwIMui/MGG/MmxZ1DYI3iRSKI4qcYrvYgDf4Ss2oCR3RL3F3dYK7uAGQgMIuQ==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.34.2':
resolution: {integrity: sha512-UlFk+E46TZEoxD9ufLKDBzfSG7Ki03fo6hsNRRRHF+KuvNZ5vd1RRVQm8YZlGsjcJG8R252XFK0xNPay+4WV7w==}
'@rollup/rollup-linux-arm64-musl@4.34.3':
resolution: {integrity: sha512-4XQhG8v/t3S7Rxs7rmFUuM6j09hVrTArzONS3fUZ6oBRSN/ps9IPQjVhp62P0W3KhqJdQADo/MRlYRMdgxr/3w==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-loongarch64-gnu@4.34.2':
resolution: {integrity: sha512-hJhfsD9ykx59jZuuoQgYT1GEcNNi3RCoEmbo5OGfG8RlHOiVS7iVNev9rhLKh7UBYq409f4uEw0cclTXx8nh8Q==}
'@rollup/rollup-linux-loongarch64-gnu@4.34.3':
resolution: {integrity: sha512-QlW1jCUZ1LHUIYCAK2FciVw1ptHsxzApYVi05q7bz2A8oNE8QxQ85NhM4arLxkAlcnS42t4avJbSfzSQwbIaKg==}
cpu: [loong64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.34.2':
resolution: {integrity: sha512-g/O5IpgtrQqPegvqopvmdCF9vneLE7eqYfdPWW8yjPS8f63DNam3U4ARL1PNNB64XHZDHKpvO2Giftf43puB8Q==}
'@rollup/rollup-linux-powerpc64le-gnu@4.34.3':
resolution: {integrity: sha512-kMbLToizVeCcN69+nnm20Dh0hrRIAjgaaL+Wh0gWZcNt8e542d2FUGtsyuNsHVNNF3gqTJrpzUGIdwMGLEUM7g==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.34.2':
resolution: {integrity: sha512-bSQijDC96M6PuooOuXHpvXUYiIwsnDmqGU8+br2U7iPoykNi9JtMUpN7K6xml29e0evK0/g0D1qbAUzWZFHY5Q==}
'@rollup/rollup-linux-riscv64-gnu@4.34.3':
resolution: {integrity: sha512-YgD0DnZ3CHtvXRH8rzjVSxwI0kMTr0RQt3o1N92RwxGdx7YejzbBO0ELlSU48DP96u1gYYVWfUhDRyaGNqJqJg==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.34.2':
resolution: {integrity: sha512-49TtdeVAsdRuiUHXPrFVucaP4SivazetGUVH8CIxVsNsaPHV4PFkpLmH9LeqU/R4Nbgky9lzX5Xe1NrzLyraVA==}
'@rollup/rollup-linux-s390x-gnu@4.34.3':
resolution: {integrity: sha512-dIOoOz8altjp6UjAi3U9EW99s8nta4gzi52FeI45GlPyrUH4QixUoBMH9VsVjt+9A2RiZBWyjYNHlJ/HmJOBCQ==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.34.2':
resolution: {integrity: sha512-j+jFdfOycLIQ7FWKka9Zd3qvsIyugg5LeZuHF6kFlXo6MSOc6R1w37YUVy8VpAKd81LMWGi5g9J25P09M0SSIw==}
'@rollup/rollup-linux-x64-gnu@4.34.3':
resolution: {integrity: sha512-lOyG3aF4FTKrhpzXfMmBXgeKUUXdAWmP2zSNf8HTAXPqZay6QYT26l64hVizBjq+hJx3pl0DTEyvPi9sTA6VGA==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.34.2':
resolution: {integrity: sha512-aDPHyM/D2SpXfSNCVWCxyHmOqN9qb7SWkY1+vaXqMNMXslZYnwh9V/UCudl6psyG0v6Ukj7pXanIpfZwCOEMUg==}
'@rollup/rollup-linux-x64-musl@4.34.3':
resolution: {integrity: sha512-usztyYLu2i+mYzzOjqHZTaRXbUOqw3P6laNUh1zcqxbPH1P2Tz/QdJJCQSnGxCtsRQeuU2bCyraGMtMumC46rw==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.34.2':
resolution: {integrity: sha512-LQRkCyUBnAo7r8dbEdtNU08EKLCJMgAk2oP5H3R7BnUlKLqgR3dUjrLBVirmc1RK6U6qhtDw29Dimeer8d5hzQ==}
'@rollup/rollup-win32-arm64-msvc@4.34.3':
resolution: {integrity: sha512-ojFOKaz/ZyalIrizdBq2vyc2f0kFbJahEznfZlxdB6pF9Do6++i1zS5Gy6QLf8D7/S57MHrmBLur6AeRYeQXSA==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.34.2':
resolution: {integrity: sha512-wt8OhpQUi6JuPFkm1wbVi1BByeag87LDFzeKSXzIdGcX4bMLqORTtKxLoCbV57BHYNSUSOKlSL4BYYUghainYA==}
'@rollup/rollup-win32-ia32-msvc@4.34.3':
resolution: {integrity: sha512-K/V97GMbNa+Da9mGcZqmSl+DlJmWfHXTuI9V8oB2evGsQUtszCl67+OxWjBKpeOnYwox9Jpmt/J6VhpeRCYqow==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.34.2':
resolution: {integrity: sha512-rUrqINax0TvrPBXrFKg0YbQx18NpPN3NNrgmaao9xRNbTwek7lOXObhx8tQy8gelmQ/gLaGy1WptpU2eKJZImg==}
'@rollup/rollup-win32-x64-msvc@4.34.3':
resolution: {integrity: sha512-CUypcYP31Q8O04myV6NKGzk9GVXslO5EJNfmARNSzLF2A+5rmZUlDJ4et6eoJaZgBT9wrC2p4JZH04Vkic8HdQ==}
cpu: [x64]
os: [win32]
@ -2192,32 +2192,32 @@ packages:
engines: {node: '>=8.0.0'}
hasBin: true
applesauce-accounts@0.0.0-next-20250204170704:
resolution: {integrity: sha512-y80D4PfoT9gkd+TtUExeQdiq5grlchUbsdjRfmEG3JuAopTCzdgEAa2L7wfLT4DmsUwBvO8ISAm+zKRZ+GI/NQ==}
applesauce-accounts@0.0.0-next-20250204175150:
resolution: {integrity: sha512-b3zqfiDc6X9aKhrLXgSuyuivdn3GEQW8prwg/Bu7nZD7egoGcLsTX4fyAAzgBz6H2CO2oKxnwoyziugHEy6ZIQ==}
applesauce-content@0.0.0-next-20250204170704:
resolution: {integrity: sha512-hY6H2v6wSa/rzq6OCnjSL+y2Lv4/AR5z7irdqbSuzhSElslm78pqUPjsFlnVt7nu5K+1cAv0B/Ib/2yhaacgYw==}
applesauce-content@0.0.0-next-20250204175150:
resolution: {integrity: sha512-rFi/cWZbUYpMlFU8fNrEo0KkFy8lCyeGXxylcDW2zB6eMu8rUqQ2SZ9CiW9VUuBmjpxugetkhNJZtrINXhKj+w==}
applesauce-core@0.0.0-next-20250204170704:
resolution: {integrity: sha512-TcvcmFHYmduVFZsR+87/cPldZEUz/uafyHIMGPdM8Go4TWg/SMB2tRILdDuUrEjgIgtUVskAGlPF4uIoiH/bEA==}
applesauce-core@0.0.0-next-20250204175150:
resolution: {integrity: sha512-mirjWA0QdyKMGWqtmCWCr8vi+BH68KdWVjuP1mMn636rz1JvbseS0IG/TkuClJaZenZOaPQIfO0n/pK8EzzYzQ==}
applesauce-core@0.10.0:
resolution: {integrity: sha512-QMhUh4FIARcqY5soCB4Z8DIu+py0rYb28IgWT4gP9DLBGpDrY8lStXk7W1/46TLjEH97y0hbiXFK7kMCZ31oOQ==}
applesauce-factory@0.0.0-next-20250204170704:
resolution: {integrity: sha512-5lA2aWaKibpt3jNQkNSJJWjopk9CNSWJzDOiM156fBG2Qhe8GzdN7fBiCPlm+iPCRogGyDCAHabSS+JnZB6LTA==}
applesauce-factory@0.0.0-next-20250204175150:
resolution: {integrity: sha512-zYIg/5bh7xaGS8CiniHS50aTeAbKT3CTeu4BDv4Sbygu86b8R9Xd9LjxW8MsVDNwn6dlj+OqfC8r0mlUK/icEg==}
applesauce-loaders@0.0.0-next-20250204170704:
resolution: {integrity: sha512-7mYbE2qJA58A5cfdnpfQbdkohRvg6uhct+QieLlGOc/mNEmMxpA10GP9Wu7FprokWdCJEs0Pn7q7pobsJgPpKw==}
applesauce-loaders@0.0.0-next-20250204175150:
resolution: {integrity: sha512-z/cPftJvuIHWOaaPmq2YSQbkVIZ90K4QD7T+ZPh8IBZzlz2/tvdoidDBA174KXEVmBUn5fVE6Z1CAxrf/PBGDA==}
applesauce-net@0.10.0:
resolution: {integrity: sha512-ZsAs/MkeGHiPZ2/a8lwP8lx/Eh+5Dot0qG4BLTAqjg4emP/RsiqW+hyc6v6QcVbdvuR0+hP1gka3+wWtiy/cTA==}
applesauce-react@0.0.0-next-20250204170704:
resolution: {integrity: sha512-rHA9dO4k5UUeZESMtsrD0oGk7q26AFlpjLjWHBDKiFxRPsr5MusoKyW8X2fZOrN1fyNhgHD+lhCtesTWVUOU7Q==}
applesauce-react@0.0.0-next-20250204175150:
resolution: {integrity: sha512-J9ULdSeedSJXmFZtwECx+51I2cOVoCaOkWn7/HcFN4LiBtXuufx7viEZypWPv/nSdvpNK8Xb7jbOSuE75GmNWQ==}
applesauce-signers@0.0.0-next-20250204170704:
resolution: {integrity: sha512-SZUA5uZQChBReIRj34t1y4ZQnV8LR96oHyMelnhAPFJXZT57wUfMJB7Z6KVkxbMHwo2SYUP6qi4MpkC6KQyGng==}
applesauce-signers@0.0.0-next-20250204175150:
resolution: {integrity: sha512-n7tzmEHCnyLOULG2aYYsil6cEkmI3St2f8if6Dshu92avTRi83/rpn6g8nIFxXu5iBLzuEJup9kXsZtVfdfDAg==}
arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
@ -3033,8 +3033,8 @@ packages:
engines: {node: '>=0.10.0'}
hasBin: true
electron-to-chromium@1.5.91:
resolution: {integrity: sha512-sNSHHyq048PFmZY4S90ax61q+gLCs0X0YmcOII9wG9S2XwbVr+h4VW2wWhnbp/Eys3cCwTxVF292W3qPaxIapQ==}
electron-to-chromium@1.5.92:
resolution: {integrity: sha512-BeHgmNobs05N1HMmMZ7YIuHfYBGlq/UmvlsTgg+fsbFs9xVMj+xJHFg19GN04+9Q+r8Xnh9LXqaYIyEWElnNgQ==}
elementtree@0.1.7:
resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==}
@ -3667,8 +3667,8 @@ packages:
resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
engines: {node: '>= 0.4'}
is-boolean-object@1.2.1:
resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==}
is-boolean-object@1.2.2:
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
engines: {node: '>= 0.4'}
is-callable@1.2.7:
@ -4456,8 +4456,8 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
object-inspect@1.13.3:
resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==}
object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
object-keys@1.1.1:
@ -5135,8 +5135,8 @@ packages:
engines: {node: '>=10.0.0'}
hasBin: true
rollup@4.34.2:
resolution: {integrity: sha512-sBDUoxZEaqLu9QeNalL8v3jw6WjPku4wfZGyTU7l7m1oC+rpRihXc/n/H+4148ZkGz5Xli8CHMns//fFGKvpIQ==}
rollup@4.34.3:
resolution: {integrity: sha512-ORCtU0UBJyiAIn9m0llUXJXAswG/68pZptCrqxHG7//Z2DDzAUeyyY5hqf4XrsGlUxscMr9GkQ2QI7KTLqeyPw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@ -5506,8 +5506,8 @@ packages:
resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==}
engines: {node: '>=8'}
terser@5.37.0:
resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==}
terser@5.38.0:
resolution: {integrity: sha512-a4GD5R1TjEeuCT6ZRiYMHmIf7okbCPEuhQET8bczV6FrQMMlFXA1n+G0KKjdlFCm3TEHV77GxfZB3vZSUQGFpg==}
engines: {node: '>=10'}
hasBin: true
@ -7888,7 +7888,7 @@ snapshots:
dependencies:
serialize-javascript: 6.0.2
smob: 1.5.0
terser: 5.37.0
terser: 5.38.0
optionalDependencies:
rollup: 2.79.2
@ -7907,61 +7907,61 @@ snapshots:
optionalDependencies:
rollup: 2.79.2
'@rollup/rollup-android-arm-eabi@4.34.2':
'@rollup/rollup-android-arm-eabi@4.34.3':
optional: true
'@rollup/rollup-android-arm64@4.34.2':
'@rollup/rollup-android-arm64@4.34.3':
optional: true
'@rollup/rollup-darwin-arm64@4.34.2':
'@rollup/rollup-darwin-arm64@4.34.3':
optional: true
'@rollup/rollup-darwin-x64@4.34.2':
'@rollup/rollup-darwin-x64@4.34.3':
optional: true
'@rollup/rollup-freebsd-arm64@4.34.2':
'@rollup/rollup-freebsd-arm64@4.34.3':
optional: true
'@rollup/rollup-freebsd-x64@4.34.2':
'@rollup/rollup-freebsd-x64@4.34.3':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.34.2':
'@rollup/rollup-linux-arm-gnueabihf@4.34.3':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.34.2':
'@rollup/rollup-linux-arm-musleabihf@4.34.3':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.34.2':
'@rollup/rollup-linux-arm64-gnu@4.34.3':
optional: true
'@rollup/rollup-linux-arm64-musl@4.34.2':
'@rollup/rollup-linux-arm64-musl@4.34.3':
optional: true
'@rollup/rollup-linux-loongarch64-gnu@4.34.2':
'@rollup/rollup-linux-loongarch64-gnu@4.34.3':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.34.2':
'@rollup/rollup-linux-powerpc64le-gnu@4.34.3':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.34.2':
'@rollup/rollup-linux-riscv64-gnu@4.34.3':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.34.2':
'@rollup/rollup-linux-s390x-gnu@4.34.3':
optional: true
'@rollup/rollup-linux-x64-gnu@4.34.2':
'@rollup/rollup-linux-x64-gnu@4.34.3':
optional: true
'@rollup/rollup-linux-x64-musl@4.34.2':
'@rollup/rollup-linux-x64-musl@4.34.3':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.34.2':
'@rollup/rollup-win32-arm64-msvc@4.34.3':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.34.2':
'@rollup/rollup-win32-ia32-msvc@4.34.3':
optional: true
'@rollup/rollup-win32-x64-msvc@4.34.2':
'@rollup/rollup-win32-x64-msvc@4.34.3':
optional: true
'@sagold/json-pointer@5.1.2': {}
@ -8329,14 +8329,14 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
'@vitejs/plugin-react@4.3.4(vite@5.4.14(@types/node@22.13.1)(terser@5.37.0))':
'@vitejs/plugin-react@4.3.4(vite@5.4.14(@types/node@22.13.1)(terser@5.38.0))':
dependencies:
'@babel/core': 7.26.7
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.7)
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.7)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 5.4.14(@types/node@22.13.1)(terser@5.37.0)
vite: 5.4.14(@types/node@22.13.1)(terser@5.38.0)
transitivePeerDependencies:
- supports-color
@ -8423,10 +8423,10 @@ snapshots:
dependencies:
entities: 2.2.0
applesauce-accounts@0.0.0-next-20250204170704(typescript@5.7.3):
applesauce-accounts@0.0.0-next-20250204175150(typescript@5.7.3):
dependencies:
'@noble/hashes': 1.7.1
applesauce-signers: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-signers: 0.0.0-next-20250204175150(typescript@5.7.3)
nanoid: 5.0.9
nostr-tools: 2.10.4(typescript@5.7.3)
rxjs: 7.8.1
@ -8434,13 +8434,13 @@ snapshots:
- supports-color
- typescript
applesauce-content@0.0.0-next-20250204170704(typescript@5.7.3):
applesauce-content@0.0.0-next-20250204175150(typescript@5.7.3):
dependencies:
'@cashu/cashu-ts': 2.0.0-rc1
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
'@types/unist': 3.0.3
applesauce-core: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-core: 0.0.0-next-20250204175150(typescript@5.7.3)
mdast-util-find-and-replace: 3.0.2
nostr-tools: 2.10.4(typescript@5.7.3)
remark: 15.0.1
@ -8451,7 +8451,7 @@ snapshots:
- supports-color
- typescript
applesauce-core@0.0.0-next-20250204170704(typescript@5.7.3):
applesauce-core@0.0.0-next-20250204175150(typescript@5.7.3):
dependencies:
'@scure/base': 1.2.4
debug: 4.4.0
@ -8479,19 +8479,19 @@ snapshots:
- supports-color
- typescript
applesauce-factory@0.0.0-next-20250204170704(typescript@5.7.3):
applesauce-factory@0.0.0-next-20250204175150(typescript@5.7.3):
dependencies:
applesauce-content: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-core: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-content: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-core: 0.0.0-next-20250204175150(typescript@5.7.3)
nanoid: 5.0.9
nostr-tools: 2.10.4(typescript@5.7.3)
transitivePeerDependencies:
- supports-color
- typescript
applesauce-loaders@0.0.0-next-20250204170704(typescript@5.7.3):
applesauce-loaders@0.0.0-next-20250204175150(typescript@5.7.3):
dependencies:
applesauce-core: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-core: 0.0.0-next-20250204175150(typescript@5.7.3)
nanoid: 5.0.9
nostr-tools: 2.10.4(typescript@5.7.3)
rx-nostr: 3.5.0
@ -8510,12 +8510,12 @@ snapshots:
- supports-color
- typescript
applesauce-react@0.0.0-next-20250204170704(typescript@5.7.3):
applesauce-react@0.0.0-next-20250204175150(typescript@5.7.3):
dependencies:
applesauce-accounts: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-content: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-core: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-factory: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-accounts: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-content: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-core: 0.0.0-next-20250204175150(typescript@5.7.3)
applesauce-factory: 0.0.0-next-20250204175150(typescript@5.7.3)
nostr-tools: 2.10.4(typescript@5.7.3)
react: 18.3.1
rxjs: 7.8.1
@ -8523,12 +8523,12 @@ snapshots:
- supports-color
- typescript
applesauce-signers@0.0.0-next-20250204170704(typescript@5.7.3):
applesauce-signers@0.0.0-next-20250204175150(typescript@5.7.3):
dependencies:
'@noble/hashes': 1.7.1
'@noble/secp256k1': 1.7.1
'@scure/base': 1.2.4
applesauce-core: 0.0.0-next-20250204170704(typescript@5.7.3)
applesauce-core: 0.0.0-next-20250204175150(typescript@5.7.3)
debug: 4.4.0
nanoid: 5.0.9
nostr-tools: 2.10.4(typescript@5.7.3)
@ -8753,7 +8753,7 @@ snapshots:
browserslist@4.24.4:
dependencies:
caniuse-lite: 1.0.30001697
electron-to-chromium: 1.5.91
electron-to-chromium: 1.5.92
node-releases: 2.0.19
update-browserslist-db: 1.1.2(browserslist@4.24.4)
@ -9450,7 +9450,7 @@ snapshots:
dependencies:
jake: 10.9.2
electron-to-chromium@1.5.91: {}
electron-to-chromium@1.5.92: {}
elementtree@0.1.7:
dependencies:
@ -9535,7 +9535,7 @@ snapshots:
is-typed-array: 1.1.15
is-weakref: 1.1.1
math-intrinsics: 1.1.0
object-inspect: 1.13.3
object-inspect: 1.13.4
object-keys: 1.1.1
object.assign: 4.1.7
own-keys: 1.0.1
@ -10194,7 +10194,7 @@ snapshots:
dependencies:
has-bigints: 1.1.0
is-boolean-object@1.2.1:
is-boolean-object@1.2.2:
dependencies:
call-bound: 1.0.3
has-tostringtag: 1.0.2
@ -11156,7 +11156,7 @@ snapshots:
object-assign@4.1.1: {}
object-inspect@1.13.3: {}
object-inspect@1.13.4: {}
object-keys@1.1.1: {}
@ -11911,29 +11911,29 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
rollup@4.34.2:
rollup@4.34.3:
dependencies:
'@types/estree': 1.0.6
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.34.2
'@rollup/rollup-android-arm64': 4.34.2
'@rollup/rollup-darwin-arm64': 4.34.2
'@rollup/rollup-darwin-x64': 4.34.2
'@rollup/rollup-freebsd-arm64': 4.34.2
'@rollup/rollup-freebsd-x64': 4.34.2
'@rollup/rollup-linux-arm-gnueabihf': 4.34.2
'@rollup/rollup-linux-arm-musleabihf': 4.34.2
'@rollup/rollup-linux-arm64-gnu': 4.34.2
'@rollup/rollup-linux-arm64-musl': 4.34.2
'@rollup/rollup-linux-loongarch64-gnu': 4.34.2
'@rollup/rollup-linux-powerpc64le-gnu': 4.34.2
'@rollup/rollup-linux-riscv64-gnu': 4.34.2
'@rollup/rollup-linux-s390x-gnu': 4.34.2
'@rollup/rollup-linux-x64-gnu': 4.34.2
'@rollup/rollup-linux-x64-musl': 4.34.2
'@rollup/rollup-win32-arm64-msvc': 4.34.2
'@rollup/rollup-win32-ia32-msvc': 4.34.2
'@rollup/rollup-win32-x64-msvc': 4.34.2
'@rollup/rollup-android-arm-eabi': 4.34.3
'@rollup/rollup-android-arm64': 4.34.3
'@rollup/rollup-darwin-arm64': 4.34.3
'@rollup/rollup-darwin-x64': 4.34.3
'@rollup/rollup-freebsd-arm64': 4.34.3
'@rollup/rollup-freebsd-x64': 4.34.3
'@rollup/rollup-linux-arm-gnueabihf': 4.34.3
'@rollup/rollup-linux-arm-musleabihf': 4.34.3
'@rollup/rollup-linux-arm64-gnu': 4.34.3
'@rollup/rollup-linux-arm64-musl': 4.34.3
'@rollup/rollup-linux-loongarch64-gnu': 4.34.3
'@rollup/rollup-linux-powerpc64le-gnu': 4.34.3
'@rollup/rollup-linux-riscv64-gnu': 4.34.3
'@rollup/rollup-linux-s390x-gnu': 4.34.3
'@rollup/rollup-linux-x64-gnu': 4.34.3
'@rollup/rollup-linux-x64-musl': 4.34.3
'@rollup/rollup-win32-arm64-msvc': 4.34.3
'@rollup/rollup-win32-ia32-msvc': 4.34.3
'@rollup/rollup-win32-x64-msvc': 4.34.3
fsevents: 2.3.3
rtl-css-js@1.16.1:
@ -12084,27 +12084,27 @@ snapshots:
side-channel-list@1.0.0:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.3
object-inspect: 1.13.4
side-channel-map@1.0.1:
dependencies:
call-bound: 1.0.3
es-errors: 1.3.0
get-intrinsic: 1.2.7
object-inspect: 1.13.3
object-inspect: 1.13.4
side-channel-weakmap@1.0.2:
dependencies:
call-bound: 1.0.3
es-errors: 1.3.0
get-intrinsic: 1.2.7
object-inspect: 1.13.3
object-inspect: 1.13.4
side-channel-map: 1.0.1
side-channel@1.1.0:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.3
object-inspect: 1.13.4
side-channel-list: 1.0.0
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
@ -12392,7 +12392,7 @@ snapshots:
term-size@2.2.1: {}
terser@5.37.0:
terser@5.38.0:
dependencies:
'@jridgewell/source-map': 0.3.6
acorn: 8.14.0
@ -12722,37 +12722,37 @@ snapshots:
vite-plugin-funding@0.1.0: {}
vite-plugin-pwa@0.21.1(vite@5.4.14(@types/node@22.13.1)(terser@5.37.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0):
vite-plugin-pwa@0.21.1(vite@5.4.14(@types/node@22.13.1)(terser@5.38.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0):
dependencies:
debug: 4.4.0
pretty-bytes: 6.1.1
tinyglobby: 0.2.10
vite: 5.4.14(@types/node@22.13.1)(terser@5.37.0)
vite: 5.4.14(@types/node@22.13.1)(terser@5.38.0)
workbox-build: 7.3.0(@types/babel__core@7.20.5)
workbox-window: 7.3.0
transitivePeerDependencies:
- supports-color
vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@5.4.14(@types/node@22.13.1)(terser@5.37.0)):
vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@5.4.14(@types/node@22.13.1)(terser@5.38.0)):
dependencies:
debug: 4.4.0
globrex: 0.1.2
tsconfck: 3.1.4(typescript@5.7.3)
optionalDependencies:
vite: 5.4.14(@types/node@22.13.1)(terser@5.37.0)
vite: 5.4.14(@types/node@22.13.1)(terser@5.38.0)
transitivePeerDependencies:
- supports-color
- typescript
vite@5.4.14(@types/node@22.13.1)(terser@5.37.0):
vite@5.4.14(@types/node@22.13.1)(terser@5.38.0):
dependencies:
esbuild: 0.21.5
postcss: 8.5.1
rollup: 4.34.2
rollup: 4.34.3
optionalDependencies:
'@types/node': 22.13.1
fsevents: 2.3.3
terser: 5.37.0
terser: 5.38.0
w3c-keyname@2.2.8: {}
@ -12790,7 +12790,7 @@ snapshots:
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0
is-boolean-object: 1.2.1
is-boolean-object: 1.2.2
is-number-object: 1.1.1
is-string: 1.1.1
is-symbol: 1.1.1

View File

@ -1,243 +0,0 @@
import { AbstractRelay } from "nostr-tools/abstract-relay";
import { IConnectionPool } from "applesauce-net/connection";
import dayjs from "dayjs";
import { Subject, BehaviorSubject } from "rxjs";
import { logger } from "../helpers/debug";
import { safeRelayUrl, validateRelayURL } from "../helpers/relay";
import SuperMap from "./super-map";
import verifyEventMethod from "../services/verify-event";
import localSettings from "../services/local-settings";
export type Notice = {
message: string;
date: number;
relay: AbstractRelay;
};
export type RelayAuthMode = "always" | "ask" | "never";
export default class RelayPool implements IConnectionPool {
log = logger.extend("RelayPool");
relays = new Map<string, AbstractRelay>();
onRelayCreated = new Subject<AbstractRelay>();
onRelayChallenge = new Subject<[AbstractRelay, string]>();
notices = new SuperMap<AbstractRelay, BehaviorSubject<Notice[]>>(() => new BehaviorSubject<Notice[]>([]));
connectionErrors = new SuperMap<AbstractRelay, Error[]>(() => []);
connecting = new SuperMap<AbstractRelay, BehaviorSubject<boolean>>(() => new BehaviorSubject(false));
challenges = new SuperMap<AbstractRelay, BehaviorSubject<string | undefined>>(
() => new BehaviorSubject<string | undefined>(undefined),
);
authForPublish = new SuperMap<AbstractRelay, BehaviorSubject<boolean | undefined>>(
() => new BehaviorSubject<boolean | undefined>(undefined),
);
authForSubscribe = new SuperMap<AbstractRelay, BehaviorSubject<boolean | undefined>>(
() => new BehaviorSubject<boolean | undefined>(undefined),
);
authenticated = new SuperMap<AbstractRelay, BehaviorSubject<boolean>>(() => new BehaviorSubject(false));
getRelay(relayOrUrl: string | URL | AbstractRelay) {
if (typeof relayOrUrl === "string") {
const safeURL = safeRelayUrl(relayOrUrl);
if (safeURL) {
return this.relays.get(safeURL) || this.requestRelay(safeURL);
} else return;
} else if (relayOrUrl instanceof URL) {
return this.relays.get(relayOrUrl.toString()) || this.requestRelay(relayOrUrl.toString());
}
return relayOrUrl;
}
// TODO: for now this is just a copy of
getConnection(relayOrUrl: string | URL | AbstractRelay) {
if (typeof relayOrUrl === "string") {
const safeURL = safeRelayUrl(relayOrUrl);
if (safeURL) {
return this.relays.get(safeURL) || this.requestRelay(safeURL);
} else {
throw new Error(`Bad relay url ${relayOrUrl}`);
}
} else if (relayOrUrl instanceof URL) {
return this.relays.get(relayOrUrl.toString()) || this.requestRelay(relayOrUrl.toString());
}
return relayOrUrl;
}
getRelays(urls?: Iterable<string | URL | AbstractRelay>) {
if (urls) {
const relays: AbstractRelay[] = [];
for (const url of urls) {
const relay = this.getRelay(url);
if (relay) relays.push(relay);
}
return relays;
}
return Array.from(this.relays.values());
}
requestRelay(url: string | URL, connect = true) {
url = validateRelayURL(url);
const key = url.toString();
if (!this.relays.has(key)) {
const r = new AbstractRelay(key, { verifyEvent: verifyEventMethod });
r._onauth = (challenge) => this.handleRelayChallenge(r, challenge);
r.onnotice = (notice) => this.handleRelayNotice(r, notice);
this.relays.set(key, r);
this.onRelayCreated.next(r);
}
const relay = this.relays.get(key) as AbstractRelay;
if (connect && !relay.connected) this.requestConnect(relay);
return relay;
}
async waitForOpen(relayOrUrl: string | URL | AbstractRelay, quite = true) {
const relay = this.getRelay(relayOrUrl);
if (!relay) return Promise.reject("Missing relay");
if (relay.connected) return true;
try {
// if the relay is connecting, wait. otherwise request a connection
// @ts-expect-error
(await relay.connectionPromise) || this.requestConnect(relay, quite);
return true;
} catch (err) {
if (quite) return false;
else throw err;
}
}
async requestConnect(relayOrUrl: string | URL | AbstractRelay, quite = true) {
const relay = this.getRelay(relayOrUrl);
if (!relay) return;
if (!relay.connected) {
this.connecting.get(relay).next(true);
try {
await relay.connect();
this.connecting.get(relay).next(false);
} catch (e) {
e = e || new Error("Unknown error");
if (e instanceof Error) {
this.log(`Failed to connect to ${relay.url}`, e.message);
this.connectionErrors.get(relay).push(e);
}
this.connecting.get(relay).next(false);
if (!quite) throw e;
}
}
}
getRelayAuthStorageKey(relayOrUrl: string | URL | AbstractRelay) {
const relay = this.getRelay(relayOrUrl);
return `${relay!.url}-auth-mode`;
}
getRelayAuthMode(relayOrUrl: string | URL | AbstractRelay): RelayAuthMode | undefined {
const relay = this.getRelay(relayOrUrl);
if (!relay) return;
const defaultMode = localSettings.defaultAuthenticationMode.value;
const mode = (localStorage.getItem(this.getRelayAuthStorageKey(relay)) as RelayAuthMode) ?? undefined;
return mode || defaultMode;
}
setRelayAuthMode(relayOrUrl: string | URL | AbstractRelay, mode: RelayAuthMode) {
const relay = this.getRelay(relayOrUrl);
if (!relay) return;
localStorage.setItem(this.getRelayAuthStorageKey(relay), mode);
}
pendingAuth = new Map<AbstractRelay, Promise<string | undefined>>();
async authenticate(
relayOrUrl: string | URL | AbstractRelay,
sign: Parameters<AbstractRelay["auth"]>[0],
quite = true,
) {
const relay = this.getRelay(relayOrUrl);
if (!relay) return;
const pending = this.pendingAuth.get(relay);
if (pending) return pending;
if (this.getRelayAuthMode(relay) === "never") throw new Error("Auth disabled for relay");
if (!relay.connected) throw new Error("Not connected");
const promise = new Promise<string | undefined>(async (res) => {
if (!relay) return;
try {
const message = await relay.auth(sign);
this.authenticated.get(relay).next(true);
res(message);
} catch (e) {
e = e || new Error("Unknown error");
if (e instanceof Error) {
this.log(`Failed to authenticate to ${relay.url}`, e.message);
}
this.authenticated.get(relay).next(false);
if (!quite) throw e;
}
this.pendingAuth.delete(relay);
});
this.pendingAuth.set(relay, promise);
return await promise;
}
canSubscribe(relayOrUrl: string | URL | AbstractRelay) {
const relay = this.getRelay(relayOrUrl);
if (!relay) return false;
return this.authForSubscribe.get(relay).value !== false;
}
private handleRelayChallenge(relay: AbstractRelay, challenge: string) {
this.onRelayChallenge.next([relay, challenge]);
this.challenges.get(relay).next(challenge);
}
handleRelayNotice(relay: AbstractRelay, message: string) {
const subject = this.notices.get(relay);
subject.next([...subject.value, { message, date: dayjs().unix(), relay }]);
if (message.includes("auth-required")) {
const authForSubscribe = this.authForSubscribe.get(relay);
if (!authForSubscribe.value) authForSubscribe.next(true);
}
}
disconnectFromUnused() {
for (const [url, relay] of this.relays) {
if (!relay.connected) continue;
// don't disconnect from authenticated relays
if (this.authenticated.get(relay).value) continue;
let disconnect = false;
if (disconnect) {
this.log(`No active processes using ${relay.url}, disconnecting`);
relay.close();
// NOTE: fix nostr-tools not resetting the connection promise
// @ts-expect-error
relay.connectionPromise = false;
}
}
}
}

View File

@ -52,13 +52,11 @@ type GifPickerProps = Omit<ModalProps, "children"> & { onSelect: (gif: NostrEven
export default function GifPickerModal({ onClose, isOpen, onSelect, ...props }: GifPickerProps) {
const [search, setSearch] = useState<string>();
const [searchRelayUrl, setSearchRelayUrl] = useState<string>();
const [searchRelay, setSearchRelay] = useState<string>("");
const [list, setList] = useState<ListId>("global");
const { selected, setSelected, filter, listId } = usePeopleListSelect(list, setList);
// const searchRelay = useSearchRelay(searchRelayUrl);
const [debounceSearch, setDebounceSearch] = useState<string>();
useEffect(() => {
setDebounceSearch(undefined);
@ -75,8 +73,8 @@ export default function GifPickerModal({ onClose, isOpen, onSelect, ...props }:
const readRelays = useReadRelays();
const { loader, timeline } = useTimelineLoader(
[listId, "gifs", searchRelayUrl ?? "all", debounceSearch ?? "all"].join("-"),
searchRelayUrl !== undefined ? [searchRelayUrl] : readRelays,
[listId, "gifs", searchRelay ?? "all", debounceSearch ?? "all"].join("-"),
!!searchRelay ? [searchRelay] : readRelays,
debounceSearch !== undefined ? { ...baseFilter, search: debounceSearch } : baseFilter,
);
@ -96,7 +94,7 @@ export default function GifPickerModal({ onClose, isOpen, onSelect, ...props }:
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<SearchRelayPicker value={searchRelayUrl} onChange={(e) => setSearchRelayUrl(e.target.value)} />
<SearchRelayPicker value={searchRelay} onChange={(e) => setSearchRelay(e.target.value)} />
<Button type="submit">Search</Button>
</Flex>
<ButtonGroup size="xs">

View File

@ -1,8 +1,8 @@
import { Select, SelectProps } from "@chakra-ui/react";
import { useObservable } from "applesauce-react/hooks";
import { RelayAuthMode } from "../../classes/relay-pool";
import localSettings from "../../services/local-settings";
import { RelayAuthMode } from "../../services/authentication-signer";
export default function RelayAuthModeSelect({
relay,

View File

@ -2,7 +2,7 @@ import { Select, SelectProps } from "@chakra-ui/react";
import { useObservable } from "applesauce-react/hooks";
import localSettings from "../../services/local-settings";
import { RelayAuthMode } from "../../classes/relay-pool";
import { RelayAuthMode } from "../../services/authentication-signer";
export default function DefaultAuthModeSelect({ ...props }: Omit<SelectProps, "children" | "value" | "onChange">) {
const defaultAuthenticationMode = useObservable(localSettings.defaultAuthenticationMode);

View File

@ -1,15 +1,14 @@
import { getReplaceableIdentifier, getTagValue } from "applesauce-core/helpers";
import { NostrEvent, isDTag } from "../../types/nostr-event";
export const SELF_REPORTED_KIND = 10066;
export const MONITOR_METADATA_KIND = 10166;
export const MONITOR_STATS_KIND = 30066;
export const MONITOR_STATS_KIND = 30166;
export function getRelayURL(stats: NostrEvent) {
if (stats.kind === SELF_REPORTED_KIND) return stats.tags.find((t) => t[0] === "r")?.[1];
return stats.tags.find(isDTag)?.[1];
return getReplaceableIdentifier(stats);
}
export function getNetwork(stats: NostrEvent) {
return stats.tags.find((t) => t[0] === "n")?.[1];
return getTagValue(stats, "n");
}
export function getSupportedNIPs(stats: NostrEvent) {
return stats.tags.filter((t) => t[0] === "N" && t[1]).map((t) => t[1] && parseInt(t[1]));

View File

@ -1,7 +1,5 @@
import { ThemeTypings } from "@chakra-ui/react";
import { Filter } from "nostr-tools";
import { SubCloser, SubscribeManyParams } from "nostr-tools/abstract-pool";
import { AbstractRelay, Subscription } from "nostr-tools/abstract-relay";
import { ConnectionState } from "rx-nostr";
// NOTE: only use this for equality checks and querying
@ -96,85 +94,6 @@ export function splitQueryByPubkeys(query: Filter, relayPubkeyMap: Record<string
return filtersByRelay;
}
// copied from nostr-tools, SimplePool#subscribeMany
export function subscribeMany(relays: string[], filters: Filter[], params: SubscribeManyParams): SubCloser {
const _knownIds = new Set<string>();
const subs: Subscription[] = [];
// batch all EOSEs into a single
const eosesReceived: boolean[] = [];
let handleEose = (i: number) => {
eosesReceived[i] = true;
if (eosesReceived.filter((a) => a).length === relays.length) {
params.oneose?.();
handleEose = () => {};
}
};
// batch all closes into a single
const closesReceived: string[] = [];
let handleClose = (i: number, reason: string) => {
handleEose(i);
closesReceived[i] = reason;
if (closesReceived.filter((a) => a).length === relays.length) {
params.onclose?.(closesReceived);
handleClose = () => {};
}
};
const localAlreadyHaveEventHandler = (id: string) => {
if (params.alreadyHaveEvent?.(id)) {
return true;
}
const have = _knownIds.has(id);
_knownIds.add(id);
return have;
};
// open a subscription in all given relays
const allOpened = Promise.all(
relays.map(validateRelayURL).map(async (url, i, arr) => {
if (arr.indexOf(url) !== i) {
// duplicate
handleClose(i, "duplicate url");
return;
}
let relay: AbstractRelay;
try {
const { default: relayPoolService } = await import("../services/relay-pool");
relay = relayPoolService.requestRelay(url);
await relayPoolService.requestConnect(relay);
// changed from nostr-tools
// relay = await this.ensureRelay(url, {
// connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1000) : undefined,
// });
} catch (err) {
handleClose(i, (err as any)?.message || String(err));
return;
}
const subscription = relay.subscribe(filters, {
...params,
oneose: () => handleEose(i),
onclose: (reason) => handleClose(i, reason),
alreadyHaveEvent: localAlreadyHaveEventHandler,
eoseTimeout: params.maxWait,
});
subs.push(subscription);
}),
);
return {
async close() {
await allOpened;
subs.forEach((sub) => {
sub.close();
});
},
};
}
export function getConnectionStateColor(state: ConnectionState): ThemeTypings["colorSchemes"] {
switch (state) {
case "initialized":

View File

@ -1,17 +1,15 @@
import { useObservable } from "applesauce-react/hooks";
import relayStatsService from "../services/relay-stats";
import { useEffect } from "react";
import { useStoreQuery } from "applesauce-react/hooks";
import { ReplaceableQuery } from "applesauce-core/queries";
import { MONITOR_PUBKEY, MONITOR_RELAY } from "../services/relay-status-loader";
import monitorRelayStatusLoader from "../services/relay-status-loader";
import { MONITOR_STATS_KIND } from "../helpers/nostr/relay-stats";
export default function useRelayStats(relay: string) {
const monitorSub = relayStatsService.requestMonitorStats(relay);
const selfReportedSub = relayStatsService.requestSelfReported(relay);
useEffect(() => {
monitorRelayStatusLoader.next({ value: relay, relays: [MONITOR_RELAY] });
}, [relay]);
const monitor = useObservable(monitorSub);
const selfReported = useObservable(selfReportedSub);
const stats = monitor || selfReported || undefined;
return {
monitor,
selfReported,
stats,
};
return useStoreQuery(ReplaceableQuery, [MONITOR_STATS_KIND, MONITOR_PUBKEY, relay]);
}

View File

@ -3,6 +3,19 @@ import { useActiveAccount } from "applesauce-react/hooks";
import { DEFAULT_SEARCH_RELAYS } from "../const";
import { getRelaysFromList } from "../helpers/nostr/lists";
import useUserSearchRelayList from "./use-user-search-relay-list";
import useCacheRelay from "./use-cache-relay";
import { useRelayInfo } from "./use-relay-info";
import { AbstractRelay } from "nostr-tools/abstract-relay";
import WasmRelay from "../services/wasm-relay";
export function useCacheRelaySupportsSearch() {
const cacheRelay = useCacheRelay();
const { info: cacheRelayInfo } = useRelayInfo(cacheRelay instanceof AbstractRelay ? cacheRelay : undefined, true);
return (
cacheRelay instanceof WasmRelay ||
(cacheRelay instanceof AbstractRelay && !!cacheRelayInfo?.supported_nips?.includes(50))
);
}
export default function useSearchRelays() {
const account = useActiveAccount();

View File

@ -1,5 +0,0 @@
import RelayPool from "../classes/relay-pool";
const relayPoolService = new RelayPool();
export default relayPoolService;

View File

@ -1,99 +0,0 @@
import _throttle from "lodash.throttle";
import { Filter } from "nostr-tools";
import { BehaviorSubject } from "rxjs";
import SuperMap from "../classes/super-map";
import { NostrEvent } from "../types/nostr-event";
import relayInfoService from "./relay-info";
import { MONITOR_STATS_KIND, SELF_REPORTED_KIND, getRelayURL } from "../helpers/nostr/relay-stats";
import relayPoolService from "./relay-pool";
import { alwaysVerify } from "./verify-event";
import { eventStore } from "./event-store";
import { getCacheRelay } from "./cache-relay";
const MONITOR_PUBKEY = "151c17c9d234320cf0f189af7b761f63419fd6c38c6041587a008b7682e4640f";
const MONITOR_RELAY = "wss://relay.nostr.watch";
class RelayStatsService {
private selfReported = new SuperMap<string, BehaviorSubject<NostrEvent | null | undefined>>(
() => new BehaviorSubject<NostrEvent | null | undefined>(undefined),
);
private monitorStats = new SuperMap<string, BehaviorSubject<NostrEvent | undefined>>(
() => new BehaviorSubject<NostrEvent | undefined>(undefined),
);
constructor() {
// load all stats from cache and subscribe to future ones
getCacheRelay()?.subscribe([{ kinds: [SELF_REPORTED_KIND, MONITOR_STATS_KIND] }], {
onevent: (e) => this.handleEvent(e),
});
}
handleEvent(event: NostrEvent) {
if (!alwaysVerify(event)) return;
// ignore all events before NIP-66 start date
if (event.created_at < 1704196800) return;
const relay = getRelayURL(event);
if (!relay) return;
eventStore.add(event);
const sub = this.monitorStats.get(relay);
if (event.kind === SELF_REPORTED_KIND) {
if (!sub.value || event.created_at > sub.value.created_at) sub.next(event);
} else if (event.kind === MONITOR_STATS_KIND) {
if (!sub.value || event.created_at > sub.value.created_at) sub.next(event);
}
}
requestSelfReported(relay: string) {
const sub = this.selfReported.get(relay);
if (sub.value === undefined) {
relayInfoService.getInfo(relay).then((info) => {
if (!info.pubkey) return sub.next(null);
const filter: Filter = { kinds: [SELF_REPORTED_KIND], authors: [info.pubkey] };
const subscription = relayPoolService
.requestRelay(MONITOR_RELAY)
.subscribe([filter], { onevent: (event) => this.handleEvent(event), oneose: () => subscription.close() });
});
}
return sub;
}
requestMonitorStats(relay: string) {
const sub = this.monitorStats.get(relay);
if (sub.value === undefined) {
this.pendingMonitorStats.add(relay);
this.throttleBatchRequestMonitorStats();
}
return sub;
}
throttleBatchRequestMonitorStats = _throttle(this.batchRequestMonitorStats, 200);
pendingMonitorStats = new Set<string>();
private batchRequestMonitorStats() {
const relays = Array.from(this.pendingMonitorStats);
const filter: Filter = { since: 1704196800, kinds: [MONITOR_STATS_KIND], "#d": relays, authors: [MONITOR_PUBKEY] };
const sub = relayPoolService
.requestRelay(MONITOR_RELAY)
.subscribe([filter], { onevent: (event) => this.handleEvent(event), oneose: () => sub.close() });
this.pendingMonitorStats.clear();
}
}
const relayStatsService = new RelayStatsService();
if (import.meta.env.DEV) {
//@ts-expect-error debug
window.relayStatsService = relayStatsService;
}
export default relayStatsService;

View File

@ -0,0 +1,21 @@
import { MONITOR_STATS_KIND } from "../helpers/nostr/relay-stats";
import { TagValueLoader } from "applesauce-loaders";
import rxNostr from "./rx-nostr";
import { eventStore } from "./event-store";
export const MONITOR_PUBKEY = "151c17c9d234320cf0f189af7b761f63419fd6c38c6041587a008b7682e4640f";
export const MONITOR_RELAY = "wss://relay.nostr.watch/";
const monitorRelayStatusLoader = new TagValueLoader(rxNostr, "d", {
name: "relay-monitor",
kinds: [MONITOR_STATS_KIND],
authors: [MONITOR_PUBKEY],
since: 1704196800,
});
// start the loader and send all events to the event store
monitorRelayStatusLoader.subscribe((packet) => {
eventStore.add(packet.event, packet.from);
});
export default monitorRelayStatusLoader;

View File

@ -2,6 +2,7 @@ import { NostrEvent } from "nostr-tools";
import { Box, Button, Flex, Heading, Image, Spinner, Text, useDisclosure } from "@chakra-ui/react";
import dayjs from "dayjs";
import { ThreadIcon } from "../../components/icons";
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
import useReplaceableEvent from "../../hooks/use-replaceable-event";
import VerticalPageLayout from "../../components/vertical-page-layout";
@ -24,7 +25,6 @@ import BookmarkEventButton from "../../components/note/bookmark-event";
import EventQuoteButton from "../../components/note/event-quote-button";
import { GenericComments } from "../../components/comment/generic-comments";
import GenericCommentForm from "../../components/comment/generic-comment-form";
import { ThreadIcon } from "../../components/icons";
function ArticlePage({ article }: { article: NostrEvent }) {
const image = getArticleImage(article);

View File

@ -45,7 +45,7 @@ export const Metadata = ({ name, children }: { name: string } & PropsWithChildre
export function RelayMetadata({ url, extended }: { url: string; extended?: boolean }) {
const { info } = useRelayInfo(url);
const { stats } = useRelayStats(url);
const stats = useRelayStats(url);
return (
<Box>

View File

@ -1,32 +1,18 @@
import { forwardRef } from "react";
import { Select, SelectProps } from "@chakra-ui/react";
import { AbstractRelay } from "nostr-tools/abstract-relay";
import useSearchRelays from "../../../hooks/use-search-relays";
import { useRelayInfo } from "../../../hooks/use-relay-info";
import WasmRelay from "../../../services/wasm-relay";
import relayPoolService from "../../../services/relay-pool";
import useCacheRelay from "../../../hooks/use-cache-relay";
import useSearchRelays, { useCacheRelaySupportsSearch } from "../../../hooks/use-search-relays";
export function useSearchRelay(relay?: string) {
const cacheRelay = useCacheRelay();
if (!relay) return undefined;
if (relay === "local") return cacheRelay as AbstractRelay;
else return relayPoolService.requestRelay(relay);
}
const SearchRelayPicker = forwardRef<any, Omit<SelectProps, "children">>(({ value, onChange, ...props }, ref) => {
const SearchRelayPicker = forwardRef<
any,
Omit<SelectProps, "children" | "value"> & { value?: string; showLocal?: boolean }
>(({ value, onChange, showLocal, ...props }, ref) => {
const searchRelays = useSearchRelays();
const cacheRelay = useCacheRelay();
const { info: cacheRelayInfo } = useRelayInfo(cacheRelay instanceof AbstractRelay ? cacheRelay : undefined, true);
const localSearchSupported =
cacheRelay instanceof WasmRelay ||
(cacheRelay instanceof AbstractRelay && !!cacheRelayInfo?.supported_nips?.includes(50));
const localSearchSupported = useCacheRelaySupportsSearch();
return (
<Select ref={ref} w="auto" value={value} onChange={onChange} {...props}>
{localSearchSupported && <option value="local">Local Relay</option>}
{showLocal && localSearchSupported && <option value="">Local Relay</option>}
{searchRelays.map((url) => (
<option key={url} value={url}>
{url}

View File

@ -1,50 +1,48 @@
import { useEffect, useMemo, useState } from "react";
import { Filter, kinds, NostrEvent } from "nostr-tools";
import { AbstractRelay, Subscription, SubscriptionParams } from "nostr-tools/abstract-relay";
import { Alert, AlertDescription, AlertIcon, AlertTitle, Heading, Spinner, Text } from "@chakra-ui/react";
import { map, Observable } from "rxjs";
import { LRU } from "applesauce-core/helpers";
import relayPoolService from "../../../services/relay-pool";
import ProfileSearchResults from "./profile-results";
import NoteSearchResults from "./note-results";
import ArticleSearchResults from "./article-results";
import { eventStore } from "../../../services/event-store";
import { createRxOneshotReq, EventPacket } from "rx-nostr";
import rxNostr from "../../../services/rx-nostr";
import { cacheRequest } from "../../../services/cache-relay";
function createSearchAction(url: string | AbstractRelay) {
let sub: Subscription | undefined = undefined;
export function createSearchAction(relays?: string[]): (filters: Filter[]) => Observable<EventPacket> {
return (filters: Filter[]) => {
// search local
if (!relays || relays.length === 0)
return cacheRequest(filters).pipe(
map(
(event) =>
({
event,
from: "",
subId: "cache",
type: "EVENT",
message: ["EVENT", "cache", event],
}) as EventPacket,
),
);
let running = true;
const search = async (filters: Filter[], params: Partial<SubscriptionParams>) => {
running = true;
const relay = typeof url === "string" ? await relayPoolService.requestRelay(url, false) : url;
await relayPoolService.requestConnect(relay);
sub = relay.subscribe(filters, {
onevent: (event) => running && params.onevent?.(event),
oneose: () => {
sub?.close();
params.oneose?.();
},
onclose: params.onclose,
});
// search remote
const req = createRxOneshotReq({ filters });
return rxNostr.use(req, { on: { relays } });
};
const cancel = () => {
running = false;
if (sub) sub.close();
};
return { search, cancel, relay: url };
}
const searchCache = new LRU<NostrEvent[]>(10);
export default function SearchResults({ query, relay }: { query: string; relay: string | AbstractRelay }) {
export default function SearchResults({ query, relay }: { query: string; relay: string }) {
const [results, setResults] = useState<NostrEvent[]>([]);
const [searching, setSearching] = useState(false);
const [error, setError] = useState<Error>();
const search = useMemo(() => createSearchAction(relay), [relay]);
const search = useMemo(() => createSearchAction(relay ? [relay] : []), [relay]);
useEffect(() => {
if (query.length < 3) return;
@ -59,22 +57,20 @@ export default function SearchResults({ query, relay }: { query: string; relay:
// run a new search
setResults([]);
setSearching(true);
search
.search([{ search: query, kinds: [kinds.Metadata, kinds.ShortTextNote, kinds.LongFormArticle], limit: 200 }], {
onevent: (event) => {
event = eventStore.add(event, typeof search.relay === "string" ? search.relay : search.relay.url);
setResults((arr) => {
const newArr = [...arr, event];
searchCache.set(query + relay, newArr);
return newArr;
});
},
oneose: () => setSearching(false),
})
.catch((err) => setError(err));
const sub = search([
{ search: query, kinds: [kinds.Metadata, kinds.ShortTextNote, kinds.LongFormArticle], limit: 200 },
]).subscribe((packet) => {
const event = eventStore.add(packet.event, packet.from);
return () => search.cancel();
setResults((arr) => {
const newArr = [...arr, event];
searchCache.set(query + relay, newArr);
return newArr;
});
});
return () => sub.unsubscribe();
}
}, [query, search]);

View File

@ -1,7 +1,6 @@
import { useCallback, useEffect, useMemo } from "react";
import { ButtonGroup, Flex, IconButton, Input, Select } from "@chakra-ui/react";
import { useCallback, useEffect } from "react";
import { ButtonGroup, Flex, IconButton, Input } from "@chakra-ui/react";
import { useNavigate, useSearchParams, Link as RouterLink } from "react-router-dom";
import { AbstractRelay } from "nostr-tools/abstract-relay";
import { useForm } from "react-hook-form";
import { safeDecode } from "../../helpers/nip19";
@ -12,44 +11,32 @@ import PeopleListProvider from "../../providers/local/people-list-provider";
import { useBreakpointValue } from "../../providers/global/breakpoint-provider";
import QRCodeScannerButton from "../../components/qr-code/qr-code-scanner-button";
import SearchResults from "./components/search-results";
import useSearchRelays from "../../hooks/use-search-relays";
import { useRelayInfo } from "../../hooks/use-relay-info";
import WasmRelay from "../../services/wasm-relay";
import relayPoolService from "../../services/relay-pool";
import useSearchRelays, { useCacheRelaySupportsSearch } from "../../hooks/use-search-relays";
import useCacheRelay from "../../hooks/use-cache-relay";
import SearchRelayPicker from "./components/search-relay-picker";
export function SearchPage() {
const cacheRelay = useCacheRelay();
const navigate = useNavigate();
const searchRelays = useSearchRelays();
const { info: cacheRelayInfo } = useRelayInfo(cacheRelay instanceof AbstractRelay ? cacheRelay : undefined, true);
const localSearchSupported =
cacheRelay instanceof WasmRelay ||
(cacheRelay instanceof AbstractRelay && !!cacheRelayInfo?.supported_nips?.includes(50));
const localSearchSupported = useCacheRelaySupportsSearch();
const autoFocusSearch = useBreakpointValue({ base: false, lg: true });
const [params, setParams] = useSearchParams();
const searchQuery = params.get("q") || "";
const relayURL = params.get("relay");
const searchRelay = useMemo(() => {
if (relayURL === "local") return cacheRelay;
else if (relayURL) return relayPoolService.requestRelay(relayURL);
else if (localSearchSupported) return cacheRelay;
else return relayPoolService.requestRelay(searchRelays[0]);
}, [relayURL, localSearchSupported, cacheRelay, searchRelays[0]]);
const relay = params.get("relay") ?? (localSearchSupported ? "" : undefined) ?? searchRelays[0] ?? "";
const { register, handleSubmit, setValue } = useForm({
defaultValues: { query: searchQuery, relay: searchRelay === cacheRelay ? "local" : searchRelay?.url },
defaultValues: { query: searchQuery, relay },
mode: "all",
});
// reset the relay when the search relay changes
useEffect(
() => setValue("relay", searchRelay === cacheRelay ? "local" : searchRelay?.url),
[searchRelay, cacheRelay],
);
// when the relay changes update the form
useEffect(() => {
setValue("relay", relay);
}, [relay]);
const handleSearchText = (text: string) => {
const cleanText = text.trim();
@ -78,11 +65,12 @@ export function SearchPage() {
const newParams = new URLSearchParams(params);
newParams.set("q", values.query);
if (values.relay) newParams.set("relay", values.relay);
else newParams.delete("relay");
setParams(newParams);
}
});
const shouldSearch = searchQuery && searchRelay;
const shouldSearch = !!searchQuery && (!!relay || (localSearchSupported && !!cacheRelay));
return (
<VerticalPageLayout>
@ -106,14 +94,7 @@ export function SearchPage() {
{...register("query", { required: true, minLength: 3 })}
autoComplete="off"
/>
<Select w="auto" {...register("relay")}>
{localSearchSupported && <option value="local">Local Relay</option>}
{searchRelays.map((url) => (
<option key={url} value={url}>
{url}
</option>
))}
</Select>
<SearchRelayPicker {...register("relay")} showLocal />
<ButtonGroup>
<IconButton type="submit" aria-label="Search" icon={<SearchIcon boxSize={5} />} colorScheme="primary" />
<IconButton
@ -127,7 +108,7 @@ export function SearchPage() {
</Flex>
<Flex direction="column" gap="2">
{shouldSearch ? <SearchResults relay={searchRelay as AbstractRelay} query={searchQuery} /> : null}
{shouldSearch ? <SearchResults relay={relay} query={searchQuery} /> : null}
</Flex>
</VerticalPageLayout>
);

View File

@ -1,4 +1,4 @@
import { memo, useCallback, useRef, useState } from "react";
import { memo, useCallback, useEffect, useRef, useState } from "react";
import {
Alert,
AlertDescription,
@ -9,23 +9,20 @@ import {
ButtonGroup,
CloseButton,
Flex,
Heading,
IconButton,
Switch,
Text,
useDisclosure,
} from "@chakra-ui/react";
import { NostrEvent } from "nostr-tools";
import { AbstractRelay, Subscription } from "nostr-tools/abstract-relay";
import { useLocalStorage } from "react-use";
import { Subscription as IDBSubscription } from "nostr-idb";
import _throttle from "lodash.throttle";
import stringify from "json-stringify-deterministic";
import { useLocation, useSearchParams } from "react-router-dom";
import { safeParse } from "applesauce-core/helpers/json";
import { createRxForwardReq, EventPacket } from "rx-nostr";
import { Subscription } from "rxjs";
import VerticalPageLayout from "../../../components/vertical-page-layout";
import BackButton from "../../../components/router/back-button";
import Play from "../../../components/icons/play";
import ClockRewind from "../../../components/icons/clock-rewind";
import HistoryDrawer from "./history-drawer";
@ -35,10 +32,12 @@ import HelpModal from "./help-modal";
import HelpCircle from "../../../components/icons/help-circle";
import { DownloadIcon, ShareIcon } from "../../../components/icons";
import { RelayUrlInput } from "../../../components/relay-url-input";
import { validateRelayURL } from "../../../helpers/relay";
import FilterEditor from "./filter-editor";
import relayPoolService from "../../../services/relay-pool";
import useCacheRelay from "../../../hooks/use-cache-relay";
import SimpleView from "../../../components/layout/presets/simple-view";
import { cacheRequest } from "../../../services/cache-relay";
import { eventStore } from "../../../services/event-store";
import rxNostr from "../../../services/rx-nostr";
const EventTimeline = memo(({ events }: { events: NostrEvent[] }) => {
return (
@ -58,10 +57,9 @@ export default function EventConsoleView() {
const [history, setHistory] = useLocalStorage<string[]>("console-history", []);
const helpModal = useDisclosure();
const queryRelay = useDisclosure({ defaultIsOpen: params.has("relay") });
const [relayURL, setRelayURL] = useState(params.get("relay") || "");
const [relay, setRelay] = useState<AbstractRelay | null>(null);
const [relay, setRelay] = useState(params.get("relay") || "");
const [sub, setSub] = useState<Subscription | IDBSubscription | null>(null);
const [sub, setSub] = useState<Subscription | null>(null);
const [query, setQuery] = useState(() => {
if (params.has("filter") || location.state?.filter) {
@ -82,54 +80,54 @@ export default function EventConsoleView() {
const [events, setEvents] = useState<NostrEvent[]>([]);
const loadEvents = useCallback(async () => {
try {
if (queryRelay.isOpen && !relayURL) throw new Error("Must set relay");
const filter = await processFilter(JSON.parse(query));
setLoading(true);
setHistory((arr) => (arr ? (!arr.includes(query) ? [query, ...arr] : arr) : [query]));
setEvents([]);
setError("");
if (sub) sub.close();
// stop previous subscription
if (sub) sub.unsubscribe();
const buffer: NostrEvent[] = [];
const flush = _throttle(
() => {
setEvents([...buffer]);
},
1000 / 10,
{ trailing: true },
);
const handleEvent = (event: NostrEvent) => {
event = eventStore.add(event);
buffer.push(event);
flush();
};
const handleEventPacket = (packet: EventPacket) => {
const event = eventStore.add(packet.event, packet.from);
buffer.push(event);
flush();
};
if (!cacheRelay) throw new Error("Local relay disabled");
let r = cacheRelay!;
if (queryRelay.isOpen) {
const url = validateRelayURL(relayURL);
if (!relay || relay.url !== url.toString()) {
r = await relayPoolService.requestRelay(url);
await relayPoolService.requestConnect(r);
setRelay(r as AbstractRelay);
} else r = relay;
if (!relay) throw new Error("Must set relay");
// query remote relay
const req = createRxForwardReq();
const sub = rxNostr.use(req, { on: { relays: [relay] } }).subscribe(handleEventPacket);
req.emit([filter]);
setSub(sub);
} else {
if (relay) setRelay(null);
// query cache relay
if (!cacheRelay) throw new Error("Local relay disabled");
const sub = cacheRequest([filter]).subscribe(handleEvent);
setSub(sub);
}
await new Promise<void>((res) => {
const buffer: NostrEvent[] = [];
const flush = _throttle(() => setEvents([...buffer]), 1000 / 10, { trailing: true });
setError("");
const s = r.subscribe([filter], {
onevent: (e) => {
buffer.push(e);
flush();
},
oneose: () => {
setEvents([...buffer]);
res();
},
onclose: (reason) => {
if (!buffer.length) setError(reason);
},
});
setSub(s);
});
} catch (e) {
if (e instanceof Error) setError(e.message);
}
setLoading(false);
}, [queryRelay.isOpen, query, relayURL, relay, sub, cacheRelay]);
}, [queryRelay.isOpen, query, relay, relay, sub, cacheRelay]);
const submitRef = useRef(loadEvents);
submitRef.current = loadEvents;
@ -146,47 +144,57 @@ export default function EventConsoleView() {
const updateSharedURL = () => {
const p = new URLSearchParams(params);
p.set("filter", query);
p.set("relay", relayURL);
if (relay) p.set("relay", relay);
setParams(p, { replace: true });
};
return (
<VerticalPageLayout>
<Flex gap="2" alignItems="center" wrap="wrap">
<BackButton size="sm" />
<Heading size="md">Event Console</Heading>
<SimpleView
title="Event Console"
actions={
<>
<ButtonGroup ml="auto">
<IconButton icon={<HelpCircle />} aria-label="Help" title="Help" size="sm" onClick={helpModal.onOpen} />
<IconButton icon={<ShareIcon />} aria-label="Share" size="sm" onClick={updateSharedURL} />
<IconButton
icon={<ClockRewind />}
aria-label="History"
title="History"
size="sm"
onClick={historyDrawer.onOpen}
/>
</ButtonGroup>
</>
}
>
<Flex gap="2" wrap="wrap" alignItems="center">
<Switch size="sm" isChecked={queryRelay.isOpen} onChange={queryRelay.onToggle}>
Query Relay
</Switch>
{queryRelay.isOpen && (
<RelayUrlInput
size="sm"
borderRadius="md"
w="xs"
value={relayURL}
onChange={(e) => setRelayURL(e.target.value)}
/>
<RelayUrlInput size="sm" borderRadius="md" w="xs" value={relay} onChange={(e) => setRelay(e.target.value)} />
)}
<ButtonGroup ml="auto">
<IconButton icon={<HelpCircle />} aria-label="Help" title="Help" size="sm" onClick={helpModal.onOpen} />
{queryRelay.isOpen && (
<IconButton icon={<ShareIcon />} aria-label="Share" size="sm" onClick={updateSharedURL} />
</Flex>
<FilterEditor value={query} onChange={setQuery} onRun={submitCode} />
<Flex gap="2" alignItems="center">
<Text>{events.length} events</Text>
{sub && <Text color="green.500">Subscribed</Text>}
<ButtonGroup ms="auto" size="sm">
{events.length > 0 && (
<IconButton
aria-label="Download Events"
title="Download Events"
icon={<DownloadIcon />}
onClick={downloadEvents}
/>
)}
<IconButton
icon={<ClockRewind />}
aria-label="History"
title="History"
size="sm"
onClick={historyDrawer.onOpen}
/>
<Button colorScheme="primary" onClick={loadEvents} isLoading={loading} leftIcon={<Play />} size="sm">
<Button colorScheme="primary" onClick={loadEvents} isLoading={loading} leftIcon={<Play />}>
Run
</Button>
</ButtonGroup>
</Flex>
<FilterEditor value={query} onChange={setQuery} onRun={submitCode} />
{error && (
<Alert status="error">
<AlertIcon />
@ -205,23 +213,6 @@ export default function EventConsoleView() {
</Alert>
)}
<Flex gap="2">
<Text>{events.length} events</Text>
{sub && (
<Text color="green.500" ml="auto">
Subscribed
</Text>
)}
{events.length > 0 && (
<IconButton
aria-label="Download Events"
title="Download Events"
icon={<DownloadIcon />}
onClick={downloadEvents}
size="xs"
/>
)}
</Flex>
<Box>
<EventTimeline events={events} />
</Box>
@ -238,6 +229,6 @@ export default function EventConsoleView() {
/>
<HelpModal isOpen={helpModal.isOpen} onClose={helpModal.onClose} />
</VerticalPageLayout>
</SimpleView>
);
}

View File

@ -1,11 +1,13 @@
import { useEffect, useMemo, useState } from "react";
import { useMemo, useState } from "react";
import { Box, Button, Flex, Input, Text } from "@chakra-ui/react";
import AutoSizer from "react-virtualized-auto-sizer";
import ForceGraph, { LinkObject, NodeObject } from "react-force-graph-3d";
import { Filter, kinds } from "nostr-tools";
import dayjs from "dayjs";
import { getProfileContent } from "applesauce-core/helpers";
import { useStoreQuery } from "applesauce-react/hooks";
import { TimelineQuery } from "applesauce-core/queries";
import { useNavigate } from "react-router-dom";
import { useDebounce } from "react-use";
import { useThrottle } from "react-use";
import {
Group,
Mesh,
@ -16,6 +18,7 @@ import {
SpriteMaterial,
TextureLoader,
} from "three";
import dayjs from "dayjs";
import { useActiveAccount } from "applesauce-react/hooks";
import RequireActiveAccount from "../../components/router/require-active-account";
@ -25,12 +28,9 @@ import useUserProfile from "../../hooks/use-user-profile";
import { isPTag } from "../../types/nostr-event";
import { ChevronLeftIcon } from "../../components/icons";
import { useReadRelays } from "../../hooks/use-client-relays";
import { subscribeMany } from "../../helpers/relay";
import useUserProfiles from "../../hooks/use-user-profiles";
import { eventStore } from "../../services/event-store";
import { getProfileContent } from "applesauce-core/helpers";
import { useStoreQuery } from "applesauce-react/hooks";
import { TimelineQuery } from "applesauce-core/queries";
import useForwardSubscription from "../../hooks/use-forward-subscription";
type NodeType = { id: string; image?: string; name?: string };
@ -48,27 +48,20 @@ function NetworkDMGraphPage() {
const [until, setUntil] = useState(dayjs().unix());
const [since, setSince] = useState(dayjs().subtract(1, "week").unix());
const [fetchData] = useDebounce(
() => {
if (!contacts) return;
const filter: Filter = {
const filters = useMemo<Filter[]>(
() => [
{
authors: contactsPubkeys,
kinds: [kinds.EncryptedDirectMessage],
since,
until,
};
const sub = subscribeMany(Array.from(relays), [filter], {
onevent: (event) => eventStore.add(event),
oneose: () => sub.close(),
});
},
2 * 1000,
[relays, contactsPubkeys, since, until],
},
],
[contactsPubkeys, since, until],
);
useEffect(() => {
fetchData();
}, [relays, contactsPubkeys, since, until]);
const throttledFilter = useThrottle(filters, 2 * 1000);
useForwardSubscription(relays, throttledFilter);
const selfMetadata = useUserProfile(account.pubkey);
const userProfiles = useUserProfiles(contactsPubkeys);

View File

@ -1,25 +1,22 @@
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { Navigate } from "react-router-dom";
import { Button, Flex, Heading, Input, Link } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { Filter, NostrEvent } from "nostr-tools";
import { useForm } from "react-hook-form";
import { Subscription, getEventUID } from "nostr-idb";
import { getEventUID } from "nostr-idb";
import VerticalPageLayout from "../../components/vertical-page-layout";
import useRouteSearchValue from "../../hooks/use-route-search-value";
import { subscribeMany } from "../../helpers/relay";
import { DEFAULT_SEARCH_RELAYS, WIKI_RELAYS } from "../../const";
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
import { cacheRelay$ } from "../../services/cache-relay";
import WikiPageResult from "./components/wiki-page-result";
import { useWebOfTrust } from "../../providers/global/web-of-trust-provider";
import { useObservable } from "applesauce-react/hooks";
import { eventStore } from "../../services/event-store";
import { ErrorBoundary } from "../../components/error-boundary";
import { createSearchAction } from "../search/components/search-results";
export default function WikiSearchView() {
const cacheRelay = useObservable(cacheRelay$);
const webOfTrust = useWebOfTrust();
const { value: query, setValue: setQuery } = useRouteSearchValue("q");
if (!query) return <Navigate to="/wiki" />;
@ -30,6 +27,7 @@ export default function WikiSearchView() {
});
const [results, setResults] = useState<NostrEvent[]>([]);
const search = useMemo(() => createSearchAction([...DEFAULT_SEARCH_RELAYS, ...WIKI_RELAYS]), []);
useEffect(() => {
setResults([]);
@ -38,25 +36,18 @@ export default function WikiSearchView() {
const seen = new Set<string>();
const handleEvent = (event: NostrEvent) => {
eventStore.add(event);
if (seen.has(getEventUID(event))) return;
setResults((arr) => arr.concat(event));
seen.add(getEventUID(event));
};
const remoteSearchSub = subscribeMany([...DEFAULT_SEARCH_RELAYS, ...WIKI_RELAYS], [filter], {
onevent: handleEvent,
oneose: () => remoteSearchSub.close(),
const sub = search([filter]).subscribe((packet) => {
eventStore.add(packet.event, packet.from);
handleEvent(packet.event);
});
if (cacheRelay) {
const localSearchSub: Subscription = cacheRelay.subscribe([filter], {
onevent: handleEvent,
oneose: () => localSearchSub.close(),
});
}
}, [query, setResults, cacheRelay]);
return () => sub.unsubscribe();
}, [query, setResults]);
const sorted = webOfTrust ? webOfTrust.sortByDistanceAndConnections(results, (p) => p.pubkey) : results;