diff --git a/package.json b/package.json index ed70e4d2..b310f471 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,6 @@ "react-hotkeys-hook": "^4.4.1", "react-router-dom": "^6.20.1", "react-string-replace": "^1.1.1", - "reactflow": "^11.10.1", "sonner": "^1.2.4", "tauri-controls": "github:reyamir/tauri-controls", "tippy.js": "^6.3.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ffebbf17..aa7e38ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,9 +191,6 @@ dependencies: react-string-replace: specifier: ^1.1.1 version: 1.1.1 - reactflow: - specifier: ^11.10.1 - version: 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) sonner: specifier: ^1.2.4 version: 1.2.4(react-dom@18.2.0)(react@18.2.0) @@ -1752,114 +1749,6 @@ packages: '@babel/runtime': 7.23.5 dev: false - /@reactflow/background@11.3.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-06FPlSUOOMALEEs+2PqPAbpqmL7WDjrkbG2UsDr2d6mbcDDhHiV4tu9FYoz44SQvXo7ma9VRotlsaR4OiRcYsg==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - classcat: 5.0.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/controls@11.2.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-4QHT92/ACVlZkvV+Hq44bAPV8WbMhkJl+/J0EbXcqQ1+an7cWJsF84eeelJw7R5J76RoaSSpKdsWsL2v7HAVlw==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - classcat: 5.0.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/core@11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-GIh3usY1W3eVobx//OO9+Cwm+5evQBBdPGxDaeXwm25UqPMWRI240nXQA5F/5gL5Mwpf0DUC7DR2EmrKNQy+Rw==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@types/d3': 7.4.3 - '@types/d3-drag': 3.0.7 - '@types/d3-selection': 3.0.10 - '@types/d3-zoom': 3.0.8 - classcat: 5.0.4 - d3-drag: 3.0.0 - d3-selection: 3.0.0 - d3-zoom: 3.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/minimap@11.7.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-kJEtyeQkTZYViLGebVWHVUJROMAGcvejvT+iX4DqKnFb5yK8E8LWlXQpRx2FrL9gDy80mJJaciy7IxnnQKE1bg==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - '@types/d3-selection': 3.0.10 - '@types/d3-zoom': 3.0.8 - classcat: 5.0.4 - d3-selection: 3.0.0 - d3-zoom: 3.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/node-resizer@2.2.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-1Xb6q97uP7hRBLpog9sRCNfnsHdDgFRGEiU+lQqGgPEAeYwl4nRjWa/sXwH6ajniKxBhGEvrdzOgEFn6CRMcpQ==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - classcat: 5.0.4 - d3-drag: 3.0.0 - d3-selection: 3.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/node-toolbar@1.3.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-JXDEuZ0wKjZ8z7qK2bIst0eZPzNyVEsiHL0e93EyuqT4fA9icoyE0fLq2ryNOOp7MXgId1h7LusnH6ta45F0yQ==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - classcat: 5.0.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - zustand: 4.4.7(@types/react@18.2.42)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - /@remirror/core-constants@2.0.2: resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} dev: false @@ -2598,189 +2487,6 @@ packages: - supports-color dev: true - /@types/d3-array@3.2.1: - resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} - dev: false - - /@types/d3-axis@3.0.6: - resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} - dependencies: - '@types/d3-selection': 3.0.10 - dev: false - - /@types/d3-brush@3.0.6: - resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} - dependencies: - '@types/d3-selection': 3.0.10 - dev: false - - /@types/d3-chord@3.0.6: - resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} - dev: false - - /@types/d3-color@3.1.3: - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - dev: false - - /@types/d3-contour@3.0.6: - resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} - dependencies: - '@types/d3-array': 3.2.1 - '@types/geojson': 7946.0.13 - dev: false - - /@types/d3-delaunay@6.0.4: - resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} - dev: false - - /@types/d3-dispatch@3.0.6: - resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==} - dev: false - - /@types/d3-drag@3.0.7: - resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} - dependencies: - '@types/d3-selection': 3.0.10 - dev: false - - /@types/d3-dsv@3.0.7: - resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} - dev: false - - /@types/d3-ease@3.0.2: - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - dev: false - - /@types/d3-fetch@3.0.7: - resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} - dependencies: - '@types/d3-dsv': 3.0.7 - dev: false - - /@types/d3-force@3.0.9: - resolution: {integrity: sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==} - dev: false - - /@types/d3-format@3.0.4: - resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} - dev: false - - /@types/d3-geo@3.1.0: - resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} - dependencies: - '@types/geojson': 7946.0.13 - dev: false - - /@types/d3-hierarchy@3.1.6: - resolution: {integrity: sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==} - dev: false - - /@types/d3-interpolate@3.0.4: - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} - dependencies: - '@types/d3-color': 3.1.3 - dev: false - - /@types/d3-path@3.0.2: - resolution: {integrity: sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==} - dev: false - - /@types/d3-polygon@3.0.2: - resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} - dev: false - - /@types/d3-quadtree@3.0.6: - resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} - dev: false - - /@types/d3-random@3.0.3: - resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} - dev: false - - /@types/d3-scale-chromatic@3.0.3: - resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==} - dev: false - - /@types/d3-scale@4.0.8: - resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} - dependencies: - '@types/d3-time': 3.0.3 - dev: false - - /@types/d3-selection@3.0.10: - resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==} - dev: false - - /@types/d3-shape@3.1.6: - resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} - dependencies: - '@types/d3-path': 3.0.2 - dev: false - - /@types/d3-time-format@4.0.3: - resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} - dev: false - - /@types/d3-time@3.0.3: - resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==} - dev: false - - /@types/d3-timer@3.0.2: - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - dev: false - - /@types/d3-transition@3.0.8: - resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==} - dependencies: - '@types/d3-selection': 3.0.10 - dev: false - - /@types/d3-zoom@3.0.8: - resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} - dependencies: - '@types/d3-interpolate': 3.0.4 - '@types/d3-selection': 3.0.10 - dev: false - - /@types/d3@7.4.3: - resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - dependencies: - '@types/d3-array': 3.2.1 - '@types/d3-axis': 3.0.6 - '@types/d3-brush': 3.0.6 - '@types/d3-chord': 3.0.6 - '@types/d3-color': 3.1.3 - '@types/d3-contour': 3.0.6 - '@types/d3-delaunay': 6.0.4 - '@types/d3-dispatch': 3.0.6 - '@types/d3-drag': 3.0.7 - '@types/d3-dsv': 3.0.7 - '@types/d3-ease': 3.0.2 - '@types/d3-fetch': 3.0.7 - '@types/d3-force': 3.0.9 - '@types/d3-format': 3.0.4 - '@types/d3-geo': 3.1.0 - '@types/d3-hierarchy': 3.1.6 - '@types/d3-interpolate': 3.0.4 - '@types/d3-path': 3.0.2 - '@types/d3-polygon': 3.0.2 - '@types/d3-quadtree': 3.0.6 - '@types/d3-random': 3.0.3 - '@types/d3-scale': 4.0.8 - '@types/d3-scale-chromatic': 3.0.3 - '@types/d3-selection': 3.0.10 - '@types/d3-shape': 3.1.6 - '@types/d3-time': 3.0.3 - '@types/d3-time-format': 4.0.3 - '@types/d3-timer': 3.0.2 - '@types/d3-transition': 3.0.8 - '@types/d3-zoom': 3.0.8 - dev: false - - /@types/geojson@7946.0.13: - resolution: {integrity: sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==} - dev: false - /@types/html-to-text@9.0.4: resolution: {integrity: sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==} dev: true @@ -3307,10 +3013,6 @@ packages: fsevents: 2.3.3 dev: true - /classcat@5.0.4: - resolution: {integrity: sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==} - dev: false - /cli-cursor@4.0.0: resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3403,71 +3105,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - /d3-color@3.1.0: - resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} - engines: {node: '>=12'} - dev: false - - /d3-dispatch@3.0.1: - resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} - engines: {node: '>=12'} - dev: false - - /d3-drag@3.0.0: - resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} - engines: {node: '>=12'} - dependencies: - d3-dispatch: 3.0.1 - d3-selection: 3.0.0 - dev: false - - /d3-ease@3.0.1: - resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} - engines: {node: '>=12'} - dev: false - - /d3-interpolate@3.0.1: - resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} - engines: {node: '>=12'} - dependencies: - d3-color: 3.1.0 - dev: false - - /d3-selection@3.0.0: - resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} - engines: {node: '>=12'} - dev: false - - /d3-timer@3.0.1: - resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} - engines: {node: '>=12'} - dev: false - - /d3-transition@3.0.1(d3-selection@3.0.0): - resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} - engines: {node: '>=12'} - peerDependencies: - d3-selection: 2 - 3 - dependencies: - d3-color: 3.1.0 - d3-dispatch: 3.0.1 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-timer: 3.0.1 - dev: false - - /d3-zoom@3.0.0: - resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} - engines: {node: '>=12'} - dependencies: - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - dev: false - /d@1.0.1: resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} dependencies: @@ -5585,25 +5222,6 @@ packages: loose-envify: 1.4.0 dev: false - /reactflow@11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Q616fElAc5/N37tMwjuRkkgm/VgmnLLTNNCj61z5mvJxae+/VXZQMfot1K6a5LLz9G3SVKqU97PMb9Ga1PRXew==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/background': 11.3.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - '@reactflow/controls': 11.2.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - '@reactflow/core': 11.10.1(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - '@reactflow/minimap': 11.7.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - '@reactflow/node-resizer': 2.2.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - '@reactflow/node-toolbar': 1.3.6(@types/react@18.2.42)(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: diff --git a/src/app.css b/src/app.css index 69801706..fac2b4d3 100644 --- a/src/app.css +++ b/src/app.css @@ -1,5 +1,3 @@ -/* @import 'reactflow/dist/style.css'; */ - /* Vidstack */ @import '@vidstack/react/player/styles/default/theme.css'; @import '@vidstack/react/player/styles/default/layouts/video.css'; diff --git a/src/app.tsx b/src/app.tsx index f5cbc78b..3165aa5b 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,11 +1,9 @@ import { message } from '@tauri-apps/plugin-dialog'; import { fetch } from '@tauri-apps/plugin-http'; import { RouterProvider, createBrowserRouter, defer, redirect } from 'react-router-dom'; -import { ReactFlowProvider } from 'reactflow'; import { ChatsScreen } from '@app/chats'; import { ErrorScreen } from '@app/error'; -import { ExploreScreen } from '@app/explore'; import { useArk } from '@libs/ark'; @@ -87,15 +85,6 @@ export default function App() { return { Component: RelayScreen }; }, }, - { - path: 'explore', - element: ( - - - - ), - errorElement: , - }, { path: 'chats', element: , diff --git a/src/app/chats/chat.tsx b/src/app/chats/chat.tsx index 5f66f3d8..a6bccbbb 100644 --- a/src/app/chats/chat.tsx +++ b/src/app/chats/chat.tsx @@ -12,16 +12,13 @@ import { useArk } from '@libs/ark'; import { LoaderIcon } from '@shared/icons'; import { User } from '@shared/user'; -import { useNostr } from '@utils/hooks/useNostr'; - export function ChatScreen() { const { ark } = useArk(); const { pubkey } = useParams(); - const { fetchNIP04Messages } = useNostr(); const { status, data } = useQuery({ queryKey: ['nip04-dm', pubkey], queryFn: async () => { - return await fetchNIP04Messages(pubkey); + return await ark.getAllMessagesByPubkey({ pubkey }); }, refetchOnWindowFocus: false, }); diff --git a/src/app/chats/index.tsx b/src/app/chats/index.tsx index 67698c9a..700360c8 100644 --- a/src/app/chats/index.tsx +++ b/src/app/chats/index.tsx @@ -5,16 +5,16 @@ import { Outlet } from 'react-router-dom'; import { ChatListItem } from '@app/chats/components/chatListItem'; +import { useArk } from '@libs/ark'; + import { LoaderIcon } from '@shared/icons'; -import { useNostr } from '@utils/hooks/useNostr'; - export function ChatsScreen() { - const { getAllNIP04Chats } = useNostr(); + const { ark } = useArk(); const { status, data } = useQuery({ queryKey: ['nip04-chats'], queryFn: async () => { - return await getAllNIP04Chats(); + return await ark.getAllChats(); }, refetchOnWindowFocus: false, refetchOnMount: false, diff --git a/src/app/explore/components/edge.tsx b/src/app/explore/components/edge.tsx deleted file mode 100644 index 296996b1..00000000 --- a/src/app/explore/components/edge.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { BaseEdge, EdgeProps, getBezierPath } from 'reactflow'; - -export function Edge({ - sourceX, - sourceY, - targetX, - targetY, - sourcePosition, - targetPosition, - style = {}, - markerEnd, -}: EdgeProps) { - const [edgePath] = getBezierPath({ - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, - }); - - return ( - - ); -} diff --git a/src/app/explore/components/groupTitle.tsx b/src/app/explore/components/groupTitle.tsx deleted file mode 100644 index 14828d7f..00000000 --- a/src/app/explore/components/groupTitle.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { memo } from 'react'; - -import { useProfile } from '@utils/hooks/useProfile'; - -export const GroupTitle = memo(function GroupTitle({ pubkey }: { pubkey: string }) { - const { isLoading, user } = useProfile(pubkey); - - if (isLoading) { - return
; - } - - return ( -

{`${ - user.name || user.display_name - }'s network`}

- ); -}); diff --git a/src/app/explore/components/line.tsx b/src/app/explore/components/line.tsx deleted file mode 100644 index 993667c7..00000000 --- a/src/app/explore/components/line.tsx +++ /dev/null @@ -1,14 +0,0 @@ -export function Line({ fromX, fromY, toX, toY }) { - return ( - - - - - ); -} diff --git a/src/app/explore/components/userGroupNode.tsx b/src/app/explore/components/userGroupNode.tsx deleted file mode 100644 index 0356d291..00000000 --- a/src/app/explore/components/userGroupNode.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Handle, Position } from 'reactflow'; - -import { UserWithDrawer } from '@app/explore/components/userWithDrawer'; - -import { GroupTitle } from './groupTitle'; - -export function UserGroupNode({ data }) { - return ( - <> - -
- {data.title ? ( -

{data.title}

- ) : ( - - )} -
- {data.list.map((user: string) => ( - - ))} -
-
- - - ); -} diff --git a/src/app/explore/components/userLatestPosts.tsx b/src/app/explore/components/userLatestPosts.tsx deleted file mode 100644 index 05a0d9fb..00000000 --- a/src/app/explore/components/userLatestPosts.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; -import { useQuery } from '@tanstack/react-query'; -import { useCallback } from 'react'; - -import { LoaderIcon } from '@shared/icons'; -import { MemoizedRepost, MemoizedTextNote, UnknownNote } from '@shared/notes'; - -import { useNostr } from '@utils/hooks/useNostr'; - -export function UserLatestPosts({ pubkey }: { pubkey: string }) { - const { getEventsByPubkey } = useNostr(); - const { status, data } = useQuery({ - queryKey: ['user-posts', pubkey], - queryFn: async () => { - return await getEventsByPubkey(pubkey); - }, - refetchOnWindowFocus: false, - }); - - const renderItem = useCallback( - (event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Repost: - return ; - default: - return ; - } - }, - [data] - ); - - return ( -
-

- Latest post -

-
- {status === 'pending' ? ( -
-
- - Loading latest posts... -
-
- ) : data.length < 1 ? ( -
-
- No posts from 24 hours ago -
-
- ) : ( - data.map((event) => renderItem(event)) - )} -
-
- ); -} diff --git a/src/app/explore/components/userNode.tsx b/src/app/explore/components/userNode.tsx deleted file mode 100644 index 0f5af72e..00000000 --- a/src/app/explore/components/userNode.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Handle, Position } from 'reactflow'; - -import { User } from '@shared/user'; - -export function UserNode({ data }) { - return ( - <> -
- -
- -
-
- - - ); -} diff --git a/src/app/explore/components/userWithDrawer.tsx b/src/app/explore/components/userWithDrawer.tsx deleted file mode 100644 index f498b70b..00000000 --- a/src/app/explore/components/userWithDrawer.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { NDKEvent, NDKKind, NDKUser } from '@nostr-dev-kit/ndk'; -import * as Dialog from '@radix-ui/react-dialog'; -import { memo, useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; -import { toast } from 'sonner'; - -import { useNDK } from '@libs/ndk/provider'; -import { useStorage } from '@libs/storage/provider'; - -import { NIP05 } from '@shared/nip05'; -import { TextNote } from '@shared/notes'; -import { User } from '@shared/user'; - -import { useProfile } from '@utils/hooks/useProfile'; -import { displayNpub } from '@utils/shortenKey'; - -import { UserLatestPosts } from './userLatestPosts'; - -export const UserWithDrawer = memo(function UserWithDrawer({ - pubkey, -}: { - pubkey: string; -}) { - const { db } = useStorage(); - const { ndk } = useNDK(); - const { isLoading, user } = useProfile(pubkey); - - const [followed, setFollowed] = useState(false); - - const follow = async (pubkey: string) => { - try { - const user = ndk.getUser({ pubkey: db.account.pubkey }); - const contacts = await user.follows(); - const add = await user.follow(new NDKUser({ pubkey: pubkey }), contacts); - - if (add) { - setFollowed(true); - } else { - toast('You already follow this user'); - } - } catch (error) { - console.log(error); - } - }; - - const unfollow = async (pubkey: string) => { - try { - const user = ndk.getUser({ pubkey: db.account.pubkey }); - const contacts = await user.follows(); - contacts.delete(new NDKUser({ pubkey: pubkey })); - - let list: string[][]; - contacts.forEach((el) => list.push(['p', el.pubkey, el.relayUrls?.[0] || '', ''])); - - const event = new NDKEvent(ndk); - event.content = ''; - event.kind = NDKKind.Contacts; - event.tags = list; - - const publishedRelays = await event.publish(); - if (publishedRelays) { - setFollowed(false); - } - } catch (error) { - console.log(error); - } - }; - - useEffect(() => { - if (db.account.contacts.includes(pubkey)) { - setFollowed(true); - } - }, []); - - return ( - - - - - - -
- {isLoading ? ( -
-

Loading...

-
- ) : ( - <> -
- {pubkey} -
-
-
-
- {user?.name || user?.display_name || user?.displayName} -
- {user?.nip05 ? ( - - ) : ( - - {displayNpub(pubkey, 16)} - - )} -
- {user?.about ? : null} -
-
- {followed ? ( - - ) : ( - - )} - - Message - -
-
-
- - - )} -
-
-
-
- ); -}); diff --git a/src/app/explore/index.tsx b/src/app/explore/index.tsx deleted file mode 100644 index 2ec9400d..00000000 --- a/src/app/explore/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { useCallback, useMemo, useRef } from 'react'; -import ReactFlow, { - Background, - ConnectionMode, - addEdge, - useEdgesState, - useNodesState, - useReactFlow, -} from 'reactflow'; - -import { Edge } from '@app/explore/components/edge'; -import { Line } from '@app/explore/components/line'; -import { UserGroupNode } from '@app/explore/components/userGroupNode'; -import { UserNode } from '@app/explore/components/userNode'; - -import { useStorage } from '@libs/storage/provider'; - -import { useNostr } from '@utils/hooks/useNostr'; -import { getMultipleRandom } from '@utils/transform'; - -let id = 2; -const getId = () => `${id++}`; -const nodeTypes = { user: UserNode, userGroup: UserGroupNode }; -const edgeTypes = { buttonedge: Edge }; - -export function ExploreScreen() { - const { db } = useStorage(); - const { getContactsByPubkey } = useNostr(); - const { project } = useReactFlow(); - - const defaultContacts = useMemo(() => getMultipleRandom(db.account.contacts, 10), []); - const reactFlowWrapper = useRef(null); - const connectingNodeId = useRef(null); - - const initialNodes = [ - { - id: '0', - type: 'user', - position: { x: 141, y: 0 }, - data: { list: [], title: '', pubkey: db.account.pubkey }, - }, - { - id: '1', - type: 'userGroup', - position: { x: 0, y: 200 }, - data: { list: defaultContacts, title: 'Starting Point', pubkey: '' }, - }, - ]; - const initialEdges = [{ id: 'e0-1', type: 'buttonedge', source: '0', target: '1' }]; - - const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); - const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); - - const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []); - - const onConnectStart = useCallback((_, { nodeId }) => { - connectingNodeId.current = nodeId; - }, []); - - const onConnectEnd = useCallback( - async (event) => { - const targetIsPane = event.target.classList.contains('react-flow__pane'); - - if (targetIsPane) { - const { top, left } = reactFlowWrapper.current.getBoundingClientRect(); - - const id = getId(); - const prevData = nodes.slice(-1)[0]; - const randomPubkey = getMultipleRandom(prevData.data.list, 1)[0]; - - const newContactList = await getContactsByPubkey(randomPubkey); - const newNode = { - id, - type: 'userGroup', - position: project({ x: event.clientX - left, y: event.clientY - top }), - data: { list: newContactList, title: null, pubkey: randomPubkey }, - }; - - setNodes((nds) => nds.concat(newNode)); - setEdges((eds) => - eds.concat({ - id, - type: 'buttonedge', - source: connectingNodeId.current, - target: id, - }) - ); - } - }, - [project] - ); - - return ( -
- - - -
- ); -} diff --git a/src/libs/ark/ark.ts b/src/libs/ark/ark.ts index 49290dd7..27d316cb 100644 --- a/src/libs/ark/ark.ts +++ b/src/libs/ark/ark.ts @@ -50,9 +50,15 @@ export class Ark { hashtag: boolean; }; - constructor({ storage }: { storage: Database }) { + constructor({ storage, platform }: { storage: Database; platform: Platform }) { this.#storage = storage; - this.#init(); + this.platform = platform; + this.settings = { + autoupdate: false, + outbox: false, + media: true, + hashtag: true, + }; } async #keyring_save(key: string, value: string) { @@ -74,10 +80,8 @@ export class Ark { } async #initNostrSigner({ nsecbunker }: { nsecbunker?: boolean }) { - if (!this.account) { - this.readyToSign = false; - return null; - } + const account = await this.getActiveAccount(); + this.account = account; try { // NIP-46 Signer @@ -128,7 +132,7 @@ export class Ark { } } - async #init() { + public async init() { const outboxSetting = await this.getSettingValue('outbox'); const bunkerSetting = await this.getSettingValue('nsecbunker'); @@ -178,6 +182,7 @@ export class Ark { this.account.contacts = [...contacts].map((user) => user.pubkey); } + this.relays = [...ndk.pool.relays.values()].map((relay) => relay.url); this.#ndk = ndk; this.#fetcher = fetcher; } @@ -214,10 +219,8 @@ export class Ark { ); if (results.length) { - this.account = results[0]; - this.account.contacts = []; + return results[0]; } else { - console.log('no active account, please create new account'); return null; } } @@ -766,6 +769,72 @@ export class Ark { return false; } + /** + * Return all NIP-04 messages + * @deprecated NIP-04 will be replace by NIP-44 in the next update + */ + public async getAllChats() { + const events = await this.#fetcher.fetchAllEvents( + this.relays, + { + kinds: [NDKKind.EncryptedDirectMessage], + '#p': [this.account.pubkey], + }, + { since: 0 } + ); + + const dedup: NDKEvent[] = Object.values( + events.reduce((ev, { id, content, pubkey, created_at, tags }) => { + if (ev[pubkey]) { + if (ev[pubkey].created_at < created_at) { + ev[pubkey] = { id, content, pubkey, created_at, tags }; + } + } else { + ev[pubkey] = { id, content, pubkey, created_at, tags }; + } + return ev; + }, {}) + ); + + return dedup; + } + + /** + * Return all NIP-04 messages by pubkey + * @deprecated NIP-04 will be replace by NIP-44 in the next update + */ + public async getAllMessagesByPubkey({ pubkey }: { pubkey: string }) { + let senderMessages: NostrEventExt[] = []; + + if (pubkey !== this.account.pubkey) { + senderMessages = await this.#fetcher.fetchAllEvents( + this.relays, + { + kinds: [NDKKind.EncryptedDirectMessage], + authors: [pubkey], + '#p': [this.account.pubkey], + }, + { since: 0 } + ); + } + + const userMessages = await this.#fetcher.fetchAllEvents( + this.relays, + { + kinds: [NDKKind.EncryptedDirectMessage], + authors: [this.account.pubkey], + '#p': [pubkey], + }, + { since: 0 } + ); + + const all = [...senderMessages, ...userMessages].sort( + (a, b) => a.created_at - b.created_at + ); + + return all as unknown as NDKEvent[]; + } + public async nip04Decrypt({ event }: { event: NDKEvent }) { try { const sender = new NDKUser({ diff --git a/src/libs/ark/provider.tsx b/src/libs/ark/provider.tsx index 6f1290fa..4e3ecc4a 100644 --- a/src/libs/ark/provider.tsx +++ b/src/libs/ark/provider.tsx @@ -1,4 +1,5 @@ import { ask } from '@tauri-apps/plugin-dialog'; +import { platform } from '@tauri-apps/plugin-os'; import { relaunch } from '@tauri-apps/plugin-process'; import Database from '@tauri-apps/plugin-sql'; import { check } from '@tauri-apps/plugin-updater'; @@ -26,9 +27,10 @@ const ArkProvider = ({ children }: PropsWithChildren) => { async function initArk() { try { const sqlite = await Database.load('sqlite:lume_v2.db'); - const _ark = new Ark({ storage: sqlite }); + const platformName = await platform(); - if (!_ark.account) await _ark.getActiveAccount(); + const _ark = new Ark({ storage: sqlite, platform: platformName }); + await _ark.init(); const settings = await _ark.getAllSettings(); let autoUpdater = false; diff --git a/src/libs/ndk/cache.ts b/src/libs/ndk/cache.ts deleted file mode 100644 index 53abe434..00000000 --- a/src/libs/ndk/cache.ts +++ /dev/null @@ -1,426 +0,0 @@ -// inspired by: https://github.com/nostr-dev-kit/ndk/tree/master/ndk-cache-dexie -import { NDKEvent, NDKRelay, profileFromEvent } from '@nostr-dev-kit/ndk'; -import type { - Hexpubkey, - NDKCacheAdapter, - NDKFilter, - NDKSubscription, - NDKUserProfile, - NostrEvent, -} from '@nostr-dev-kit/ndk'; -import { LRUCache } from 'lru-cache'; -import { matchFilter } from 'nostr-tools'; - -import { LumeStorage } from '@libs/storage/instance'; - -export default class NDKCacheAdapterTauri implements NDKCacheAdapter { - public db: LumeStorage; - public profiles?: LRUCache; - private dirtyProfiles: Set = new Set(); - readonly locking: boolean; - - constructor(db: LumeStorage) { - this.db = db; - this.locking = true; - - this.profiles = new LRUCache({ - max: 100000, - }); - - setInterval(() => { - this.dumpProfiles(); - }, 1000 * 10); - } - - public async query(subscription: NDKSubscription): Promise { - Promise.allSettled( - subscription.filters.map((filter) => this.processFilter(filter, subscription)) - ); - } - - public async fetchProfile(pubkey: Hexpubkey) { - if (!this.profiles) return null; - - let profile = this.profiles.get(pubkey); - - if (!profile) { - const user = await this.db.getCacheUser(pubkey); - if (user) { - profile = user.profile as NDKUserProfile; - this.profiles.set(pubkey, profile); - } - } - - return profile; - } - - public saveProfile(pubkey: Hexpubkey, profile: NDKUserProfile) { - if (!this.profiles) return; - - this.profiles.set(pubkey, profile); - - this.dirtyProfiles.add(pubkey); - } - - private async processFilter( - filter: NDKFilter, - subscription: NDKSubscription - ): Promise { - const _filter = { ...filter }; - delete _filter.limit; - const filterKeys = Object.keys(_filter || {}).sort(); - - try { - (await this.byKindAndAuthor(filterKeys, filter, subscription)) || - (await this.byAuthors(filterKeys, filter, subscription)) || - (await this.byKinds(filterKeys, filter, subscription)) || - (await this.byIdsQuery(filterKeys, filter, subscription)) || - (await this.byNip33Query(filterKeys, filter, subscription)) || - (await this.byTagsAndOptionallyKinds(filterKeys, filter, subscription)); - } catch (error) { - console.error(error); - } - } - - public async setEvent( - event: NDKEvent, - _filter: NDKFilter, - relay?: NDKRelay - ): Promise { - if (event.kind === 0) { - if (!this.profiles) return; - - const profile: NDKUserProfile = profileFromEvent(event); - this.profiles.set(event.pubkey, profile); - } else { - let addEvent = true; - - if (event.isParamReplaceable()) { - const replaceableId = `${event.kind}:${event.pubkey}:${event.tagId()}`; - const existingEvent = await this.db.getCacheEvent(replaceableId); - if ( - existingEvent && - event.created_at && - existingEvent.createdAt > event.created_at - ) { - addEvent = false; - } - } - - if (addEvent) { - this.db.setCacheEvent({ - id: event.tagId(), - pubkey: event.pubkey, - content: event.content, - kind: event.kind!, - createdAt: event.created_at!, - relay: relay?.url, - event: JSON.stringify(event.rawEvent()), - }); - - // Don't cache contact lists as tags since it's expensive - // and there is no use case for it - if (event.kind !== 3) { - event.tags.forEach((tag) => { - if (tag[0].length !== 1) return; - - this.db.setCacheEventTag({ - id: `${event.id}:${tag[0]}:${tag[1]}`, - eventId: event.id, - tag: tag[0], - value: tag[1], - tagValue: tag[0] + tag[1], - }); - }); - } - } - } - } - - /** - * Searches by authors - */ - private async byAuthors( - filterKeys: string[], - filter: NDKFilter, - subscription: NDKSubscription - ): Promise { - const f = ['authors']; - const hasAllKeys = - filterKeys.length === f.length && f.every((k) => filterKeys.includes(k)); - - let foundEvents = false; - - if (hasAllKeys && filter.authors) { - for (const pubkey of filter.authors) { - const events = await this.db.getCacheEventsByPubkey(pubkey); - for (const event of events) { - let rawEvent: NostrEvent; - try { - rawEvent = JSON.parse(event.event); - } catch (e) { - console.log('failed to parse event', e); - continue; - } - - const ndkEvent = new NDKEvent(undefined, rawEvent); - const relay = event.relay ? new NDKRelay(event.relay) : undefined; - subscription.eventReceived(ndkEvent, relay, true); - foundEvents = true; - } - } - } - return foundEvents; - } - - /** - * Searches by kinds - */ - private async byKinds( - filterKeys: string[], - filter: NDKFilter, - subscription: NDKSubscription - ): Promise { - const f = ['kinds']; - const hasAllKeys = - filterKeys.length === f.length && f.every((k) => filterKeys.includes(k)); - - let foundEvents = false; - - if (hasAllKeys && filter.kinds) { - for (const kind of filter.kinds) { - const events = await this.db.getCacheEventsByKind(kind); - for (const event of events) { - let rawEvent: NostrEvent; - try { - rawEvent = JSON.parse(event.event); - } catch (e) { - console.log('failed to parse event', e); - continue; - } - - const ndkEvent = new NDKEvent(undefined, rawEvent); - const relay = event.relay ? new NDKRelay(event.relay) : undefined; - subscription.eventReceived(ndkEvent, relay, true); - foundEvents = true; - } - } - } - return foundEvents; - } - - /** - * Searches by ids - */ - private async byIdsQuery( - filterKeys: string[], - filter: NDKFilter, - subscription: NDKSubscription - ): Promise { - const f = ['ids']; - const hasAllKeys = - filterKeys.length === f.length && f.every((k) => filterKeys.includes(k)); - - if (hasAllKeys && filter.ids) { - for (const id of filter.ids) { - const event = await this.db.getCacheEvent(id); - if (!event) continue; - - let rawEvent: NostrEvent; - try { - rawEvent = JSON.parse(event.event); - } catch (e) { - console.log('failed to parse event', e); - continue; - } - - const ndkEvent = new NDKEvent(undefined, rawEvent); - const relay = event.relay ? new NDKRelay(event.relay) : undefined; - subscription.eventReceived(ndkEvent, relay, true); - } - - return true; - } - - return false; - } - - /** - * Searches by NIP-33 - */ - private async byNip33Query( - filterKeys: string[], - filter: NDKFilter, - subscription: NDKSubscription - ): Promise { - const f = ['#d', 'authors', 'kinds']; - const hasAllKeys = - filterKeys.length === f.length && f.every((k) => filterKeys.includes(k)); - - if (hasAllKeys && filter.kinds && filter.authors) { - for (const kind of filter.kinds) { - const replaceableKind = kind >= 30000 && kind < 40000; - - if (!replaceableKind) continue; - - for (const author of filter.authors) { - for (const dTag of filter['#d']) { - const replaceableId = `${kind}:${author}:${dTag}`; - const event = await this.db.getCacheEvent(replaceableId); - if (!event) continue; - - let rawEvent: NostrEvent; - try { - rawEvent = JSON.parse(event.event); - } catch (e) { - console.log('failed to parse event', e); - continue; - } - - const ndkEvent = new NDKEvent(undefined, rawEvent); - const relay = event.relay ? new NDKRelay(event.relay) : undefined; - subscription.eventReceived(ndkEvent, relay, true); - } - } - } - return true; - } - return false; - } - - /** - * Searches by kind & author - */ - private async byKindAndAuthor( - filterKeys: string[], - filter: NDKFilter, - subscription: NDKSubscription - ): Promise { - const f = ['authors', 'kinds']; - const hasAllKeys = - filterKeys.length === f.length && f.every((k) => filterKeys.includes(k)); - let foundEvents = false; - - if (!hasAllKeys) return false; - - if (filter.kinds && filter.authors) { - for (const kind of filter.kinds) { - for (const author of filter.authors) { - const events = await this.db.getCacheEventsByKindAndAuthor(kind, author); - - for (const event of events) { - let rawEvent: NostrEvent; - try { - rawEvent = JSON.parse(event.event); - } catch (e) { - console.log('failed to parse event', e); - continue; - } - - const ndkEvent = new NDKEvent(undefined, rawEvent); - const relay = event.relay ? new NDKRelay(event.relay) : undefined; - subscription.eventReceived(ndkEvent, relay, true); - foundEvents = true; - } - } - } - } - return foundEvents; - } - - /** - * Searches by tags and optionally filters by tags - */ - private async byTagsAndOptionallyKinds( - filterKeys: string[], - filter: NDKFilter, - subscription: NDKSubscription - ): Promise { - for (const filterKey of filterKeys) { - const isKind = filterKey === 'kinds'; - const isTag = filterKey.startsWith('#') && filterKey.length === 2; - - if (!isKind && !isTag) return false; - } - - const events = await this.filterByTag(filterKeys, filter); - const kinds = filter.kinds as number[]; - - for (const event of events) { - if (!kinds?.includes(event.kind!)) continue; - - subscription.eventReceived(event, undefined, true); - } - - return false; - } - - private async filterByTag( - filterKeys: string[], - filter: NDKFilter - ): Promise { - const retEvents: NDKEvent[] = []; - - for (const filterKey of filterKeys) { - if (filterKey.length !== 2) continue; - const tag = filterKey.slice(1); - // const values = filter[filterKey] as string[]; - const values: string[] = []; - for (const [key, value] of Object.entries(filter)) { - if (key === filterKey) values.push(value as string); - } - - for (const value of values) { - const eventTags = await this.db.getCacheEventTagsByTagValue(tag + value); - if (!eventTags.length) continue; - - const eventIds = eventTags.map((t) => t.eventId); - - const events = await this.db.getCacheEvents(eventIds); - for (const event of events) { - let rawEvent; - try { - rawEvent = JSON.parse(event.event); - - // Make sure all passed filters match the event - if (!matchFilter(filter, rawEvent)) continue; - } catch (e) { - console.log('failed to parse event', e); - continue; - } - - const ndkEvent = new NDKEvent(undefined, rawEvent); - const relay = event.relay ? new NDKRelay(event.relay) : undefined; - ndkEvent.relay = relay; - retEvents.push(ndkEvent); - } - } - } - - return retEvents; - } - - private async dumpProfiles(): Promise { - const profiles = []; - - if (!this.profiles) return; - - for (const pubkey of this.dirtyProfiles) { - const profile = this.profiles.get(pubkey); - - if (!profile) continue; - - profiles.push({ - pubkey, - profile: JSON.stringify(profile), - createdAt: Date.now(), - }); - } - - if (profiles.length) { - await this.db.setCacheProfiles(profiles); - } - - this.dirtyProfiles.clear(); - } -} diff --git a/src/libs/ndk/instance.ts b/src/libs/ndk/instance.ts deleted file mode 100644 index 6f95c5aa..00000000 --- a/src/libs/ndk/instance.ts +++ /dev/null @@ -1,214 +0,0 @@ -import NDK, { - NDKEvent, - NDKKind, - NDKNip46Signer, - NDKPrivateKeySigner, -} from '@nostr-dev-kit/ndk'; -import { ndkAdapter } from '@nostr-fetch/adapter-ndk'; -import { useQueryClient } from '@tanstack/react-query'; -import { ask } from '@tauri-apps/plugin-dialog'; -import { relaunch } from '@tauri-apps/plugin-process'; -import { NostrFetcher, normalizeRelayUrlSet } from 'nostr-fetch'; -import { useEffect, useState } from 'react'; -import { toast } from 'sonner'; - -import NDKCacheAdapterTauri from '@libs/ndk/cache'; -import { useStorage } from '@libs/storage/provider'; - -import { FETCH_LIMIT } from '@utils/constants'; - -export const NDKInstance = () => { - const { db } = useStorage(); - const queryClient = useQueryClient(); - - const [ndk, setNDK] = useState(undefined); - const [fetcher, setFetcher] = useState(undefined); - const [relayUrls, setRelayUrls] = useState([]); - - async function getSigner(nsecbunker?: boolean) { - if (!db.account) return; - - try { - // NIP-46 Signer - if (nsecbunker) { - const localSignerPrivkey = await db.secureLoad(`${db.account.id}-nsecbunker`); - if (!localSignerPrivkey) return null; - - const localSigner = new NDKPrivateKeySigner(localSignerPrivkey); - const bunker = new NDK({ - explicitRelayUrls: ['wss://relay.nsecbunker.com', 'wss://nostr.vulpem.com'], - }); - await bunker.connect(); - - const remoteSigner = new NDKNip46Signer(bunker, db.account.pubkey, localSigner); - await remoteSigner.blockUntilReady(); - - return remoteSigner; - } - - // Privkey Signer - const userPrivkey = await db.secureLoad(db.account.pubkey); - if (!userPrivkey) return null; - return new NDKPrivateKeySigner(userPrivkey); - } catch (e) { - console.log(e); - if (e === 'Token already redeemed') { - toast.info( - 'nsecbunker token already redeemed. You need to re-login with another token.' - ); - - await db.secureRemove(`${db.account.pubkey}-nsecbunker`); - await db.accountLogout(); - } - - return null; - } - } - - async function initNDK() { - const outboxSetting = await db.getSettingValue('outbox'); - const bunkerSetting = await db.getSettingValue('nsecbunker'); - - const bunker = !!parseInt(bunkerSetting); - const outbox = !!parseInt(outboxSetting); - - const explicitRelayUrls = normalizeRelayUrlSet([ - 'wss://relay.damus.io', - 'wss://relay.nostr.band', - 'wss://nos.lol', - 'wss://nostr.mutinywallet.com', - ]); - - // #TODO: user should config outbox relays - const outboxRelayUrls = normalizeRelayUrlSet(['wss://purplepag.es']); - - // #TODO: user should config blacklist relays - const blacklistRelayUrls = normalizeRelayUrlSet(['wss://brb.io']); - - try { - const tauriAdapter = new NDKCacheAdapterTauri(db); - const instance = new NDK({ - explicitRelayUrls, - outboxRelayUrls, - blacklistRelayUrls, - enableOutboxModel: outbox, - autoConnectUserRelays: true, - autoFetchUserMutelist: true, - cacheAdapter: tauriAdapter, - // clientName: 'Lume', - // clientNip89: '', - }); - - // add signer if exist - const signer = await getSigner(bunker); - if (signer) instance.signer = signer; - - // connect - await instance.connect(); - const _fetcher = NostrFetcher.withCustomPool(ndkAdapter(instance)); - - // update account's metadata - if (db.account) { - const user = instance.getUser({ pubkey: db.account.pubkey }); - instance.activeUser = user; - - const contacts = await user.follows(undefined /* outbox */); - db.account.contacts = [...contacts].map((user) => user.pubkey); - - // prefetch newsfeed - await queryClient.prefetchInfiniteQuery({ - queryKey: ['newsfeed'], - initialPageParam: 0, - queryFn: async ({ - signal, - pageParam, - }: { - signal: AbortSignal; - pageParam: number; - }) => { - const rootIds = new Set(); - const dedupQueue = new Set(); - - const events = await _fetcher.fetchLatestEvents( - explicitRelayUrls, - { - kinds: [NDKKind.Text, NDKKind.Repost], - authors: db.account.contacts, - }, - FETCH_LIMIT, - { asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal } - ); - - const ndkEvents = events.map((event) => { - return new NDKEvent(ndk, event); - }); - - ndkEvents.forEach((event) => { - const tags = event.tags.filter((el) => el[0] === 'e'); - if (tags && tags.length > 0) { - const rootId = tags.filter((el) => el[3] === 'root')[1] ?? tags[0][1]; - if (rootIds.has(rootId)) return dedupQueue.add(event.id); - rootIds.add(rootId); - } - }); - - return ndkEvents - .filter((event) => !dedupQueue.has(event.id)) - .sort((a, b) => b.created_at - a.created_at); - }, - }); - - // prefetch notification - await queryClient.prefetchInfiniteQuery({ - queryKey: ['notification'], - initialPageParam: 0, - queryFn: async ({ - signal, - pageParam, - }: { - signal: AbortSignal; - pageParam: number; - }) => { - const events = await _fetcher.fetchLatestEvents( - explicitRelayUrls, - { - kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap], - '#p': [db.account.pubkey], - }, - FETCH_LIMIT, - { asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal } - ); - - const ndkEvents = events.map((event) => { - return new NDKEvent(ndk, event); - }); - - return ndkEvents.sort((a, b) => b.created_at - a.created_at); - }, - }); - } - - setNDK(instance); - setFetcher(_fetcher); - setRelayUrls(explicitRelayUrls); - } catch (e) { - console.error(e); - const yes = await ask(e, { - title: 'Lume', - type: 'error', - okLabel: 'Yes', - }); - if (yes) relaunch(); - } - } - - useEffect(() => { - if (!ndk) initNDK(); - }, []); - - return { - ndk, - fetcher, - relayUrls, - }; -}; diff --git a/src/libs/ndk/provider.tsx b/src/libs/ndk/provider.tsx deleted file mode 100644 index 7ad2fe56..00000000 --- a/src/libs/ndk/provider.tsx +++ /dev/null @@ -1,80 +0,0 @@ -// source: https://github.com/nostr-dev-kit/ndk-react/ -import NDK from '@nostr-dev-kit/ndk'; -import Markdown from 'markdown-to-jsx'; -import { NostrFetcher } from 'nostr-fetch'; -import { PropsWithChildren, createContext, useContext } from 'react'; - -import { NDKInstance } from '@libs/ndk/instance'; - -import { LoaderIcon } from '@shared/icons'; - -import { QUOTES } from '@utils/constants'; - -interface NDKContext { - ndk: undefined | NDK; - fetcher: undefined | NostrFetcher; - relayUrls: string[]; -} - -const NDKContext = createContext({ - ndk: undefined, - fetcher: undefined, - relayUrls: [], -}); - -const NDKProvider = ({ children }: PropsWithChildren) => { - const { ndk, relayUrls, fetcher } = NDKInstance(); - - if (!ndk) - return ( -
-
-
TIP:
- - {QUOTES[Math.floor(Math.random() * QUOTES.length)]} - -
-
- -

Connecting to relays...

-
-
- ); - - return ( - - {children} - - ); -}; - -const useNDK = () => { - const context = useContext(NDKContext); - if (context === undefined) { - throw new Error('import NDKProvider to use useNDK'); - } - return context; -}; - -export { NDKProvider, useNDK }; diff --git a/src/libs/storage/instance.ts b/src/libs/storage/instance.ts deleted file mode 100644 index 7a91ff9e..00000000 --- a/src/libs/storage/instance.ts +++ /dev/null @@ -1,486 +0,0 @@ -import { NDKEvent } from '@nostr-dev-kit/ndk'; -import { invoke } from '@tauri-apps/api/primitives'; -import { Platform } from '@tauri-apps/plugin-os'; -import Database from '@tauri-apps/plugin-sql'; - -import { rawEvent } from '@utils/transform'; -import type { - Account, - DBEvent, - NDKCacheEvent, - NDKCacheEventTag, - NDKCacheUser, - NDKCacheUserProfile, - Relays, - Widget, -} from '@utils/types'; - -export class LumeStorage { - public db: Database; - public account: Account | null; - public platform: Platform | null; - public settings: { - autoupdate: boolean; - outbox: boolean; - media: boolean; - hashtag: boolean; - }; - - constructor(sqlite: Database, platform: Platform) { - this.db = sqlite; - this.account = null; - this.platform = platform; - this.settings = { autoupdate: false, outbox: false, media: true, hashtag: true }; - } - - public async secureSave(key: string, value: string) { - return await invoke('secure_save', { key, value }); - } - - public async secureLoad(key: string) { - try { - const value: string = await invoke('secure_load', { key }); - if (!value) return null; - return value; - } catch { - return null; - } - } - - public async secureRemove(key: string) { - return await invoke('secure_remove', { key }); - } - - public async getAllCacheUsers() { - const results: Array = await this.db.select( - 'SELECT * FROM ndk_users ORDER BY createdAt DESC;' - ); - - if (!results.length) return []; - - const users: NDKCacheUserProfile[] = results.map((item) => ({ - pubkey: item.pubkey, - ...JSON.parse(item.profile as string), - })); - return users; - } - - public async getCacheUser(pubkey: string) { - const results: Array = await this.db.select( - 'SELECT * FROM ndk_users WHERE pubkey = $1 ORDER BY pubkey DESC LIMIT 1;', - [pubkey] - ); - - if (!results.length) return null; - - if (typeof results[0].profile === 'string') - results[0].profile = JSON.parse(results[0].profile); - - return results[0]; - } - - public async getCacheEvent(id: string) { - const results: Array = await this.db.select( - 'SELECT * FROM ndk_events WHERE id = $1 ORDER BY id DESC LIMIT 1;', - [id] - ); - - if (!results.length) return null; - return results[0]; - } - - public async getCacheEvents(ids: string[]) { - const idsArr = `'${ids.join("','")}'`; - - const results: Array = await this.db.select( - `SELECT * FROM ndk_events WHERE id IN (${idsArr}) ORDER BY id;` - ); - - if (!results.length) return []; - return results; - } - - public async getCacheEventsByPubkey(pubkey: string) { - const results: Array = await this.db.select( - 'SELECT * FROM ndk_events WHERE pubkey = $1 ORDER BY id;', - [pubkey] - ); - - if (!results.length) return []; - return results; - } - - public async getCacheEventsByKind(kind: number) { - const results: Array = await this.db.select( - 'SELECT * FROM ndk_events WHERE kind = $1 ORDER BY id;', - [kind] - ); - - if (!results.length) return []; - return results; - } - - public async getCacheEventsByKindAndAuthor(kind: number, pubkey: string) { - const results: Array = await this.db.select( - 'SELECT * FROM ndk_events WHERE kind = $1 AND pubkey = $2 ORDER BY id;', - [kind, pubkey] - ); - - if (!results.length) return []; - return results; - } - - public async getCacheEventTagsByTagValue(tagValue: string) { - const results: Array = await this.db.select( - 'SELECT * FROM ndk_eventtags WHERE tagValue = $1 ORDER BY id;', - [tagValue] - ); - - if (!results.length) return []; - return results; - } - - public async setCacheEvent({ - id, - pubkey, - content, - kind, - createdAt, - relay, - event, - }: NDKCacheEvent) { - return await this.db.execute( - 'INSERT OR IGNORE INTO ndk_events (id, pubkey, content, kind, createdAt, relay, event) VALUES ($1, $2, $3, $4, $5, $6, $7);', - [id, pubkey, content, kind, createdAt, relay, event] - ); - } - - public async setCacheEventTag({ id, eventId, tag, value, tagValue }: NDKCacheEventTag) { - return await this.db.execute( - 'INSERT OR IGNORE INTO ndk_eventtags (id, eventId, tag, value, tagValue) VALUES ($1, $2, $3, $4, $5);', - [id, eventId, tag, value, tagValue] - ); - } - - public async setCacheProfiles(profiles: Array) { - return await Promise.all( - profiles.map( - async (profile) => - await this.db.execute( - 'INSERT OR IGNORE INTO ndk_users (pubkey, profile, createdAt) VALUES ($1, $2, $3);', - [profile.pubkey, profile.profile, profile.createdAt] - ) - ) - ); - } - - public async checkAccount() { - const result: Array<{ total: string }> = await this.db.select( - 'SELECT COUNT(*) AS "total" FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;' - ); - return parseInt(result[0].total); - } - - public async getActiveAccount() { - const results: Array = await this.db.select( - 'SELECT * FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;' - ); - - if (results.length) { - this.account = results[0]; - this.account.contacts = []; - } else { - console.log('no active account, please create new account'); - return null; - } - } - - public async createAccount(npub: string, pubkey: string) { - const existAccounts: Array = await this.db.select( - 'SELECT * FROM accounts WHERE pubkey = $1 ORDER BY id DESC LIMIT 1;', - [pubkey] - ); - - if (existAccounts.length) { - await this.db.execute("UPDATE accounts SET is_active = '1' WHERE pubkey = $1;", [ - pubkey, - ]); - } else { - await this.db.execute( - 'INSERT OR IGNORE INTO accounts (id, pubkey, is_active) VALUES ($1, $2, $3);', - [npub, pubkey, 1] - ); - } - - return await this.getActiveAccount(); - } - - public async updateAccount(column: string, value: string) { - const insert = await this.db.execute( - `UPDATE accounts SET ${column} = $1 WHERE id = $2;`, - [value, this.account.id] - ); - - if (insert) { - const account = await this.getActiveAccount(); - return account; - } - } - - public async getWidgets() { - const widgets: Array = await this.db.select( - 'SELECT * FROM widgets WHERE account_id = $1 ORDER BY created_at DESC;', - [this.account.id] - ); - return widgets; - } - - public async createWidget(kind: number, title: string, content: string | string[]) { - const insert = await this.db.execute( - 'INSERT INTO widgets (account_id, kind, title, content) VALUES ($1, $2, $3, $4);', - [this.account.id, kind, title, content] - ); - - if (insert) { - const widgets: Array = await this.db.select( - 'SELECT * FROM widgets ORDER BY id DESC LIMIT 1;' - ); - if (widgets.length < 1) console.error('get created widget failed'); - return widgets[0]; - } else { - console.error('create widget failed'); - } - } - - public async removeWidget(id: string) { - const res = await this.db.execute('DELETE FROM widgets WHERE id = $1;', [id]); - if (res) return id; - } - - public async createEvent(event: NDKEvent) { - const rawNostrEvent = rawEvent(event); - - let root: string; - let reply: string; - - if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) { - root = event.tags[0][1]; - } else { - root = event.tags.find((el) => el[3] === 'root')?.[1]; - reply = event.tags.find((el) => el[3] === 'reply')?.[1]; - } - - return await this.db.execute( - 'INSERT OR IGNORE INTO events (id, account_id, event, author, kind, root_id, reply_id, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8);', - [ - event.id, - this.account.id, - JSON.stringify(rawNostrEvent), - event.pubkey, - event.kind, - root, - reply, - event.created_at, - ] - ); - } - - public async getEventByID(id: string) { - const results: DBEvent[] = await this.db.select( - 'SELECT * FROM events WHERE id = $1 LIMIT 1;', - [id] - ); - - if (results.length < 1) return null; - return JSON.parse(results[0].event as string) as NDKEvent; - } - - public async countTotalEvents() { - const result: Array<{ total: string }> = await this.db.select( - 'SELECT COUNT(*) AS "total" FROM events WHERE account_id = $1;', - [this.account.id] - ); - return parseInt(result[0].total); - } - - public async getAllEvents(limit: number, offset: number) { - const totalEvents = await this.countTotalEvents(); - const nextCursor = offset + limit; - - const events: { data: DBEvent[] | null; nextCursor: number } = { - data: null, - nextCursor: 0, - }; - - const query: DBEvent[] = await this.db.select( - 'SELECT * FROM events WHERE account_id = $1 GROUP BY root_id ORDER BY created_at DESC LIMIT $2 OFFSET $3;', - [this.account.id, limit, offset] - ); - - if (query && query.length > 0) { - events['data'] = query; - events['nextCursor'] = - Math.round(totalEvents / nextCursor) > 1 ? nextCursor : undefined; - - return events; - } - - return { - data: [], - nextCursor: 0, - }; - } - - public async getAllEventsByAuthors(authors: string[], limit: number, offset: number) { - const totalEvents = await this.countTotalEvents(); - const nextCursor = offset + limit; - const authorsArr = `'${authors.join("','")}'`; - - const events: { data: DBEvent[] | null; nextCursor: number } = { - data: null, - nextCursor: 0, - }; - - const query: DBEvent[] = await this.db.select( - `SELECT * FROM events WHERE author IN (${authorsArr}) ORDER BY created_at DESC LIMIT $1 OFFSET $2;`, - [limit, offset] - ); - - if (query && query.length > 0) { - events['data'] = query; - events['nextCursor'] = - Math.round(totalEvents / nextCursor) > 1 ? nextCursor : undefined; - - return events; - } - - return { - data: [], - nextCursor: 0, - }; - } - - public async getAllEventsByKinds(kinds: number[], limit: number, offset: number) { - const totalEvents = await this.countTotalEvents(); - const nextCursor = offset + limit; - const authorsArr = `'${kinds.join("','")}'`; - - const events: { data: DBEvent[] | null; nextCursor: number } = { - data: null, - nextCursor: 0, - }; - - const query: DBEvent[] = await this.db.select( - `SELECT * FROM events WHERE kinds IN (${authorsArr}) AND account_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3;`, - [this.account.id, limit, offset] - ); - - if (query && query.length > 0) { - events['data'] = query; - events['nextCursor'] = - Math.round(totalEvents / nextCursor) > 1 ? nextCursor : undefined; - - return events; - } - - return { - data: [], - nextCursor: 0, - }; - } - - public async isEventsEmpty() { - const results: DBEvent[] = await this.db.select( - 'SELECT * FROM events WHERE account_id = $1 ORDER BY id DESC LIMIT 1;', - [this.account.id] - ); - - return results.length < 1; - } - - public async createRelay(relay: string, purpose?: string) { - const existRelays: Relays[] = await this.db.select( - 'SELECT * FROM relays WHERE relay = $1 AND account_id = $2 ORDER BY id DESC LIMIT 1;', - [relay, this.account.id] - ); - - if (existRelays.length) return; - - return await this.db.execute( - 'INSERT OR IGNORE INTO relays (account_id, relay, purpose) VALUES ($1, $2, $3);', - [this.account.id, relay, purpose || ''] - ); - } - - public async removeRelay(relay: string) { - return await this.db.execute(`DELETE FROM relays WHERE relay = "${relay}";`); - } - - public async createSetting(key: string, value: string | undefined) { - if (value) { - return await this.db.execute( - 'INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2);', - [key, value] - ); - } - - const currentSetting = await this.checkSettingValue(key); - - if (!currentSetting) - return await this.db.execute( - 'INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2);', - [key, value] - ); - - const currentValue = !!parseInt(currentSetting); - - return await this.db.execute('UPDATE settings SET value = $1 WHERE key = $2;', [ - +!currentValue, - key, - ]); - } - - public async getAllSettings() { - const results: { key: string; value: string }[] = await this.db.select( - 'SELECT * FROM settings ORDER BY id DESC;' - ); - if (results.length < 1) return null; - return results; - } - - public async checkSettingValue(key: string) { - const results: { key: string; value: string }[] = await this.db.select( - 'SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;', - [key] - ); - if (!results.length) return false; - return results[0].value; - } - - public async getSettingValue(key: string) { - const results: { key: string; value: string }[] = await this.db.select( - 'SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;', - [key] - ); - if (!results.length) return '0'; - return results[0].value; - } - - public async clearCache() { - await this.db.execute('DELETE FROM ndk_events;'); - await this.db.execute('DELETE FROM ndk_eventtags;'); - await this.db.execute('DELETE FROM ndk_users;'); - } - - public async accountLogout() { - // update current account status - await this.db.execute("UPDATE accounts SET is_active = '0' WHERE id = $1;", [ - this.account.id, - ]); - this.account = null; - } - - public async close() { - return this.db.close(); - } -} diff --git a/src/libs/storage/provider.tsx b/src/libs/storage/provider.tsx deleted file mode 100644 index a2bfa4ac..00000000 --- a/src/libs/storage/provider.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { message } from '@tauri-apps/plugin-dialog'; -import { platform } from '@tauri-apps/plugin-os'; -import { relaunch } from '@tauri-apps/plugin-process'; -import Database from '@tauri-apps/plugin-sql'; -import { check } from '@tauri-apps/plugin-updater'; -import Markdown from 'markdown-to-jsx'; -import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react'; - -import { LumeStorage } from '@libs/storage/instance'; - -import { LoaderIcon } from '@shared/icons'; - -import { QUOTES } from '@utils/constants'; - -interface StorageContext { - db: LumeStorage; -} - -const StorageContext = createContext({ - db: undefined, -}); - -const StorageInstance = () => { - const [db, setDB] = useState(undefined); - const [isNewVersion, setIsNewVersion] = useState(false); - - const initLumeStorage = async () => { - try { - const sqlite = await Database.load('sqlite:lume_v2.db'); - const platformName = await platform(); - - const lumeStorage = new LumeStorage(sqlite, platformName); - if (!lumeStorage.account) await lumeStorage.getActiveAccount(); - - const settings = await lumeStorage.getAllSettings(); - let autoUpdater = false; - - if (settings) { - settings.forEach((item) => { - if (item.key === 'outbox') lumeStorage.settings.outbox = !!parseInt(item.value); - - if (item.key === 'media') lumeStorage.settings.media = !!parseInt(item.value); - - if (item.key === 'hashtag') - lumeStorage.settings.hashtag = !!parseInt(item.value); - - if (item.key === 'autoupdate') { - if (parseInt(item.value)) autoUpdater = true; - } - }); - } - - if (autoUpdater) { - // check update - const update = await check(); - // install new version - if (update) { - setIsNewVersion(true); - - await update.downloadAndInstall(); - await relaunch(); - } - } - - setDB(lumeStorage); - } catch (e) { - await message(`Cannot initialize database: ${e}`, { - title: 'Lume', - type: 'error', - }); - } - }; - - useEffect(() => { - if (!db) initLumeStorage(); - }, []); - - return { db, isNewVersion }; -}; - -const StorageProvider = ({ children }: PropsWithChildren) => { - const { db, isNewVersion } = StorageInstance(); - - if (!db) - return ( -
-
-
TIP:
- - {QUOTES[Math.floor(Math.random() * QUOTES.length)]} - -
-
- -

- {isNewVersion ? 'Found a new version, updating...' : 'Starting...'} -

-
-
- ); - - return {children}; -}; - -const useStorage = () => { - const context = useContext(StorageContext); - if (context === undefined) { - throw new Error('Storage not found'); - } - return context; -}; - -export { StorageProvider, useStorage }; diff --git a/src/shared/notes/repost.tsx b/src/shared/notes/repost.tsx index dbab01ae..f8d24907 100644 --- a/src/shared/notes/repost.tsx +++ b/src/shared/notes/repost.tsx @@ -15,24 +15,21 @@ import { User } from '@shared/user'; export function Repost({ event }: { event: NDKEvent }) { const { ark } = useArk(); - const { status, data: repostEvent } = useQuery({ + const { + isLoading, + isError, + data: repostEvent, + } = useQuery({ queryKey: ['repost', event.id], queryFn: async () => { try { - let event: NDKEvent = undefined; - if (event.content.length > 50) { const embed = JSON.parse(event.content) as NostrEvent; - event = ark.createNDKEvent({ event: embed }); + return ark.createNDKEvent({ event: embed }); } const id = event.tags.find((el) => el[0] === 'e')[1]; - if (!id) throw new Error('Failed to get repost event id'); - - event = await ark.getEventById({ id }); - - if (!event) return Promise.reject(new Error('Failed to get repost event')); - return event; + return await ark.getEventById({ id }); } catch { throw new Error('Failed to get repost event'); } @@ -54,7 +51,7 @@ export function Repost({ event }: { event: NDKEvent }) { } }; - if (status === 'pending') { + if (isLoading) { return (
@@ -62,6 +59,21 @@ export function Repost({ event }: { event: NDKEvent }) { ); } + if (isError) { + return ( +
+
+ +
+
+

Failed to load event

+
+
+
+
+ ); + } + return (
diff --git a/src/shared/widgets/newsfeed.tsx b/src/shared/widgets/newsfeed.tsx index 5ed78a79..d87f83c3 100644 --- a/src/shared/widgets/newsfeed.tsx +++ b/src/shared/widgets/newsfeed.tsx @@ -33,7 +33,9 @@ export function NewsfeedWidget() { const events = await ark.getInfiniteEvents({ filter: { kinds: [NDKKind.Text, NDKKind.Repost], - authors: ark.account.contacts, + authors: !ark.account.contacts.length + ? [ark.account.pubkey] + : ark.account.contacts, }, limit: FETCH_LIMIT, pageParam, diff --git a/src/shared/widgets/notification.tsx b/src/shared/widgets/notification.tsx index 14ef5d6e..d5fcbf46 100644 --- a/src/shared/widgets/notification.tsx +++ b/src/shared/widgets/notification.tsx @@ -1,11 +1,9 @@ -import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; +import { NDKEvent, NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk'; import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback, useEffect, useMemo } from 'react'; import { VList } from 'virtua'; import { useArk } from '@libs/ark'; -import { useNDK } from '@libs/ndk/provider'; -import { useStorage } from '@libs/storage/provider'; import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; import { MemoizedNotifyNote, NoteSkeleton } from '@shared/notes'; @@ -30,21 +28,17 @@ export function NotificationWidget() { signal: AbortSignal; pageParam: number; }) => { - const events = await fetcher.fetchLatestEvents( - relayUrls, - { + const events = await ark.getInfiniteEvents({ + filter: { kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap], - '#p': [db.account.pubkey], + '#p': [ark.account.pubkey], }, - FETCH_LIMIT, - { asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal } - ); - - const ndkEvents = events.map((event) => { - return new NDKEvent(ndk, event); + limit: FETCH_LIMIT, + pageParam, + signal, }); - return ndkEvents.sort((a, b) => b.created_at - a.created_at); + return events; }, getNextPageParam: (lastPage) => { const lastEvent = lastPage.at(-1); @@ -63,21 +57,24 @@ export function NotificationWidget() { ); const renderEvent = useCallback((event: NDKEvent) => { - if (event.pubkey === db.account.pubkey) return null; + if (event.pubkey === ark.account.pubkey) return null; return ; }, []); useEffect(() => { - if (status === 'success' && db.account) { + let sub: NDKSubscription = undefined; + + if (status === 'success' && ark.account) { const filter = { kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap], - '#p': [db.account.pubkey], + '#p': [ark.account.pubkey], since: Math.floor(Date.now() / 1000), }; - sub( + sub = ark.subscribe({ filter, - async (event) => { + closeOnEose: false, + cb: async (event) => { queryClient.setQueryData( ['notification'], (prev: { pageParams: number; pages: Array }) => ({ @@ -86,21 +83,18 @@ export function NotificationWidget() { }) ); - const user = ndk.getUser({ pubkey: event.pubkey }); - await user.fetchProfile(); + const profile = await ark.getUserProfile({ pubkey: event.pubkey }); switch (event.kind) { case NDKKind.Text: return await sendNativeNotification( - `${ - user.profile.displayName || user.profile.name - } has replied to your note` + `${profile.displayName || profile.name} has replied to your note` ); case NDKKind.EncryptedDirectMessage: { if (location.pathname !== '/chats') { return await sendNativeNotification( `${ - user.profile.displayName || user.profile.name + profile.displayName || profile.name } has send you a encrypted message` ); } else { @@ -109,28 +103,28 @@ export function NotificationWidget() { } case NDKKind.Repost: return await sendNativeNotification( - `${ - user.profile.displayName || user.profile.name - } has reposted to your note` + `${profile.displayName || profile.name} has reposted to your note` ); case NDKKind.Reaction: return await sendNativeNotification( - `${user.profile.displayName || user.profile.name} has reacted ${ + `${profile.displayName || profile.name} has reacted ${ event.content } to your note` ); case NDKKind.Zap: return await sendNativeNotification( - `${user.profile.displayName || user.profile.name} has zapped to your note` + `${profile.displayName || profile.name} has zapped to your note` ); default: break; } }, - false, - 'notification' - ); + }); } + + return () => { + if (sub) sub.stop(); + }; }, [status]); return (