diff --git a/.changeset/five-flowers-roll.md b/.changeset/five-flowers-roll.md new file mode 100644 index 000000000..5155872fb --- /dev/null +++ b/.changeset/five-flowers-roll.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Use TimelineLoader from applesauce packages diff --git a/package.json b/package.json index a63fb4c20..3eef4e185 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "cap-sync-version": "pnpm dlx capacitor-set-version . -v $(jq -r .version package.json) -b 1" }, "dependencies": { - "@cashu/cashu-ts": "^2.1.0", + "@cashu/cashu-ts": "^2.2.0", "@chakra-ui/anatomy": "^2.3.4", "@chakra-ui/breakpoint-utils": "^2.0.8", "@chakra-ui/icons": "^2.2.4", @@ -73,6 +73,7 @@ "framer-motion": "^10.18.0", "gif-picker-react": "^1.4.0", "handlebars": "^4.7.8", + "hash-sum": "^2.0.0", "hls.js": "^1.5.20", "i18n-iso-countries": "^7.13.0", "idb": "^8.0.2", @@ -120,7 +121,6 @@ "three": "^0.170.0", "three-spritetext": "^1.9.4", "three-stdlib": "^2.35.13", - "tiny-lru": "^11.2.11", "unified": "^11.0.5", "uuid": "^11.0.5", "vite-plugin-funding": "^0.1.0", @@ -146,6 +146,7 @@ "@types/debug": "^4.1.12", "@types/dom-serial": "^1.0.6", "@types/file-saver": "^2.0.7", + "@types/hash-sum": "^1.0.2", "@types/identicon.js": "^2.3.5", "@types/json-schema": "^7.0.15", "@types/leaflet": "^1.9.16", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31b5e6144..963cbb2b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,8 +13,8 @@ importers: .: dependencies: '@cashu/cashu-ts': - specifier: ^2.1.0 - version: 2.1.0 + specifier: ^2.2.0 + version: 2.2.0 '@chakra-ui/anatomy': specifier: ^2.3.4 version: 2.3.4 @@ -92,10 +92,10 @@ importers: version: 1.3.1 '@uiw/codemirror-theme-github': specifier: ^4.23.7 - version: 4.23.7(@codemirror/language@6.10.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2) + version: 4.23.7(@codemirror/language@6.10.8)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2) '@uiw/react-codemirror': specifier: ^4.23.7 - version: 4.23.7(@babel/runtime@7.26.7)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.2)(codemirror@6.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 4.23.7(@babel/runtime@7.26.7)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.2)(codemirror@6.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@webscopeio/react-textarea-autocomplete': specifier: ^4.9.2 version: 4.9.2(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -104,28 +104,28 @@ importers: version: 0.7.2 applesauce-accounts: specifier: next - version: 0.0.0-next-20250131214402(typescript@5.7.3) + version: 0.0.0-next-20250203180810(typescript@5.7.3) applesauce-content: specifier: next - version: 0.0.0-next-20250131214402(typescript@5.7.3) + version: 0.0.0-next-20250203180810(typescript@5.7.3) applesauce-core: specifier: next - version: 0.0.0-next-20250131214402(typescript@5.7.3) + version: 0.0.0-next-20250203180810(typescript@5.7.3) applesauce-factory: specifier: next - version: 0.0.0-next-20250131214402(typescript@5.7.3) + version: 0.0.0-next-20250203180810(typescript@5.7.3) applesauce-loaders: specifier: next - version: 0.0.0-next-20250131214402(typescript@5.7.3) + version: 0.0.0-next-20250203180810(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-20250131214402(typescript@5.7.3) + version: 0.0.0-next-20250203180810(typescript@5.7.3) applesauce-signers: specifier: next - version: 0.0.0-next-20250131214402(typescript@5.7.3) + version: 0.0.0-next-20250203180810(typescript@5.7.3) bech32: specifier: ^2.0.0 version: 2.0.0 @@ -149,7 +149,7 @@ importers: version: 6.0.1 codemirror-json-schema: specifier: ^0.7.9 - version: 0.7.9(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2)(@lezer/common@1.2.3) + version: 0.7.9(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2)(@lezer/common@1.2.3) dayjs: specifier: ^1.11.13 version: 1.11.13 @@ -174,6 +174,9 @@ importers: handlebars: specifier: ^4.7.8 version: 4.7.8 + hash-sum: + specifier: ^2.0.0 + version: 2.0.0 hls.js: specifier: ^1.5.20 version: 1.5.20 @@ -315,9 +318,6 @@ importers: three-stdlib: specifier: ^2.35.13 version: 2.35.13(three@0.170.0) - tiny-lru: - specifier: ^11.2.11 - version: 11.2.11 unified: specifier: ^11.0.5 version: 11.0.5 @@ -388,6 +388,9 @@ importers: '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 + '@types/hash-sum': + specifier: ^1.0.2 + version: 1.0.2 '@types/identicon.js': specifier: ^2.3.5 version: 2.3.5 @@ -1032,8 +1035,8 @@ packages: '@cashu/cashu-ts@2.0.0-rc1': resolution: {integrity: sha512-39459l7x/fUMEgOsCdGLLl6rMekO4nbv+wEuavmyElh8hgN8t66wcb29AJvdFTb6K3lPACKF2rs/jAlPYrN7Ng==} - '@cashu/cashu-ts@2.1.0': - resolution: {integrity: sha512-qFfFz1dx9keJxumjk5FyTvI1j0Yp/P5LXDy0cGO4Xlp3WYKOI1nykNOTPd+bTY9vSkvIM+xuXRer9BtQxqHtwA==} + '@cashu/cashu-ts@2.2.0': + resolution: {integrity: sha512-7b6pGyjjpm3uAJvmOL+ztpRxqp1qnmzGpydp+Pu30pOjxj93EhejPTJVrZMDJ0P35y6u5+5jIjHF4k0fpovvmg==} '@cashu/crypto@0.2.7': resolution: {integrity: sha512-1aaDfUjiHNXoJqg8nW+341TLWV9W28DsVNXJUKcHL0yAmwLs5+56SSnb8LLDJzPamLVoYL0U0bda91klAzptig==} @@ -1241,8 +1244,8 @@ packages: '@codemirror/search@6.5.8': resolution: {integrity: sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==} - '@codemirror/state@6.5.1': - resolution: {integrity: sha512-3rA9lcwciEB47ZevqvD8qgbzhM9qMb8vCcQCNmDfVRPQG4JT9mSb0Jg8H7YjKGGQcFnLN323fj9jdnG59Kx6bg==} + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} '@codemirror/theme-one-dark@6.1.2': resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==} @@ -1717,98 +1720,98 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.32.1': - resolution: {integrity: sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==} + '@rollup/rollup-android-arm-eabi@4.34.1': + resolution: {integrity: sha512-kwctwVlswSEsr4ljpmxKrRKp1eG1v2NAhlzFzDf1x1OdYaMjBYjDCbHkzWm57ZXzTwqn8stMXgROrnMw8dJK3w==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.32.1': - resolution: {integrity: sha512-If3PDskT77q7zgqVqYuj7WG3WC08G1kwXGVFi9Jr8nY6eHucREHkfpX79c0ACAjLj3QIWKPJR7w4i+f5EdLH5Q==} + '@rollup/rollup-android-arm64@4.34.1': + resolution: {integrity: sha512-4H5ZtZitBPlbPsTv6HBB8zh1g5d0T8TzCmpndQdqq20Ugle/nroOyDMf9p7f88Gsu8vBLU78/cuh8FYHZqdXxw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.32.1': - resolution: {integrity: sha512-zCpKHioQ9KgZToFp5Wvz6zaWbMzYQ2LJHQ+QixDKq52KKrF65ueu6Af4hLlLWHjX1Wf/0G5kSJM9PySW9IrvHA==} + '@rollup/rollup-darwin-arm64@4.34.1': + resolution: {integrity: sha512-f2AJ7Qwx9z25hikXvg+asco8Sfuc5NCLg8rmqQBIOUoWys5sb/ZX9RkMZDPdnnDevXAMJA5AWLnRBmgdXGEUiA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.32.1': - resolution: {integrity: sha512-sFvF+t2+TyUo/ZQqUcifrJIgznx58oFZbdHS9TvHq3xhPVL9nOp+yZ6LKrO9GWTP+6DbFtoyLDbjTpR62Mbr3Q==} + '@rollup/rollup-darwin-x64@4.34.1': + resolution: {integrity: sha512-+/2JBrRfISCsWE4aEFXxd+7k9nWGXA8+wh7ZUHn/u8UDXOU9LN+QYKKhd57sIn6WRcorOnlqPMYFIwie/OHXWw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.32.1': - resolution: {integrity: sha512-NbOa+7InvMWRcY9RG+B6kKIMD/FsnQPH0MWUvDlQB1iXnF/UcKSudCXZtv4lW+C276g3w5AxPbfry5rSYvyeYA==} + '@rollup/rollup-freebsd-arm64@4.34.1': + resolution: {integrity: sha512-SUeB0pYjIXwT2vfAMQ7E4ERPq9VGRrPR7Z+S4AMssah5EHIilYqjWQoTn5dkDtuIJUSTs8H+C9dwoEcg3b0sCA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.32.1': - resolution: {integrity: sha512-JRBRmwvHPXR881j2xjry8HZ86wIPK2CcDw0EXchE1UgU0ubWp9nvlT7cZYKc6bkypBt745b4bglf3+xJ7hXWWw==} + '@rollup/rollup-freebsd-x64@4.34.1': + resolution: {integrity: sha512-L3T66wAZiB/ooiPbxz0s6JEX6Sr2+HfgPSK+LMuZkaGZFAFCQAHiP3dbyqovYdNaiUXcl9TlgnIbcsIicAnOZg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.32.1': - resolution: {integrity: sha512-PKvszb+9o/vVdUzCCjL0sKHukEQV39tD3fepXxYrHE3sTKrRdCydI7uldRLbjLmDA3TFDmh418XH19NOsDRH8g==} + '@rollup/rollup-linux-arm-gnueabihf@4.34.1': + resolution: {integrity: sha512-UBXdQ4+ATARuFgsFrQ+tAsKvBi/Hly99aSVdeCUiHV9dRTTpMU7OrM3WXGys1l40wKVNiOl0QYY6cZQJ2xhKlQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.32.1': - resolution: {integrity: sha512-9WHEMV6Y89eL606ReYowXuGF1Yb2vwfKWKdD1A5h+OYnPZSJvxbEjxTRKPgi7tkP2DSnW0YLab1ooy+i/FQp/Q==} + '@rollup/rollup-linux-arm-musleabihf@4.34.1': + resolution: {integrity: sha512-m/yfZ25HGdcCSwmopEJm00GP7xAUyVcBPjttGLRAqZ60X/bB4Qn6gP7XTwCIU6bITeKmIhhwZ4AMh2XLro+4+w==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.32.1': - resolution: {integrity: sha512-tZWc9iEt5fGJ1CL2LRPw8OttkCBDs+D8D3oEM8mH8S1ICZCtFJhD7DZ3XMGM8kpqHvhGUTvNUYVDnmkj4BDXnw==} + '@rollup/rollup-linux-arm64-gnu@4.34.1': + resolution: {integrity: sha512-Wy+cUmFuvziNL9qWRRzboNprqSQ/n38orbjRvd6byYWridp5TJ3CD+0+HUsbcWVSNz9bxkDUkyASGP0zS7GAvg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.32.1': - resolution: {integrity: sha512-FTYc2YoTWUsBz5GTTgGkRYYJ5NGJIi/rCY4oK/I8aKowx1ToXeoVVbIE4LGAjsauvlhjfl0MYacxClLld1VrOw==} + '@rollup/rollup-linux-arm64-musl@4.34.1': + resolution: {integrity: sha512-CQ3MAGgiFmQW5XJX5W3wnxOBxKwFlUAgSXFA2SwgVRjrIiVt5LHfcQLeNSHKq5OEZwv+VCBwlD1+YKCjDG8cpg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.32.1': - resolution: {integrity: sha512-F51qLdOtpS6P1zJVRzYM0v6MrBNypyPEN1GfMiz0gPu9jN8ScGaEFIZQwteSsGKg799oR5EaP7+B2jHgL+d+Kw==} + '@rollup/rollup-linux-loongarch64-gnu@4.34.1': + resolution: {integrity: sha512-rSzb1TsY4lSwH811cYC3OC2O2mzNMhM13vcnA7/0T6Mtreqr3/qs6WMDriMRs8yvHDI54qxHgOk8EV5YRAHFbw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.32.1': - resolution: {integrity: sha512-wO0WkfSppfX4YFm5KhdCCpnpGbtgQNj/tgvYzrVYFKDpven8w2N6Gg5nB6w+wAMO3AIfSTWeTjfVe+uZ23zAlg==} + '@rollup/rollup-linux-powerpc64le-gnu@4.34.1': + resolution: {integrity: sha512-fwr0n6NS0pG3QxxlqVYpfiY64Fd1Dqd8Cecje4ILAV01ROMp4aEdCj5ssHjRY3UwU7RJmeWd5fi89DBqMaTawg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.32.1': - resolution: {integrity: sha512-iWswS9cIXfJO1MFYtI/4jjlrGb/V58oMu4dYJIKnR5UIwbkzR0PJ09O0PDZT0oJ3LYWXBSWahNf/Mjo6i1E5/g==} + '@rollup/rollup-linux-riscv64-gnu@4.34.1': + resolution: {integrity: sha512-4uJb9qz7+Z/yUp5RPxDGGGUcoh0PnKF33QyWgEZ3X/GocpWb6Mb+skDh59FEt5d8+Skxqs9mng6Swa6B2AmQZg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.32.1': - resolution: {integrity: sha512-RKt8NI9tebzmEthMnfVgG3i/XeECkMPS+ibVZjZ6mNekpbbUmkNWuIN2yHsb/mBPyZke4nlI4YqIdFPgKuoyQQ==} + '@rollup/rollup-linux-s390x-gnu@4.34.1': + resolution: {integrity: sha512-QlIo8ndocWBEnfmkYqj8vVtIUpIqJjfqKggjy7IdUncnt8BGixte1wDON7NJEvLg3Kzvqxtbo8tk+U1acYEBlw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.32.1': - resolution: {integrity: sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg==} + '@rollup/rollup-linux-x64-gnu@4.34.1': + resolution: {integrity: sha512-hzpleiKtq14GWjz3ahWvJXgU1DQC9DteiwcsY4HgqUJUGxZThlL66MotdUEK9zEo0PK/2ADeZGM9LIondE302A==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.32.1': - resolution: {integrity: sha512-BLoiyHDOWoS3uccNSADMza6V6vCNiphi94tQlVIL5de+r6r/CCQuNnerf+1g2mnk2b6edp5dk0nhdZ7aEjOBsA==} + '@rollup/rollup-linux-x64-musl@4.34.1': + resolution: {integrity: sha512-jqtKrO715hDlvUcEsPn55tZt2TEiBvBtCMkUuU0R6fO/WPT7lO9AONjPbd8II7/asSiNVQHCMn4OLGigSuxVQA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.32.1': - resolution: {integrity: sha512-w2l3UnlgYTNNU+Z6wOR8YdaioqfEnwPjIsJ66KxKAf0p+AuL2FHeTX6qvM+p/Ue3XPBVNyVSfCrfZiQh7vZHLQ==} + '@rollup/rollup-win32-arm64-msvc@4.34.1': + resolution: {integrity: sha512-RnHy7yFf2Wz8Jj1+h8klB93N0NHNHXFhNwAmiy9zJdpY7DE01VbEVtPdrK1kkILeIbHGRJjvfBDBhnxBr8kD4g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.32.1': - resolution: {integrity: sha512-Am9H+TGLomPGkBnaPWie4F3x+yQ2rr4Bk2jpwy+iV+Gel9jLAu/KqT8k3X4jxFPW6Zf8OMnehyutsd+eHoq1WQ==} + '@rollup/rollup-win32-ia32-msvc@4.34.1': + resolution: {integrity: sha512-i7aT5HdiZIcd7quhzvwQ2oAuX7zPYrYfkrd1QFfs28Po/i0q6kas/oRrzGlDhAEyug+1UfUtkWdmoVlLJj5x9Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.32.1': - resolution: {integrity: sha512-ar80GhdZb4DgmW3myIS9nRFYcpJRSME8iqWgzH2i44u+IdrzmiXVxeFnExQ5v4JYUSpg94bWjevMG8JHf1Da5Q==} + '@rollup/rollup-win32-x64-msvc@4.34.1': + resolution: {integrity: sha512-k3MVFD9Oq+laHkw2N2v7ILgoa9017ZMF/inTtHzyTVZjYs9cSH18sdyAf6spBAJIGwJ5UaC7et2ZH1WCdlhkMw==} cpu: [x64] os: [win32] @@ -1951,6 +1954,9 @@ packages: '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/hash-sum@1.0.2': + resolution: {integrity: sha512-UP28RddqY8xcU0SCEp9YKutQICXpaAq9N8U2klqF5hegGha7KzTOL8EdhIIV3bOSGBzjEpN9bU/d+nNZBdJYVw==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -2186,32 +2192,32 @@ packages: engines: {node: '>=8.0.0'} hasBin: true - applesauce-accounts@0.0.0-next-20250131214402: - resolution: {integrity: sha512-5BUtFswVFg82Oq9MG6VdkEa5VeOAuvoPMi7Fr/I4IxqJwpZDSFIU/rkuDOMtHcj+V6TCEYeTO670f2p7B4/yMg==} + applesauce-accounts@0.0.0-next-20250203180810: + resolution: {integrity: sha512-yymm0dQgBJGgf3nlv1DC+AyxK+cvHLZczmRLyYW4k4MCNM7mO7qwnFGbMJJftDmVw5l8f9xBu0T/8r3IWsOxJw==} - applesauce-content@0.0.0-next-20250131214402: - resolution: {integrity: sha512-+cDqt4IRIjQ0Ncht0WLCcMueDaZRtsuAXBw3k4/ND37qUHsnt5Qj+VfnWr5Dfmhpk0HD3ghMqbMXZBwkybN6NA==} + applesauce-content@0.0.0-next-20250203180810: + resolution: {integrity: sha512-7TpIqocvhn5g4M/Biua7AP3pWo4YtDa1C21uci/ElkbuqMNhVT37ASwttGM8GPcfLTxQ7uBkpPjOBTXm74D8Rw==} - applesauce-core@0.0.0-next-20250131214402: - resolution: {integrity: sha512-5+KRKnV2d+6z9QFzSHUGKHxbtoByRkx0MQpjOePNBcl/DCbVmC7Zf4Bo3VrouNeBG5deiCMIy5PGp0A4uuGXRg==} + applesauce-core@0.0.0-next-20250203180810: + resolution: {integrity: sha512-BNXzw8ydbp4KiPfn7X5iCMaeaNlAW5tr3o6dWz9UwV7ItWieQLd17ySh0KPLdVbE+e42msBN7ByE7iNC0ItJOQ==} applesauce-core@0.10.0: resolution: {integrity: sha512-QMhUh4FIARcqY5soCB4Z8DIu+py0rYb28IgWT4gP9DLBGpDrY8lStXk7W1/46TLjEH97y0hbiXFK7kMCZ31oOQ==} - applesauce-factory@0.0.0-next-20250131214402: - resolution: {integrity: sha512-94yTHDr+N+/IyDD88ruHzToG4craEyJ/u5nfU2TP85o6snR8Ngw5qmX5d8sD6RHJm9UjMpzfTcFjxU9ZCnbwjg==} + applesauce-factory@0.0.0-next-20250203180810: + resolution: {integrity: sha512-O4xMkwaOTX7t6JYaS78WTKzIRekCipCBnDeZpyYmLK+h4rQ+8aKd21VonV4hfh9A8dTeS28QNFsdDabbH+22Nw==} - applesauce-loaders@0.0.0-next-20250131214402: - resolution: {integrity: sha512-ycKuHqqT951jwRpvMNjrhQq1YRhFHLYh57NIFJJTZFHOHzfi3OJjpagueJr5H7brFGOFdYtaOgJew3sxptucKw==} + applesauce-loaders@0.0.0-next-20250203180810: + resolution: {integrity: sha512-ZWU23UtUQPQdM26NFLUralB8M8IuxrJqYqCwA5j5pozi5EG49OxxdPaZuXn3u37VRX8mibQYRVQqR+rhPWEC6g==} applesauce-net@0.10.0: resolution: {integrity: sha512-ZsAs/MkeGHiPZ2/a8lwP8lx/Eh+5Dot0qG4BLTAqjg4emP/RsiqW+hyc6v6QcVbdvuR0+hP1gka3+wWtiy/cTA==} - applesauce-react@0.0.0-next-20250131214402: - resolution: {integrity: sha512-wqlVTecq3Aj+YY5gi71VOqKWNMwsK4nUFYuoY9ZFh1ueJG0anKqYLwZSrMvswsbt3JyhG4xtLrPc7FGdIsdmqQ==} + applesauce-react@0.0.0-next-20250203180810: + resolution: {integrity: sha512-/LloC27jD9BieXmoTZs59SVbHw/vzjh/mh1jt98TQT8NcSh9zoPD1quR4a4BNFXUmeDDAxOi4VcrTOCVXAvweA==} - applesauce-signers@0.0.0-next-20250131214402: - resolution: {integrity: sha512-rWlDZrPPGmt2MWQ02WWRfUtApTDNJhu7Sze0hNZ4qaVSwIezq7l2f1N4vdB34A3gsagi9aXhhoipF72PtkbFHg==} + applesauce-signers@0.0.0-next-20250203180810: + resolution: {integrity: sha512-PJBPVmLW+lDYQ2TYflJGr/ddmvDu0v19l6I9XBhHfmlS1ypAej9kk4c4X56gRzROeq2zgIs/SSi1+Jm454AqRw==} arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -2312,8 +2318,8 @@ packages: bare-path@3.0.0: resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - bare-stream@2.6.4: - resolution: {integrity: sha512-G6i3A74FjNq4nVrrSTUz5h3vgXzBJnjmWAVlBWaZETkgu+LgKd7AiyOml3EDJY1AHlIbBHKDXE+TUT53Ff8OaA==} + bare-stream@2.6.5: + resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} peerDependencies: bare-buffer: '*' bare-events: '*' @@ -3575,8 +3581,8 @@ packages: immutability-helper@3.1.1: resolution: {integrity: sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ==} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} indent-string@4.0.0: @@ -5129,8 +5135,8 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - rollup@4.32.1: - resolution: {integrity: sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==} + rollup@4.34.1: + resolution: {integrity: sha512-iYZ/+PcdLYSGfH3S+dGahlW/RWmsqDhLgj1BT9DH/xXJ0ggZN7xkdP9wipPNjjNLczI+fmMLmTB9pye+d2r4GQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -5557,10 +5563,6 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tiny-lru@11.2.11: - resolution: {integrity: sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==} - engines: {node: '>=12'} - tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} @@ -6946,12 +6948,11 @@ snapshots: '@scure/bip32': 1.6.2 buffer: 6.0.3 - '@cashu/cashu-ts@2.1.0': + '@cashu/cashu-ts@2.2.0': dependencies: '@cashu/crypto': 0.3.4 '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 - '@scure/bip32': 1.6.2 buffer: 6.0.3 '@cashu/crypto@0.2.7': @@ -7291,14 +7292,14 @@ snapshots: '@codemirror/autocomplete@6.18.4': dependencies: '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 '@lezer/common': 1.2.3 '@codemirror/commands@6.8.0': dependencies: '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 '@lezer/common': 1.2.3 @@ -7311,7 +7312,7 @@ snapshots: dependencies: '@codemirror/autocomplete': 6.18.4 '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 @@ -7320,7 +7321,7 @@ snapshots: '@codemirror/language@6.10.8': dependencies: - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 @@ -7329,30 +7330,30 @@ snapshots: '@codemirror/lint@6.8.4': dependencies: - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 crelt: 1.0.6 '@codemirror/search@6.5.8': dependencies: - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 crelt: 1.0.6 - '@codemirror/state@6.5.1': + '@codemirror/state@6.5.2': dependencies: '@marijn/find-cluster-break': 1.0.2 '@codemirror/theme-one-dark@6.1.2': dependencies: '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 '@lezer/highlight': 1.2.1 '@codemirror/view@6.36.2': dependencies: - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 style-mod: 4.1.2 w3c-keyname: 2.2.8 @@ -7906,61 +7907,61 @@ snapshots: optionalDependencies: rollup: 2.79.2 - '@rollup/rollup-android-arm-eabi@4.32.1': + '@rollup/rollup-android-arm-eabi@4.34.1': optional: true - '@rollup/rollup-android-arm64@4.32.1': + '@rollup/rollup-android-arm64@4.34.1': optional: true - '@rollup/rollup-darwin-arm64@4.32.1': + '@rollup/rollup-darwin-arm64@4.34.1': optional: true - '@rollup/rollup-darwin-x64@4.32.1': + '@rollup/rollup-darwin-x64@4.34.1': optional: true - '@rollup/rollup-freebsd-arm64@4.32.1': + '@rollup/rollup-freebsd-arm64@4.34.1': optional: true - '@rollup/rollup-freebsd-x64@4.32.1': + '@rollup/rollup-freebsd-x64@4.34.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.32.1': + '@rollup/rollup-linux-arm-gnueabihf@4.34.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.32.1': + '@rollup/rollup-linux-arm-musleabihf@4.34.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.32.1': + '@rollup/rollup-linux-arm64-gnu@4.34.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.32.1': + '@rollup/rollup-linux-arm64-musl@4.34.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.32.1': + '@rollup/rollup-linux-loongarch64-gnu@4.34.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.32.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.34.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.32.1': + '@rollup/rollup-linux-riscv64-gnu@4.34.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.32.1': + '@rollup/rollup-linux-s390x-gnu@4.34.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.32.1': + '@rollup/rollup-linux-x64-gnu@4.34.1': optional: true - '@rollup/rollup-linux-x64-musl@4.32.1': + '@rollup/rollup-linux-x64-musl@4.34.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.32.1': + '@rollup/rollup-win32-arm64-msvc@4.34.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.32.1': + '@rollup/rollup-win32-ia32-msvc@4.34.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.32.1': + '@rollup/rollup-win32-x64-msvc@4.34.1': optional: true '@sagold/json-pointer@5.1.2': {} @@ -8183,6 +8184,8 @@ snapshots: '@types/geojson@7946.0.16': {} + '@types/hash-sum@1.0.2': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -8283,38 +8286,38 @@ snapshots: '@types/webxr@0.5.21': {} - '@uiw/codemirror-extensions-basic-setup@4.23.7(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2)': + '@uiw/codemirror-extensions-basic-setup@4.23.7(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2)': dependencies: '@codemirror/autocomplete': 6.18.4 '@codemirror/commands': 6.8.0 '@codemirror/language': 6.10.8 '@codemirror/lint': 6.8.4 '@codemirror/search': 6.5.8 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 - '@uiw/codemirror-theme-github@4.23.7(@codemirror/language@6.10.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2)': + '@uiw/codemirror-theme-github@4.23.7(@codemirror/language@6.10.8)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2)': dependencies: - '@uiw/codemirror-themes': 4.23.7(@codemirror/language@6.10.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2) + '@uiw/codemirror-themes': 4.23.7(@codemirror/language@6.10.8)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2) transitivePeerDependencies: - '@codemirror/language' - '@codemirror/state' - '@codemirror/view' - '@uiw/codemirror-themes@4.23.7(@codemirror/language@6.10.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2)': + '@uiw/codemirror-themes@4.23.7(@codemirror/language@6.10.8)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2)': dependencies: '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 - '@uiw/react-codemirror@4.23.7(@babel/runtime@7.26.7)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.2)(codemirror@6.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@uiw/react-codemirror@4.23.7(@babel/runtime@7.26.7)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.2)(codemirror@6.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.7 '@codemirror/commands': 6.8.0 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/theme-one-dark': 6.1.2 '@codemirror/view': 6.36.2 - '@uiw/codemirror-extensions-basic-setup': 4.23.7(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2) + '@uiw/codemirror-extensions-basic-setup': 4.23.7(@codemirror/autocomplete@6.18.4)(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2) codemirror: 6.0.1 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) @@ -8420,10 +8423,10 @@ snapshots: dependencies: entities: 2.2.0 - applesauce-accounts@0.0.0-next-20250131214402(typescript@5.7.3): + applesauce-accounts@0.0.0-next-20250203180810(typescript@5.7.3): dependencies: '@noble/hashes': 1.7.1 - applesauce-signers: 0.0.0-next-20250131214402(typescript@5.7.3) + applesauce-signers: 0.0.0-next-20250203180810(typescript@5.7.3) nanoid: 5.0.9 nostr-tools: 2.10.4(typescript@5.7.3) rxjs: 7.8.1 @@ -8431,13 +8434,13 @@ snapshots: - supports-color - typescript - applesauce-content@0.0.0-next-20250131214402(typescript@5.7.3): + applesauce-content@0.0.0-next-20250203180810(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-20250131214402(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203180810(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 @@ -8448,7 +8451,7 @@ snapshots: - supports-color - typescript - applesauce-core@0.0.0-next-20250131214402(typescript@5.7.3): + applesauce-core@0.0.0-next-20250203180810(typescript@5.7.3): dependencies: '@scure/base': 1.2.4 debug: 4.4.0 @@ -8476,19 +8479,19 @@ snapshots: - supports-color - typescript - applesauce-factory@0.0.0-next-20250131214402(typescript@5.7.3): + applesauce-factory@0.0.0-next-20250203180810(typescript@5.7.3): dependencies: - applesauce-content: 0.0.0-next-20250131214402(typescript@5.7.3) - applesauce-core: 0.0.0-next-20250131214402(typescript@5.7.3) + applesauce-content: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203180810(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-20250131214402(typescript@5.7.3): + applesauce-loaders@0.0.0-next-20250203180810(typescript@5.7.3): dependencies: - applesauce-core: 0.0.0-next-20250131214402(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203180810(typescript@5.7.3) nanoid: 5.0.9 nostr-tools: 2.10.4(typescript@5.7.3) rx-nostr: 3.5.0 @@ -8507,12 +8510,12 @@ snapshots: - supports-color - typescript - applesauce-react@0.0.0-next-20250131214402(typescript@5.7.3): + applesauce-react@0.0.0-next-20250203180810(typescript@5.7.3): dependencies: - applesauce-accounts: 0.0.0-next-20250131214402(typescript@5.7.3) - applesauce-content: 0.0.0-next-20250131214402(typescript@5.7.3) - applesauce-core: 0.0.0-next-20250131214402(typescript@5.7.3) - applesauce-factory: 0.0.0-next-20250131214402(typescript@5.7.3) + applesauce-accounts: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-content: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-factory: 0.0.0-next-20250203180810(typescript@5.7.3) nostr-tools: 2.10.4(typescript@5.7.3) react: 18.3.1 rxjs: 7.8.1 @@ -8520,12 +8523,12 @@ snapshots: - supports-color - typescript - applesauce-signers@0.0.0-next-20250131214402(typescript@5.7.3): + applesauce-signers@0.0.0-next-20250203180810(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-20250131214402(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203180810(typescript@5.7.3) debug: 4.4.0 nanoid: 5.0.9 nostr-tools: 2.10.4(typescript@5.7.3) @@ -8625,7 +8628,7 @@ snapshots: dependencies: bare-events: 2.5.4 bare-path: 3.0.0 - bare-stream: 2.6.4(bare-events@2.5.4) + bare-stream: 2.6.5(bare-events@2.5.4) transitivePeerDependencies: - bare-buffer optional: true @@ -8638,7 +8641,7 @@ snapshots: bare-os: 3.4.0 optional: true - bare-stream@2.6.4(bare-events@2.5.4): + bare-stream@2.6.5(bare-events@2.5.4): dependencies: streamx: 2.22.0 optionalDependencies: @@ -8680,7 +8683,7 @@ snapshots: blossom-client-sdk@0.0.0-next-20250124155258: dependencies: - '@cashu/cashu-ts': 2.1.0 + '@cashu/cashu-ts': 2.2.0 '@noble/hashes': 1.7.1 blossom-client-sdk@0.9.1: @@ -8897,11 +8900,11 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - codemirror-json-schema@0.7.9(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/state@6.5.1)(@codemirror/view@6.36.2)(@lezer/common@1.2.3): + codemirror-json-schema@0.7.9(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2)(@lezer/common@1.2.3): dependencies: '@codemirror/language': 6.10.8 '@codemirror/lint': 6.8.4 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 '@lezer/common': 1.2.3 '@sagold/json-pointer': 5.1.2 @@ -8923,7 +8926,7 @@ snapshots: codemirror-json5@1.0.3: dependencies: '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 @@ -8944,7 +8947,7 @@ snapshots: '@codemirror/language': 6.10.8 '@codemirror/lint': 6.8.4 '@codemirror/search': 6.5.8 - '@codemirror/state': 6.5.1 + '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.2 color-convert@1.9.3: @@ -9119,7 +9122,7 @@ snapshots: cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 - import-fresh: 3.3.0 + import-fresh: 3.3.1 parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 @@ -10110,7 +10113,7 @@ snapshots: immutability-helper@3.1.1: {} - import-fresh@3.3.0: + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 @@ -11908,29 +11911,29 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - rollup@4.32.1: + rollup@4.34.1: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.32.1 - '@rollup/rollup-android-arm64': 4.32.1 - '@rollup/rollup-darwin-arm64': 4.32.1 - '@rollup/rollup-darwin-x64': 4.32.1 - '@rollup/rollup-freebsd-arm64': 4.32.1 - '@rollup/rollup-freebsd-x64': 4.32.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.32.1 - '@rollup/rollup-linux-arm-musleabihf': 4.32.1 - '@rollup/rollup-linux-arm64-gnu': 4.32.1 - '@rollup/rollup-linux-arm64-musl': 4.32.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.32.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.32.1 - '@rollup/rollup-linux-riscv64-gnu': 4.32.1 - '@rollup/rollup-linux-s390x-gnu': 4.32.1 - '@rollup/rollup-linux-x64-gnu': 4.32.1 - '@rollup/rollup-linux-x64-musl': 4.32.1 - '@rollup/rollup-win32-arm64-msvc': 4.32.1 - '@rollup/rollup-win32-ia32-msvc': 4.32.1 - '@rollup/rollup-win32-x64-msvc': 4.32.1 + '@rollup/rollup-android-arm-eabi': 4.34.1 + '@rollup/rollup-android-arm64': 4.34.1 + '@rollup/rollup-darwin-arm64': 4.34.1 + '@rollup/rollup-darwin-x64': 4.34.1 + '@rollup/rollup-freebsd-arm64': 4.34.1 + '@rollup/rollup-freebsd-x64': 4.34.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.1 + '@rollup/rollup-linux-arm-musleabihf': 4.34.1 + '@rollup/rollup-linux-arm64-gnu': 4.34.1 + '@rollup/rollup-linux-arm64-musl': 4.34.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.1 + '@rollup/rollup-linux-riscv64-gnu': 4.34.1 + '@rollup/rollup-linux-s390x-gnu': 4.34.1 + '@rollup/rollup-linux-x64-gnu': 4.34.1 + '@rollup/rollup-linux-x64-musl': 4.34.1 + '@rollup/rollup-win32-arm64-msvc': 4.34.1 + '@rollup/rollup-win32-ia32-msvc': 4.34.1 + '@rollup/rollup-win32-x64-msvc': 4.34.1 fsevents: 2.3.3 rtl-css-js@1.16.1: @@ -12458,8 +12461,6 @@ snapshots: tiny-invariant@1.3.3: {} - tiny-lru@11.2.11: {} - tinycolor2@1.6.0: {} tinyglobby@0.2.10: @@ -12747,7 +12748,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.5.1 - rollup: 4.32.1 + rollup: 4.34.1 optionalDependencies: '@types/node': 22.13.0 fsevents: 2.3.3 diff --git a/src/classes/chunked-request.ts b/src/classes/chunked-request.ts deleted file mode 100644 index 3e9acfbe7..000000000 --- a/src/classes/chunked-request.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Debugger } from "debug"; -import { Filter, NostrEvent, matchFilters } from "nostr-tools"; -import { AbstractRelay } from "nostr-tools/abstract-relay"; -import { SimpleRelay } from "nostr-idb"; -import _throttle from "lodash.throttle"; -import { nanoid } from "nanoid"; -import { Subject, Subscription } from "rxjs"; - -import { logger } from "../helpers/debug"; -import EventStore from "./event-store"; -import { mergeFilter } from "../helpers/nostr/filter"; -import relayPoolService from "../services/relay-pool"; -import Process from "./process"; -import processManager from "../services/process-manager"; -import LayersThree01 from "../components/icons/layers-three-01"; -import { eventStore } from "../services/event-store"; - -const DEFAULT_CHUNK_SIZE = 100; - -export type EventFilter = (event: NostrEvent) => boolean; - -/** @deprecated this should be replaced with a rx-nostr based timeline loader */ -export default class ChunkedRequest { - id: string; - process: Process; - relay: AbstractRelay; - filters: Filter[]; - chunkSize = DEFAULT_CHUNK_SIZE; - private log: Debugger; - private subs: Subscription[] = []; - - loading = false; - events: EventStore; - /** set to true when the next chunk produces 0 events */ - complete = false; - - private lastChunkIdx = 0; - onChunkFinish = new Subject(); - - constructor(relay: SimpleRelay | AbstractRelay, filters: Filter[], log?: Debugger) { - this.id = nanoid(8); - this.process = new Process("ChunkedRequest", this, [relay]); - this.process.icon = LayersThree01; - this.relay = relay as AbstractRelay; - this.filters = filters; - - this.log = log || logger.extend(relay.url); - this.events = new EventStore(relay.url); - - processManager.registerProcess(this.process); - } - - async loadNextChunk() { - if (this.loading) return; - - // check if its possible to subscribe to this relay - if (!relayPoolService.canSubscribe(this.relay)) { - this.log("Cant subscribe to relay, aborting"); - return; - } - - this.loading = true; - - if (!this.relay.connected) { - this.log("requesting relay connection"); - relayPoolService.requestConnect(this.relay); - this.loading = false; - return; - } - - let filters: Filter[] = mergeFilter(this.filters, { limit: this.chunkSize }); - const oldestEvent = this.getLastEvent(); - if (oldestEvent) { - filters = mergeFilter(filters, { until: oldestEvent.created_at - 1 }); - } - - let gotEvents = 0; - - this.process.active = true; - await new Promise((res) => { - const sub = this.relay.subscribe(filters, { - id: this.id + "-" + this.lastChunkIdx++, - onevent: (event) => { - this.handleEvent(event); - gotEvents++; - }, - oneose: () => { - this.loading = false; - if (gotEvents === 0) { - this.complete = true; - this.log("Complete"); - } else { - this.log(`Got ${gotEvents} events`); - } - - this.onChunkFinish.next(gotEvents); - sub.close(); - this.process.active = false; - res(gotEvents); - }, - onclose: (reason) => { - relayPoolService.handleRelayNotice(this.relay, reason); - }, - }); - }); - } - - private handleEvent(event: NostrEvent) { - if (!matchFilters(this.filters, event)) return; - - event = eventStore.add(event, this.relay.url); - - return this.events.addEvent(event); - } - - getFirstEvent(nth = 0, eventFilter?: EventFilter) { - return this.events.getFirstEvent(nth, eventFilter); - } - getLastEvent(nth = 0, eventFilter?: EventFilter) { - return this.events.getLastEvent(nth, eventFilter); - } - - destroy() { - for (const sub of this.subs) sub.unsubscribe(); - this.subs = []; - processManager.unregisterProcess(this.process); - } -} diff --git a/src/classes/event-store.ts b/src/classes/event-store.ts deleted file mode 100644 index 4cecc3704..000000000 --- a/src/classes/event-store.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { NostrEvent } from "nostr-tools"; -import { Subject, Subscription } from "rxjs"; -import { nanoid } from "nanoid"; - -import { getEventUID, sortByDate } from "../helpers/nostr/event"; -import SuperMap from "./super-map"; - -export type EventFilter = (event: NostrEvent) => boolean; - -/** - * a class used to store and sort events - * @deprecated there shouldn't be a need for one-off event stores now that EventStore from applesauce is used - */ -export default class EventStore { - id = nanoid(8); - name?: string; - events = new Map(); - - customSort?: typeof sortByDate; - - constructor(name?: string, customSort?: typeof sortByDate) { - this.name = name; - this.customSort = customSort; - } - - getSortedEvents() { - return Array.from(this.events.values()).sort(this.customSort || sortByDate); - } - - onEvent = new Subject(); - onDelete = new Subject(); - onClear = new Subject(); - - private handleEvent(event: NostrEvent) { - const uid = getEventUID(event); - const existing = this.events.get(uid); - if (!existing || event.created_at > existing.created_at) { - this.events.set(uid, event); - this.onEvent.next(event); - } - } - - addEvent(event: NostrEvent) { - this.handleEvent(event); - } - getEvent(id: string) { - return this.events.get(id); - } - deleteEvent(id: string) { - if (this.events.has(id)) { - this.events.delete(id); - this.onDelete.next(id); - } - } - - clear() { - this.events.clear(); - this.onClear.next(undefined); - } - - private storeSubs = new SuperMap(() => []); - connect(other: EventStore, fullSync = true) { - const subs = this.storeSubs.get(other); - subs.push( - other.onEvent.subscribe((e) => { - if (fullSync || this.events.has(getEventUID(e))) this.addEvent(e); - }), - ); - subs.push(other.onDelete.subscribe(this.deleteEvent.bind(this))); - } - disconnect(other: EventStore) { - const subs = this.storeSubs.get(other); - for (const sub of subs) sub.unsubscribe(); - this.storeSubs.delete(other); - } - - cleanup() { - this.clear(); - for (const [_, subs] of this.storeSubs) { - for (const sub of subs) sub.unsubscribe(); - } - this.storeSubs.clear(); - } - - getFirstEvent(nth = 0, filter?: EventFilter) { - const events = this.getSortedEvents(); - - let i = 0; - while (true) { - const event = events.shift(); - if (!event) return; - if (filter && !filter(event)) continue; - if (i === nth) return event; - i++; - } - } - getLastEvent(nth = 0, filter?: EventFilter) { - const events = this.getSortedEvents(); - - let i = 0; - while (true) { - const event = events.pop(); - if (!event) return; - try { - if (filter && !filter(event)) continue; - } catch (error) {} - if (i === nth) return event; - i++; - } - } -} diff --git a/src/classes/timeline-loader.ts b/src/classes/timeline-loader.ts deleted file mode 100644 index 0802c2d16..000000000 --- a/src/classes/timeline-loader.ts +++ /dev/null @@ -1,284 +0,0 @@ -import dayjs from "dayjs"; -import { Debugger } from "debug"; -import { Filter, NostrEvent } from "nostr-tools"; -import { AbstractRelay } from "nostr-tools/abstract-relay"; -import _throttle from "lodash.throttle"; -import { BehaviorSubject, Observable, Subscription, map } from "rxjs"; -import { isFilterEqual } from "applesauce-core/helpers"; -import { shareLatestValue } from "applesauce-core/observable"; -import { MultiSubscription } from "applesauce-net/subscription"; - -import { logger } from "../helpers/debug"; -import { mergeFilter } from "../helpers/nostr/filter"; -import SuperMap from "./super-map"; -import ChunkedRequest from "./chunked-request"; -import relayPoolService from "../services/relay-pool"; -import Process from "./process"; -import AlignHorizontalCentre02 from "../components/icons/align-horizontal-centre-02"; -import processManager from "../services/process-manager"; -import { eventStore, queryStore } from "../services/event-store"; -import { getCacheRelay } from "../services/cache-relay"; - -const BLOCK_SIZE = 100; - -export type EventFilter = (event: NostrEvent) => boolean; - -export default class TimelineLoader { - cursor = dayjs().unix(); - filters: Filter[] = []; - relays: AbstractRelay[] = []; - - loading = new BehaviorSubject(false); - complete = new BehaviorSubject(false); - - loadNextBlockBuffer = 2; - eventFilter?: EventFilter; - useCache = true; - - name: string; - /** @deprecated */ - timeline?: Observable; - process: Process; - private log: Debugger; - private subscription: MultiSubscription; - - private cacheLoader: ChunkedRequest | null = null; - private loaders = new Map(); - - constructor(name: string) { - this.name = name; - this.process = new Process("TimelineLoader", this); - this.process.name = name; - this.process.icon = AlignHorizontalCentre02; - - this.log = logger.extend("TimelineLoader:" + name); - - this.subscription = new MultiSubscription(relayPoolService); - this.subscription.onEvent.subscribe(this.handleEvent.bind(this)); - - processManager.registerProcess(this.process); - } - - private updateTimeline() { - const timeline = queryStore.timeline(this.filters); - - if (this.eventFilter) { - // add filter - this.timeline = timeline.pipe( - map((events) => - events.filter((e) => { - try { - return this.eventFilter!(e); - } catch (error) {} - return false; - }), - ), - shareLatestValue(), - ); - } else { - this.timeline = timeline.pipe(shareLatestValue()); - } - } - - private seenInCache = new Set(); - private handleEvent(event: NostrEvent, fromCache = false) { - event = eventStore.add(event); - - if (fromCache) this.seenInCache.add(event.id); - } - private handleChunkFinished() { - this.updateLoading(); - this.updateComplete(); - } - - private chunkLoaderSubs = new SuperMap(() => []); - private connectToChunkLoader(loader: ChunkedRequest) { - this.process.addChild(loader.process); - - const subs = this.chunkLoaderSubs.get(loader); - subs.push(loader.onChunkFinish.subscribe(this.handleChunkFinished.bind(this))); - } - private disconnectFromChunkLoader(loader: ChunkedRequest) { - loader.destroy(); - - const subs = this.chunkLoaderSubs.get(loader); - for (const sub of subs) sub.unsubscribe(); - this.chunkLoaderSubs.delete(loader); - } - - setFilters(filters: Filter[]) { - if (isFilterEqual(this.filters, filters)) return; - - this.log("Set filters", filters); - - // recreate all chunk loaders - for (const relay of this.relays) { - const loader = this.loaders.get(relay.url); - if (loader) { - this.disconnectFromChunkLoader(loader); - this.loaders.delete(relay.url); - } - - const chunkLoader = new ChunkedRequest( - relayPoolService.requestRelay(relay.url), - filters, - this.log.extend(relay.url), - ); - this.loaders.set(relay.url, chunkLoader); - this.connectToChunkLoader(chunkLoader); - } - - // set filters - this.filters = filters; - - // recreate cache chunk loader - if (this.cacheLoader) this.disconnectFromChunkLoader(this.cacheLoader); - const cacheRelay = getCacheRelay(); - if (cacheRelay && this.useCache) { - this.cacheLoader = new ChunkedRequest(cacheRelay, this.filters, this.log.extend("cache-relay")); - this.connectToChunkLoader(this.cacheLoader); - } - - // update the live subscription query map and add limit - this.subscription.setFilters(mergeFilter(filters, { limit: BLOCK_SIZE / 2 })); - - // update timeline - this.updateTimeline(); - } - - setRelays(relays: Iterable) { - const newRelays = relayPoolService.getRelays(relays); - - // remove chunk loaders - for (const relay of newRelays) { - const loader = this.loaders.get(relay.url); - if (!loader) continue; - if (!this.relays.includes(relay)) { - this.disconnectFromChunkLoader(loader); - this.loaders.delete(relay.url); - } - } - - // create chunk loaders only if filters are set - if (this.filters.length > 0) { - for (const relay of newRelays) { - if (!this.loaders.has(relay.url)) { - const loader = new ChunkedRequest(relay, this.filters, this.log.extend(relay.url)); - this.loaders.set(relay.url, loader); - this.connectToChunkLoader(loader); - } - } - } - - this.relays = relayPoolService.getRelays(relays); - this.process.relays = new Set(this.relays); - - // update live subscription - this.subscription.setRelays(this.relays); - } - - setEventFilter(filter?: EventFilter) { - this.eventFilter = filter; - this.updateTimeline(); - } - setCursor(cursor: number) { - this.cursor = cursor; - this.triggerChunkLoad(); - } - - private getAllLoaders() { - return this.cacheLoader ? [...this.loaders.values(), this.cacheLoader] : Array.from(this.loaders.values()); - } - - triggerChunkLoad() { - let triggeredLoad = false; - const loaders = this.getAllLoaders(); - - for (const loader of loaders) { - // skip loader if its already loading or complete - if (loader.complete || loader.loading) continue; - - const event = loader.getLastEvent(this.loadNextBlockBuffer, this.eventFilter); - if (!event || event.created_at >= this.cursor) { - loader.loadNextChunk(); - triggeredLoad = true; - } - } - - if (triggeredLoad) this.updateLoading(); - } - loadAllNextChunks() { - let triggeredLoad = false; - const loaders = this.getAllLoaders(); - - for (const loader of loaders) { - // skip loader if its already loading or complete - if (loader.complete || loader.loading) continue; - - loader.loadNextChunk(); - triggeredLoad = true; - } - - if (triggeredLoad) this.updateLoading(); - } - - private updateLoading() { - const loaders = this.getAllLoaders(); - - for (const loader of loaders) { - if (loader.loading) { - if (!this.loading.value) { - this.loading.next(true); - return; - } - } - } - if (this.loading.value) this.loading.next(false); - } - private updateComplete() { - const loaders = this.getAllLoaders(); - - for (const loader of loaders) { - if (!loader.complete) { - this.complete.next(false); - return; - } - } - return this.complete.next(true); - } - open() { - this.process.active = true; - this.subscription.open(); - } - close() { - this.process.active = false; - this.subscription.close(); - } - - forgetEvents() { - this.timeline = undefined; - } - reset() { - this.cursor = dayjs().unix(); - const loaders = this.getAllLoaders(); - for (const loader of loaders) this.disconnectFromChunkLoader(loader); - this.loaders.clear(); - this.cacheLoader = null; - this.forgetEvents(); - } - - /** close the subscription and remove any event listeners for this timeline */ - destroy() { - this.close(); - - const loaders = this.getAllLoaders(); - for (const loader of loaders) this.disconnectFromChunkLoader(loader); - this.loaders.clear(); - this.cacheLoader = null; - - this.subscription.close(); - - this.process.remove(); - processManager.unregisterProcess(this.process); - } -} diff --git a/src/components/gif/gif-picker-modal.tsx b/src/components/gif/gif-picker-modal.tsx index 99e1c79f3..1011f2df0 100644 --- a/src/components/gif/gif-picker-modal.tsx +++ b/src/components/gif/gif-picker-modal.tsx @@ -22,7 +22,7 @@ import useTimelineLoader from "../../hooks/use-timeline-loader"; import { useReadRelays } from "../../hooks/use-client-relays"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import IntersectionObserverProvider from "../../providers/local/intersection-observer"; -import SearchRelayPicker, { useSearchRelay } from "../../views/search/components/search-relay-picker"; +import SearchRelayPicker from "../../views/search/components/search-relay-picker"; import useEventIntersectionRef from "../../hooks/use-event-intersection-ref"; import { ListId, usePeopleListSelect } from "../../providers/local/people-list-provider"; @@ -57,7 +57,7 @@ export default function GifPickerModal({ onClose, isOpen, onSelect, ...props }: const [list, setList] = useState("global"); const { selected, setSelected, filter, listId } = usePeopleListSelect(list, setList); - const searchRelay = useSearchRelay(searchRelayUrl); + // const searchRelay = useSearchRelay(searchRelayUrl); const [debounceSearch, setDebounceSearch] = useState(); useEffect(() => { @@ -75,8 +75,8 @@ export default function GifPickerModal({ onClose, isOpen, onSelect, ...props }: const readRelays = useReadRelays(); const { loader, timeline } = useTimelineLoader( - [listId, "gifs", searchRelay?.url ?? "all", debounceSearch ?? "all"].join("-"), - searchRelay !== undefined ? [searchRelay] : readRelays, + [listId, "gifs", searchRelayUrl ?? "all", debounceSearch ?? "all"].join("-"), + searchRelayUrl !== undefined ? [searchRelayUrl] : readRelays, debounceSearch !== undefined ? { ...baseFilter, search: debounceSearch } : baseFilter, ); diff --git a/src/components/timeline-page/index.tsx b/src/components/timeline-page/index.tsx index 14dc13df7..b4dc965c3 100644 --- a/src/components/timeline-page/index.tsx +++ b/src/components/timeline-page/index.tsx @@ -2,18 +2,17 @@ import { useCallback } from "react"; import { FlexProps } from "@chakra-ui/react"; import { useSearchParams } from "react-router-dom"; import { Expressions } from "applesauce-content/helpers"; +import { TimelineLoader } from "applesauce-loaders"; import IntersectionObserverProvider from "../../providers/local/intersection-observer"; import GenericNoteTimeline from "./generic-note-timeline"; import MediaTimeline from "./media-timeline"; -import TimelineLoader from "../../classes/timeline-loader"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import TimelineActionAndStatus from "../timeline/timeline-action-and-status"; import { NostrEvent } from "../../types/nostr-event"; import TimelineHealth from "./timeline-health"; import useRouteSearchValue from "../../hooks/use-route-search-value"; import VerticalPageLayout from "../vertical-page-layout"; -import useAppSettings from "../../hooks/use-user-app-settings"; import useMaxPageWidth from "../../hooks/use-max-page-width"; export function useTimelinePageEventFilter() { @@ -36,13 +35,12 @@ export default function TimelinePage({ timeline, header, ...props -}: { loader: TimelineLoader; timeline: NostrEvent[]; header?: React.ReactNode } & Omit< +}: { loader?: TimelineLoader; timeline: NostrEvent[]; header?: React.ReactNode } & Omit< FlexProps, "children" | "direction" | "gap" >) { const callback = useTimelineCurserIntersectionCallback(loader); - const { maxPageWidth } = useAppSettings(); const viewParam = useRouteSearchValue("view", "timeline"); const mode = (viewParam.value as TimelineViewType) ?? "timeline"; @@ -67,7 +65,7 @@ export default function TimelinePage({ {header} {renderTimeline()} - + ); diff --git a/src/components/timeline-page/timeline-health/index.tsx b/src/components/timeline-page/timeline-health/index.tsx index 63a5ff378..48cf31ccc 100644 --- a/src/components/timeline-page/timeline-health/index.tsx +++ b/src/components/timeline-page/timeline-health/index.tsx @@ -15,8 +15,8 @@ import { useColorMode, } from "@chakra-ui/react"; import { getSeenRelays } from "applesauce-core/helpers"; +import { TimelineLoader } from "applesauce-loaders"; -import TimelineLoader from "../../../classes/timeline-loader"; import { NostrEvent } from "../../../types/nostr-event"; import RelayFavicon from "../../relay-favicon"; import { NoteLink } from "../../note/note-link"; @@ -70,8 +70,8 @@ function EventRow({ ); } -export default function TimelineHealth({ timeline, loader }: { loader: TimelineLoader; timeline: NostrEvent[] }) { - const relays = loader.relays.map((r) => r.url); +export default function TimelineHealth({ timeline, loader }: { loader?: TimelineLoader; timeline: NostrEvent[] }) { + const relays = loader && loader.requests ? Object.keys(loader.requests) : []; return ( <> diff --git a/src/components/timeline/timeline-action-and-status.tsx b/src/components/timeline/timeline-action-and-status.tsx index e8eecd569..db2f0d5f3 100644 --- a/src/components/timeline/timeline-action-and-status.tsx +++ b/src/components/timeline/timeline-action-and-status.tsx @@ -1,11 +1,10 @@ import { Alert, AlertIcon, Button, Spinner } from "@chakra-ui/react"; +import { TimelineLoader } from "applesauce-loaders"; import { useObservable } from "applesauce-react/hooks"; -import TimelineLoader from "../../classes/timeline-loader"; - -export default function TimelineActionAndStatus({ timeline }: { timeline: TimelineLoader }) { - const loading = useObservable(timeline.loading); - const complete = useObservable(timeline.complete); +export default function TimelineActionAndStatus({ loader }: { loader?: TimelineLoader }) { + const loading = useObservable(loader?.loading$); + const complete = false; if (complete) { return ( @@ -20,15 +19,10 @@ export default function TimelineActionAndStatus({ timeline }: { timeline: Timeli return ; } + if (!loader) return null; + return ( - ); diff --git a/src/helpers/nostr/mailbox.ts b/src/helpers/nostr/mailbox.ts index aed3371f1..243ff150a 100644 --- a/src/helpers/nostr/mailbox.ts +++ b/src/helpers/nostr/mailbox.ts @@ -1,8 +1,8 @@ import { kinds } from "nostr-tools"; -import { RelayMode } from "../../classes/relay"; import { DraftNostrEvent, NostrEvent, RTag, Tag, isRTag } from "../../types/nostr-event"; import { safeRelayUrl } from "../relay"; import { cloneEvent } from "./event"; +import { RelayMode } from "../../services/app-relays"; /** fixes or removes any bad r tags */ export function cleanRTags(tags: Tag[]) { diff --git a/src/hooks/timeline/use-number-cache.ts b/src/hooks/timeline/use-number-cache.ts index ac0a3aa25..699b30a53 100644 --- a/src/hooks/timeline/use-number-cache.ts +++ b/src/hooks/timeline/use-number-cache.ts @@ -1,5 +1,5 @@ import { useCallback } from "react"; -import { LRU } from "tiny-lru"; +import { LRU } from "applesauce-core/helpers"; const cache = new LRU>(10); diff --git a/src/hooks/use-forward-subscription.ts b/src/hooks/use-forward-subscription.ts new file mode 100644 index 000000000..aa40408f1 --- /dev/null +++ b/src/hooks/use-forward-subscription.ts @@ -0,0 +1,46 @@ +import { nanoid } from "nanoid"; +import { Filter } from "nostr-tools"; +import { useEffect, useMemo } from "react"; +import { createRxForwardReq } from "rx-nostr"; +import hash from "hash-sum"; + +import rxNostr from "../services/rx-nostr"; +import { useEventStore } from "applesauce-react/hooks/use-event-store"; + +export default function useForwardSubscription(relays: string[], filters?: Filter | Filter[]) { + const eventStore = useEventStore(); + const id = useMemo(() => nanoid(10), []); + const rxReq = useMemo(() => createRxForwardReq(id), [id]); + + // load from cache + // useEffect(() => { + // const sub = cacheRequest(Array.isArray(filters) ? filters : [filters]).subscribe({ + // next: (e) => eventStore.add(e), + // complete: () => sub.unsubscribe(), + // }); + // }, [hash(filters)]); + + // attach to rxNostr + const observable = useMemo(() => rxNostr.use(rxReq, { on: { relays } }), [rxReq, relays.join(",")]); + + // subscribe + // NOTE: have to subscribe before emitting filter + useEffect(() => { + const sub = observable.subscribe((packet) => { + eventStore.add(packet.event, packet.from); + }); + + return () => sub.unsubscribe(); + }, [observable, eventStore]); + + // update filters + useEffect(() => { + if (filters) { + if (Array.isArray(filters)) { + if (filters.length > 0) rxReq.emit(filters); + } else rxReq.emit([filters]); + } + }, [rxReq, hash(filters)]); + + return observable; +} diff --git a/src/hooks/use-relays-changed.ts b/src/hooks/use-relays-changed.ts deleted file mode 100644 index d7d8d235d..000000000 --- a/src/hooks/use-relays-changed.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useEffect, useRef } from "react"; -import { usePrevious } from "react-use"; - -export default function useRelaysChanged(relays: string[], cb: (relays: string[]) => void) { - const callback = useRef(cb); - callback.current = cb; - - const prev = usePrevious(relays); - useEffect(() => { - if (!!prev && prev?.join(",") !== relays.join(",")) { - // always call the latest callback - callback.current(relays); - } - }, [relays.join(",")]); -} diff --git a/src/hooks/use-timeline-cursor-intersection-callback.ts b/src/hooks/use-timeline-cursor-intersection-callback.ts index eb2258dd7..2db6274a5 100644 --- a/src/hooks/use-timeline-cursor-intersection-callback.ts +++ b/src/hooks/use-timeline-cursor-intersection-callback.ts @@ -1,35 +1,43 @@ +import { useEffect, useRef } from "react"; import { useInterval } from "react-use"; import { NostrEvent } from "nostr-tools"; +import { TimelineLoader } from "applesauce-loaders"; -import TimelineLoader from "../classes/timeline-loader"; import { useCachedIntersectionMapCallback } from "../providers/local/intersection-observer"; import { eventStore } from "../services/event-store"; -export function useTimelineCurserIntersectionCallback(timeline: TimelineLoader) { +export function useTimelineCurserIntersectionCallback(loader?: TimelineLoader) { + const oldest = useRef(undefined); + + // Request next batch when components mounts + useEffect(() => { + if (loader) + setTimeout(() => { + loader?.next(-Infinity); + }, 100); + }, [loader]); + // if the cursor is set too far ahead and the last block did not overlap with the cursor // we need to keep loading blocks until the timeline is complete or the blocks pass the cursor useInterval(() => { - timeline.triggerChunkLoad(); + if (oldest.current) loader?.next(oldest.current.created_at - 1); + else loader?.next(-Infinity); }, 1000); return useCachedIntersectionMapCallback( (map) => { // find oldest event that is visible - let oldestEvent: NostrEvent | undefined = undefined; for (const [id, entry] of map) { if (!entry.isIntersecting) continue; const event = eventStore.getEvent(id); if (!event) continue; - if (!oldestEvent || event.created_at < oldestEvent.created_at) { - oldestEvent = event; + if (!oldest.current || event.created_at < oldest.current.created_at - 1) { + oldest.current = event; } } - if (oldestEvent) { - timeline.setCursor(oldestEvent.created_at); - timeline.triggerChunkLoad(); - } + if (oldest.current) loader?.next(oldest.current.created_at); }, - [timeline], + [loader], ); } diff --git a/src/hooks/use-timeline-loader.ts b/src/hooks/use-timeline-loader.ts index e88eefa1a..dd4297f41 100644 --- a/src/hooks/use-timeline-loader.ts +++ b/src/hooks/use-timeline-loader.ts @@ -1,67 +1,43 @@ import { useEffect, useMemo } from "react"; -import { usePrevious, useUnmount } from "react-use"; -import { AbstractRelay } from "nostr-tools/abstract-relay"; import { useStoreQuery } from "applesauce-react/hooks"; +import { useEventStore } from "applesauce-react/hooks/use-event-store"; import { Queries } from "applesauce-core"; -import { Filter } from "nostr-tools"; +import { Filter, NostrEvent } from "nostr-tools"; +import sum from "hash-sum"; import timelineCacheService from "../services/timeline-cache"; -import { EventFilter } from "../classes/timeline-loader"; +import useForwardSubscription from "./use-forward-subscription"; type Options = { - eventFilter?: EventFilter; - useCache?: boolean; + eventFilter?: (event: NostrEvent) => boolean; }; export default function useTimelineLoader( key: string, - relays: Iterable, + relays: string[], filters: Filter | Filter[] | undefined, opts?: Options, ) { - const loader = useMemo(() => timelineCacheService.createTimeline(key), [key]); + // start a forward subscription while component is mounted + useForwardSubscription(relays, filters); - // set use cache - if (opts?.useCache !== undefined) loader.useCache = opts?.useCache; + const eventStore = useEventStore(); + const loader = useMemo(() => { + if (filters) return timelineCacheService.createTimeline(key, relays, Array.isArray(filters) ? filters : [filters]); + }, [key, sum(filters), relays.join(",")]); - // update relays + // start and stop loader useEffect(() => { - loader.setRelays(relays); - loader.triggerChunkLoad(); - }, [ - Array.from(relays) - .map((t) => (typeof t === "string" ? t : t.url)) - .join("|"), - ]); + const sub = loader?.subscribe((packet) => { + eventStore.add(packet.event, packet.from); + }); - // update filters - useEffect(() => { - if (filters) { - loader.setFilters(Array.isArray(filters) ? filters : [filters]); - loader.open(); - loader.triggerChunkLoad(); - } else loader.close(); - }, [loader, JSON.stringify(filters)]); - - // update event filter - useEffect(() => { - loader.setEventFilter(opts?.eventFilter); - }, [loader, opts?.eventFilter]); - - // close the old timeline when the key changes - const oldTimeline = usePrevious(loader); - useEffect(() => { - if (oldTimeline && oldTimeline !== loader) { - oldTimeline.close(); - } - }, [loader, oldTimeline]); - - // stop the loader when unmount - useUnmount(() => { - loader.close(); - }); + return () => sub?.unsubscribe(); + }, [eventStore, loader]); let timeline = useStoreQuery(Queries.TimelineQuery, filters && [filters]) ?? []; + + // set event filter if (opts?.eventFilter) timeline = timeline.filter((e) => { try { diff --git a/src/providers/global/dms-provider.tsx b/src/providers/global/dms-provider.tsx deleted file mode 100644 index 211de4287..000000000 --- a/src/providers/global/dms-provider.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react"; -import { kinds } from "nostr-tools"; -import { useActiveAccount } from "applesauce-react/hooks"; - -import TimelineLoader from "../../classes/timeline-loader"; -import { NostrEvent } from "../../types/nostr-event"; -import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; -import useTimelineLoader from "../../hooks/use-timeline-loader"; -import { useUserInbox } from "../../hooks/use-user-mailboxes"; -import { truncateId } from "../../helpers/string"; - -type DMTimelineContextType = { - timeline?: TimelineLoader; -}; -const DMTimelineContext = createContext({}); - -export function useDMTimeline() { - const context = useContext(DMTimelineContext); - - if (!context?.timeline) throw new Error("No dm timeline"); - - return context.timeline; -} - -export default function DMTimelineProvider({ children }: PropsWithChildren) { - const account = useActiveAccount(); - const inbox = useUserInbox(account?.pubkey); - - const userMuteFilter = useClientSideMuteFilter(); - const eventFilter = useCallback( - (event: NostrEvent) => { - if (userMuteFilter(event)) return false; - return true; - }, - [userMuteFilter], - ); - - const { loader } = useTimelineLoader( - `${truncateId(account?.pubkey ?? "anon")}-dms`, - inbox ?? [], - account?.pubkey - ? [ - { authors: [account.pubkey], kinds: [kinds.EncryptedDirectMessage] }, - { "#p": [account.pubkey], kinds: [kinds.EncryptedDirectMessage] }, - ] - : undefined, - { eventFilter }, - ); - - const context = useMemo(() => ({ timeline: loader }), [loader]); - - return {children}; -} diff --git a/src/providers/global/index.tsx b/src/providers/global/index.tsx index d07968aed..8844a0a87 100644 --- a/src/providers/global/index.tsx +++ b/src/providers/global/index.tsx @@ -8,7 +8,6 @@ import useAppSettings from "../../hooks/use-user-app-settings"; import NotificationsProvider from "./notifications-provider"; import { UserEmojiProvider } from "./emoji-provider"; import BreakpointProvider from "./breakpoint-provider"; -import DMTimelineProvider from "./dms-provider"; import PublishProvider from "./publish-provider"; import WebOfTrustProvider from "./web-of-trust-provider"; import { queryStore } from "../../services/event-store"; @@ -35,13 +34,11 @@ export const GlobalProviders = ({ children }: { children: React.ReactNode }) => - - - - {children} - - - + + + {children} + + diff --git a/src/providers/global/notifications-provider.tsx b/src/providers/global/notifications-provider.tsx index 3dcf5ebef..73193aca3 100644 --- a/src/providers/global/notifications-provider.tsx +++ b/src/providers/global/notifications-provider.tsx @@ -1,9 +1,9 @@ import { PropsWithChildren, createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { kinds } from "nostr-tools"; import { useActiveAccount } from "applesauce-react/hooks"; +import { TimelineLoader } from "applesauce-loaders"; import { useReadRelays } from "../../hooks/use-client-relays"; -import TimelineLoader from "../../classes/timeline-loader"; import { NostrEvent } from "../../types/nostr-event"; import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -13,7 +13,7 @@ import AccountNotifications from "../../classes/notifications"; import { truncateId } from "../../helpers/string"; type NotificationTimelineContextType = { - timeline: TimelineLoader; + timeline?: TimelineLoader; notifications?: AccountNotifications; }; const NotificationTimelineContext = createContext(null); diff --git a/src/providers/local/thread-provider.tsx b/src/providers/local/thread-provider.tsx index 495d1a310..9bed8c145 100644 --- a/src/providers/local/thread-provider.tsx +++ b/src/providers/local/thread-provider.tsx @@ -1,8 +1,6 @@ import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react"; import { NostrEvent } from "nostr-tools"; -import { useObservable } from "applesauce-react/hooks"; -import TimelineLoader from "../../classes/timeline-loader"; import { eventStore } from "../../services/event-store"; export type Thread = { @@ -25,9 +23,7 @@ export function useThreadsContext() { return useContext(ThreadsContext); } -export default function ThreadsProvider({ timeline, children }: { timeline: TimelineLoader } & PropsWithChildren) { - const messages = useObservable(timeline.timeline) ?? []; - +export default function ThreadsProvider({ messages, children }: { messages: NostrEvent[] } & PropsWithChildren) { const threads = useMemo(() => { const grouped: Record = {}; for (const message of messages) { diff --git a/src/services/timeline-cache.ts b/src/services/timeline-cache.ts index d0c9496bc..704865566 100644 --- a/src/services/timeline-cache.ts +++ b/src/services/timeline-cache.ts @@ -1,36 +1,25 @@ -import TimelineLoader from "../classes/timeline-loader"; +import { LRU } from "applesauce-core/helpers"; +import { TimelessFilter, TimelineLoader } from "applesauce-loaders"; + +import rxNostr from "./rx-nostr"; import { logger } from "../helpers/debug"; const MAX_CACHE = 30; +const BATCH_LIMIT = 100; class TimelineCacheService { - private timelines = new Map(); - private cacheQueue: string[] = []; - private log = logger.extend("TimelineCacheService"); + protected timelines = new LRU(MAX_CACHE); + protected log = logger.extend("TimelineCacheService"); - createTimeline(key: string) { + createTimeline(key: string, relays: string[], filters: TimelessFilter[]) { let timeline = this.timelines.get(key); - if (!timeline) { + + if (!timeline && relays.length > 0 && filters.length > 0) { this.log(`Creating ${key}`); - timeline = new TimelineLoader(key); + timeline = new TimelineLoader(rxNostr, TimelineLoader.simpleFilterMap(relays, filters), { limit: BATCH_LIMIT }); this.timelines.set(key, timeline); } - // add or move the timelines key to the top of the queue - this.cacheQueue = this.cacheQueue.filter((p) => p !== key).concat(key); - - // remove any timelines at the end of the queue - while (this.cacheQueue.length > MAX_CACHE) { - const deleteKey = this.cacheQueue.shift(); - if (!deleteKey) break; - const deadTimeline = this.timelines.get(deleteKey); - if (deadTimeline) { - this.log(`Destroying ${deadTimeline.name}`); - this.timelines.delete(deleteKey); - deadTimeline.destroy(); - } - } - return timeline; } } diff --git a/src/views/articles/index.tsx b/src/views/articles/index.tsx index dfe075635..fb18e22d4 100644 --- a/src/views/articles/index.tsx +++ b/src/views/articles/index.tsx @@ -61,7 +61,7 @@ function ArticlesHomePage() { ))} - + ); diff --git a/src/views/badges/badge-details.tsx b/src/views/badges/badge-details.tsx index 00c4a3b6e..6bc6b5298 100644 --- a/src/views/badges/badge-details.tsx +++ b/src/views/badges/badge-details.tsx @@ -1,6 +1,5 @@ import { useNavigate } from "react-router-dom"; import { kinds } from "nostr-tools"; -import { useObservable } from "applesauce-react/hooks"; import { Button, Flex, @@ -32,31 +31,22 @@ import UserLink from "../../components/user/user-link"; import Timestamp from "../../components/timestamp"; import VerticalPageLayout from "../../components/vertical-page-layout"; import BadgeAwardCard from "./components/badge-award-card"; -import TimelineLoader from "../../classes/timeline-loader"; import { ErrorBoundary } from "../../components/error-boundary"; import useParamsAddressPointer from "../../hooks/use-params-address-pointer"; -function BadgeActivityTab({ timeline }: { timeline: TimelineLoader }) { - const awards = useObservable(timeline.timeline); - const callback = useTimelineCurserIntersectionCallback(timeline); - +function BadgeActivityTab({ awards }: { awards: NostrEvent[] }) { return ( - - {awards?.map((award) => ( - - - - ))} - + {awards?.map((award) => ( + + + + ))} ); } -function BadgeUsersTab({ timeline }: { timeline: TimelineLoader }) { - const awards = useObservable(timeline.timeline); - const callback = useTimelineCurserIntersectionCallback(timeline); - +function BadgeUsersTab({ awards }: { awards: NostrEvent[] }) { const pubkeys = new Set(); if (awards) { for (const award of awards) { @@ -68,14 +58,12 @@ function BadgeUsersTab({ timeline }: { timeline: TimelineLoader }) { return ( - - {Array.from(pubkeys).map((pubkey) => ( - - - - - ))} - + {Array.from(pubkeys).map((pubkey) => ( + + + + + ))} ); } @@ -88,68 +76,72 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) { const readRelays = useReadRelays(); const coordinate = getEventCoordinate(badge); - const { loader } = useTimelineLoader(`${coordinate}-awards`, readRelays, { + const { loader, timeline } = useTimelineLoader(`${coordinate}-awards`, readRelays, { "#a": [coordinate], kinds: [kinds.BadgeAward], }); + const callback = useTimelineCurserIntersectionCallback(loader); + if (!badge) return ; return ( - - + + + - - - | - {getBadgeName(badge)} - - - - - - - - {image && ( - - )} - + + + | {getBadgeName(badge)} - - Created by: {" "} - - - - Created: - - {description && ( - <> - - Description - - {description} - - )} - - - - - Activity - Users - - - - - - - - - - + + + + + + + {image && ( + + )} + + {getBadgeName(badge)} + + Created by: {" "} + + + + Created: + + {description && ( + <> + + Description + + {description} + + )} + + + + + + Activity + Users + + + + + + + + + + + ); } diff --git a/src/views/channels/channel.tsx b/src/views/channels/channel.tsx index d04e28d83..ae7156646 100644 --- a/src/views/channels/channel.tsx +++ b/src/views/channels/channel.tsx @@ -1,5 +1,4 @@ import { memo, useCallback, useMemo } from "react"; -import { useNavigate } from "react-router-dom"; import { Button, ButtonGroup, Flex, Spinner, useDisclosure } from "@chakra-ui/react"; import { kinds } from "nostr-tools"; import { useStoreQuery } from "applesauce-react/hooks"; @@ -17,7 +16,6 @@ import useTimelineLoader from "../../hooks/use-timeline-loader"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import IntersectionObserverProvider from "../../providers/local/intersection-observer"; import ThreadsProvider from "../../providers/local/thread-provider"; -import TimelineLoader from "../../classes/timeline-loader"; import { groupMessages } from "../../helpers/nostr/dms"; import ChannelMessageBlock from "./components/channel-message-block"; import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status"; @@ -28,7 +26,7 @@ import { truncateId } from "../../helpers/string"; import ContainedSimpleView from "../../components/layout/presets/contained-simple-view"; import ChannelImage from "./components/channel-image"; -const ChannelChatLog = memo(({ timeline, channel }: { timeline: TimelineLoader; channel: NostrEvent }) => { +const ChannelChatLog = memo(({ channel }: { channel: NostrEvent }) => { const messages = useStoreQuery(ChannelMessagesQuery, [channel]) ?? []; const mutes = useStoreQuery(ChannelMutedQuery, [channel]); const hidden = useStoreQuery(ChannelHiddenQuery, [channel]); @@ -55,7 +53,6 @@ const ChannelChatLog = memo(({ timeline, channel }: { timeline: TimelineLoader; }); function ChannelPage({ channel }: { channel: NostrEvent }) { - const navigate = useNavigate(); const relays = useReadRelays(); const drawer = useDisclosure(); @@ -81,7 +78,7 @@ function ChannelPage({ channel }: { channel: NostrEvent }) { const callback = useTimelineCurserIntersectionCallback(loader); return ( - + } > - - + + {drawer.isOpen && } diff --git a/src/views/channels/components/channel-metadata-drawer.tsx b/src/views/channels/components/channel-metadata-drawer.tsx index f432dff99..98ef15917 100644 --- a/src/views/channels/components/channel-metadata-drawer.tsx +++ b/src/views/channels/components/channel-metadata-drawer.tsx @@ -41,7 +41,7 @@ function UserCard({ pubkey }: { pubkey: string }) { ); } -function ChannelMembers({ channel, relays }: { channel: NostrEvent; relays: Iterable }) { +function ChannelMembers({ channel, relays }: { channel: NostrEvent; relays: string[] }) { const { loader, timeline: userLists } = useTimelineLoader(`${channel.id}-members`, relays, { kinds: [kinds.PublicChatsList], "#e": [channel.id], diff --git a/src/views/files/index.tsx b/src/views/files/index.tsx index d0a7a79d5..e698ce10d 100644 --- a/src/views/files/index.tsx +++ b/src/views/files/index.tsx @@ -113,7 +113,7 @@ function FilesHomePage() { - + ); } diff --git a/src/views/hashtag/index.tsx b/src/views/hashtag/index.tsx index 650089e2e..da5e8c5f1 100644 --- a/src/views/hashtag/index.tsx +++ b/src/views/hashtag/index.tsx @@ -18,7 +18,6 @@ import useTimelineLoader from "../../hooks/use-timeline-loader"; import { isReply, isRepost } from "../../helpers/nostr/event"; import { CheckIcon, EditIcon } from "../../components/icons"; import { NostrEvent } from "../../types/nostr-event"; -import useRelaysChanged from "../../hooks/use-relays-changed"; import TimelinePage, { useTimelinePageEventFilter } from "../../components/timeline-page"; import TimelineViewTypeButtons from "../../components/timeline-page/timeline-view-type"; import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; @@ -74,8 +73,6 @@ function HashTagPage() { { eventFilter }, ); - useRelaysChanged(readRelays, () => loader.reset()); - const header = ( ) { const navigate = useNavigate(); const account = useActiveAccount()!; - const timeline = useDMTimeline(); + const { timeline: messages } = useDirectMessagesTimeline(account.pubkey); - const messages = useObservable(timeline.timeline) ?? []; const conversations = useMemo(() => { const grouped = groupIntoConversations(messages) .map((c) => identifyConversation(c, account.pubkey)) diff --git a/src/views/map/index.tsx b/src/views/map/index.tsx index a4d7008cc..ed5b904df 100644 --- a/src/views/map/index.tsx +++ b/src/views/map/index.tsx @@ -82,8 +82,7 @@ export default function MapView() { setFocused(event.id); }, []); - const events = useObservable(loader.timeline) ?? []; - useEventMarkers(events, map, handleMarkerClick); + useEventMarkers(timeline, map, handleMarkerClick); return ( @@ -98,8 +97,8 @@ export default function MapView() { - - {cells.length > 0 && } + + {cells.length > 0 && } diff --git a/src/views/map/timeline.tsx b/src/views/map/timeline.tsx index cc49fa207..e728c8b24 100644 --- a/src/views/map/timeline.tsx +++ b/src/views/map/timeline.tsx @@ -1,10 +1,8 @@ import React from "react"; import { kinds } from "nostr-tools"; -import { useObservable } from "applesauce-react/hooks"; import { ErrorBoundary } from "../../components/error-boundary"; import StreamNote from "../../components/timeline-page/generic-note-timeline/stream-note"; -import TimelineLoader from "../../classes/timeline-loader"; import { NostrEvent } from "../../types/nostr-event"; import TimelineNote from "../../components/note/timeline-note"; @@ -19,12 +17,10 @@ const RenderEvent = React.memo(({ event, focused }: { event: NostrEvent; focused } }); -const MapTimeline = React.memo(({ timeline, focused }: { timeline: TimelineLoader; focused?: string }) => { - const events = useObservable(timeline.timeline); - +const MapTimeline = React.memo(({ timeline, focused }: { timeline: NostrEvent[]; focused?: string }) => { return ( <> - {events?.map((event) => ( + {timeline?.map((event) => ( diff --git a/src/views/messages/chat.tsx b/src/views/messages/chat.tsx index f4ee325b4..fe62d4a0d 100644 --- a/src/views/messages/chat.tsx +++ b/src/views/messages/chat.tsx @@ -112,7 +112,7 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) { const callback = useTimelineCurserIntersectionCallback(loader); return ( - + } > - + {location.state?.thread && ( )} diff --git a/src/views/messages/index.tsx b/src/views/messages/index.tsx index bc08b9330..1181a1570 100644 --- a/src/views/messages/index.tsx +++ b/src/views/messages/index.tsx @@ -1,8 +1,7 @@ -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; import { Card, CardBody, Flex, LinkBox, LinkOverlay, Text } from "@chakra-ui/react"; import { Link as RouterLink, useLocation } from "react-router-dom"; -import { useObservable } from "applesauce-react/hooks"; -import { nip19 } from "nostr-tools"; +import { kinds, nip19 } from "nostr-tools"; import UserAvatar from "../../components/user/user-avatar"; import RequireActiveAccount from "../../components/router/require-active-account"; @@ -14,7 +13,6 @@ import { KnownConversation, groupIntoConversations, hasResponded, identifyConver import IntersectionObserverProvider from "../../providers/local/intersection-observer"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status"; -import { useDMTimeline } from "../../providers/global/dms-provider"; import UserName from "../../components/user/user-name"; import { NostrEvent } from "../../types/nostr-event"; import { CheckIcon } from "../../components/icons"; @@ -22,6 +20,34 @@ import UserDnsIdentity from "../../components/user/user-dns-identity"; import useEventIntersectionRef from "../../hooks/use-event-intersection-ref"; import { useKind4Decrypt } from "../../hooks/use-kind4-decryption"; import ContainedParentView from "../../components/layout/presets/contained-parent-view"; +import { truncateId } from "../../helpers/string"; +import useTimelineLoader from "../../hooks/use-timeline-loader"; +import useUserMailboxes from "../../hooks/use-user-mailboxes"; +import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; + +export function useDirectMessagesTimeline(pubkey?: string) { + const userMuteFilter = useClientSideMuteFilter(); + const eventFilter = useCallback( + (event: NostrEvent) => { + if (userMuteFilter(event)) return false; + return true; + }, + [userMuteFilter], + ); + const mailboxes = useUserMailboxes(pubkey); + + return useTimelineLoader( + `${truncateId(pubkey ?? "anon")}-dms`, + mailboxes?.inboxes ?? [], + pubkey + ? [ + { authors: [pubkey], kinds: [kinds.EncryptedDirectMessage] }, + { "#p": [pubkey], kinds: [kinds.EncryptedDirectMessage] }, + ] + : undefined, + { eventFilter }, + ); +} function MessagePreview({ message, pubkey }: { message: NostrEvent; pubkey: string }) { const ref = useEventIntersectionRef(message); @@ -64,9 +90,8 @@ function MessagesHomePage() { const { people } = usePeopleListContext(); const account = useActiveAccount()!; - const timeline = useDMTimeline(); + const { timeline: messages, loader } = useDirectMessagesTimeline(account.pubkey); - const messages = useObservable(timeline.timeline) ?? []; const conversations = useMemo(() => { const conversations = groupIntoConversations(messages).map((c) => identifyConversation(c, account.pubkey)); const filtered = conversations.filter((conversation) => @@ -76,7 +101,7 @@ function MessagesHomePage() { return filtered.sort((a, b) => b.messages[0].created_at - a.messages[0].created_at); }, [messages, people, account.pubkey]); - const callback = useTimelineCurserIntersectionCallback(timeline); + const callback = useTimelineCurserIntersectionCallback(loader); return ( @@ -88,7 +113,7 @@ function MessagesHomePage() { ))} - + ); } diff --git a/src/views/notifications/index.tsx b/src/views/notifications/index.tsx index bdeed49c5..199329a8b 100644 --- a/src/views/notifications/index.tsx +++ b/src/views/notifications/index.tsx @@ -201,7 +201,7 @@ function NotificationsPage() { - + ); } diff --git a/src/views/relays/browse-sets.tsx b/src/views/relays/browse-sets.tsx index 98c6f0210..a508221f8 100644 --- a/src/views/relays/browse-sets.tsx +++ b/src/views/relays/browse-sets.tsx @@ -53,7 +53,7 @@ function BrowseRelaySetsPage() { {relaySets?.map((set) => )} - + ); } diff --git a/src/views/relays/relay-details/relay-notes.tsx b/src/views/relays/relay-details/relay-notes.tsx index 50238a685..dc1712378 100644 --- a/src/views/relays/relay-details/relay-notes.tsx +++ b/src/views/relays/relay-details/relay-notes.tsx @@ -1,6 +1,7 @@ import { useCallback } from "react"; import { Flex, Spacer, useDisclosure } from "@chakra-ui/react"; import { kinds } from "nostr-tools"; +import { getSeenRelays } from "applesauce-core/helpers"; import { isReply, isRepost } from "../../../helpers/nostr/event"; import { useAppTitle } from "../../../hooks/use-app-title"; @@ -12,7 +13,6 @@ import PeopleListSelection from "../../../components/people-list-selection/peopl import { usePeopleListContext } from "../../../providers/local/people-list-provider"; import useClientSideMuteFilter from "../../../hooks/use-client-side-mute-filter"; import NoteFilterTypeButtons from "../../../components/note-filter-type-buttons"; -import { getSeenRelays } from "applesauce-core/helpers"; export default function RelayNotes({ relay }: { relay: string }) { useAppTitle(`${relay} - Notes`); @@ -38,10 +38,7 @@ export default function RelayNotes({ relay }: { relay: string }) { `${relay}-notes`, [relay], filter ? { ...filter, kinds: k } : undefined, - { - eventFilter, - useCache: false, - }, + { eventFilter }, ); const header = ( @@ -53,5 +50,7 @@ export default function RelayNotes({ relay }: { relay: string }) { ); + if (!loader) return null; + return ; } diff --git a/src/views/relays/relay-details/relay-users.tsx b/src/views/relays/relay-details/relay-users.tsx index 57d730a56..1d86ad15b 100644 --- a/src/views/relays/relay-details/relay-users.tsx +++ b/src/views/relays/relay-details/relay-users.tsx @@ -55,7 +55,7 @@ export default function RelayUsersTab({ relay }: { relay: string }) { {lists?.map((list) => )} - + ); } diff --git a/src/views/search/components/search-results.tsx b/src/views/search/components/search-results.tsx index 0209b06f9..8f6a0ea42 100644 --- a/src/views/search/components/search-results.tsx +++ b/src/views/search/components/search-results.tsx @@ -2,7 +2,7 @@ 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 { LRU } from "tiny-lru"; +import { LRU } from "applesauce-core/helpers"; import relayPoolService from "../../../services/relay-pool"; import ProfileSearchResults from "./profile-results"; diff --git a/src/views/streams/index.tsx b/src/views/streams/index.tsx index 772cad562..6145f106f 100644 --- a/src/views/streams/index.tsx +++ b/src/views/streams/index.tsx @@ -7,7 +7,6 @@ import useTimelineLoader from "../../hooks/use-timeline-loader"; import IntersectionObserverProvider from "../../providers/local/intersection-observer"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import StreamCard from "./components/stream-card"; -import useRelaysChanged from "../../hooks/use-relays-changed"; import PeopleListSelection from "../../components/people-list-selection/people-list-selection"; import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider"; import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status"; @@ -54,8 +53,6 @@ function StreamsPage() { }); const callback = useTimelineCurserIntersectionCallback(loader); - useRelaysChanged(relays, () => loader.reset()); - const { streams: favorites } = useFavoriteStreams(); const liveStreams = streams.filter((stream) => getStreamStatus(stream) === "live"); @@ -104,7 +101,7 @@ function StreamsPage() { )} - + ); diff --git a/src/views/user/articles.tsx b/src/views/user/articles.tsx index 8cfc52281..192f94a92 100644 --- a/src/views/user/articles.tsx +++ b/src/views/user/articles.tsx @@ -31,7 +31,7 @@ export default function UserArticlesTab() { ))} - + ); diff --git a/src/views/user/files.tsx b/src/views/user/files.tsx index 5ea7a2b38..f6854800b 100644 --- a/src/views/user/files.tsx +++ b/src/views/user/files.tsx @@ -71,7 +71,7 @@ export default function UserFilesTab() { - + ); diff --git a/src/views/user/followers.tsx b/src/views/user/followers.tsx index a119b766e..d600a0bc4 100644 --- a/src/views/user/followers.tsx +++ b/src/views/user/followers.tsx @@ -49,7 +49,7 @@ export default function UserFollowersTab() { ))} - + ); } diff --git a/src/views/user/messages.tsx b/src/views/user/messages.tsx index a00040162..ebb86b15c 100644 --- a/src/views/user/messages.tsx +++ b/src/views/user/messages.tsx @@ -81,7 +81,7 @@ export default function UserMessagesTab() { - + ); diff --git a/src/views/user/reactions.tsx b/src/views/user/reactions.tsx index 8ef4f9532..316edbf8a 100644 --- a/src/views/user/reactions.tsx +++ b/src/views/user/reactions.tsx @@ -58,7 +58,7 @@ export default function UserReactionsTab() { {reactions?.map((event) => )} - + diff --git a/src/views/user/reports.tsx b/src/views/user/reports.tsx index ec0653162..0383bb4a0 100644 --- a/src/views/user/reports.tsx +++ b/src/views/user/reports.tsx @@ -65,7 +65,7 @@ export default function UserReportsTab() { {events?.map((report) => )} - + ); diff --git a/src/views/user/streams.tsx b/src/views/user/streams.tsx index e0ec626c0..ff82582fe 100644 --- a/src/views/user/streams.tsx +++ b/src/views/user/streams.tsx @@ -34,7 +34,7 @@ export default function UserStreamsTab() { ))} - + ); diff --git a/src/views/user/torrents.tsx b/src/views/user/torrents.tsx index 425887171..66673df43 100644 --- a/src/views/user/torrents.tsx +++ b/src/views/user/torrents.tsx @@ -48,7 +48,7 @@ export default function UserTorrentsTab() { - + ); diff --git a/src/views/user/zaps.tsx b/src/views/user/zaps.tsx index b801092d0..15a8cc833 100644 --- a/src/views/user/zaps.tsx +++ b/src/views/user/zaps.tsx @@ -132,7 +132,7 @@ const UserZapsTab = () => { ))} - + ); diff --git a/src/views/videos/index.tsx b/src/views/videos/index.tsx index d27af4087..f44de933e 100644 --- a/src/views/videos/index.tsx +++ b/src/views/videos/index.tsx @@ -39,7 +39,7 @@ function VideosPage() { ))} - + ); } diff --git a/src/views/wiki/index.tsx b/src/views/wiki/index.tsx index c0bb11414..c51d1f63c 100644 --- a/src/views/wiki/index.tsx +++ b/src/views/wiki/index.tsx @@ -78,7 +78,7 @@ export default function WikiHomeView() { ))} - + ); }