fully support nip05

This commit is contained in:
Ren Amamiya 2023-06-30 16:36:03 +07:00
parent 1ba7f823cb
commit 332dbf608d
17 changed files with 250 additions and 245 deletions

View File

@ -15,7 +15,7 @@
"dependencies": { "dependencies": {
"@floating-ui/react": "^0.23.1", "@floating-ui/react": "^0.23.1",
"@headlessui/react": "^1.7.15", "@headlessui/react": "^1.7.15",
"@nostr-dev-kit/ndk": "^0.6.3", "@nostr-dev-kit/ndk": "0.6.0",
"@radix-ui/react-tooltip": "^1.0.6", "@radix-ui/react-tooltip": "^1.0.6",
"@tanstack/react-query": "^4.29.19", "@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "3.0.0-beta.54", "@tanstack/react-virtual": "3.0.0-beta.54",
@ -27,7 +27,7 @@
"get-urls": "^11.0.0", "get-urls": "^11.0.0",
"immer": "^10.0.2", "immer": "^10.0.2",
"light-bolt11-decoder": "^3.0.0", "light-bolt11-decoder": "^3.0.0",
"nostr-tools": "^1.12.0", "nostr-tools": "^1.12.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hook-form": "^7.45.1", "react-hook-form": "^7.45.1",

View File

@ -8,8 +8,8 @@ dependencies:
specifier: ^1.7.15 specifier: ^1.7.15
version: 1.7.15(react-dom@18.2.0)(react@18.2.0) version: 1.7.15(react-dom@18.2.0)(react@18.2.0)
'@nostr-dev-kit/ndk': '@nostr-dev-kit/ndk':
specifier: ^0.6.3 specifier: 0.6.0
version: 0.6.3(typescript@4.9.5) version: 0.6.0(typescript@4.9.5)
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.0.6 specifier: ^1.0.6
version: 1.0.6(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) version: 1.0.6(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
@ -44,8 +44,8 @@ dependencies:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
nostr-tools: nostr-tools:
specifier: ^1.12.0 specifier: ^1.12.1
version: 1.12.0 version: 1.12.1
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0 version: 18.2.0
@ -424,8 +424,8 @@ packages:
resolution: {integrity: sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==} resolution: {integrity: sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==}
dev: false dev: false
/@floating-ui/dom@1.4.2: /@floating-ui/dom@1.4.3:
resolution: {integrity: sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g==} resolution: {integrity: sha512-nB/68NyaQlcdY22L+Fgd1HERQ7UGv7XFN+tPxwrEfQL4nKtAP/jIZnZtpUlXbtV+VEGHh6W/63Gy2C5biWI3sA==}
dependencies: dependencies:
'@floating-ui/core': 1.3.1 '@floating-ui/core': 1.3.1
dev: false dev: false
@ -436,7 +436,7 @@ packages:
react: '>=16.8.0' react: '>=16.8.0'
react-dom: '>=16.8.0' react-dom: '>=16.8.0'
dependencies: dependencies:
'@floating-ui/dom': 1.4.2 '@floating-ui/dom': 1.4.3
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
@ -447,7 +447,7 @@ packages:
react: '>=16.8.0' react: '>=16.8.0'
react-dom: '>=16.8.0' react-dom: '>=16.8.0'
dependencies: dependencies:
'@floating-ui/dom': 1.4.2 '@floating-ui/dom': 1.4.3
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
@ -591,8 +591,8 @@ packages:
'@nodelib/fs.scandir': 2.1.5 '@nodelib/fs.scandir': 2.1.5
fastq: 1.15.0 fastq: 1.15.0
/@nostr-dev-kit/ndk@0.6.3(typescript@4.9.5): /@nostr-dev-kit/ndk@0.6.0(typescript@4.9.5):
resolution: {integrity: sha512-lXMahPRepqNmt7rIM/I7pJz/VB5PSCakZScV3+L0AmUpsN4UXHX+fFtkFjyfNpM/zW+YlxFMFbo/WM+jB55j0A==} resolution: {integrity: sha512-0ptE6OIZhFW+aRRIXAI8PvUIoVU6iQLpiwFtJj48XAUO2EC3WiSuqKLshjg6wj1bbo9qGs1PyFS9AUhUlWWJtg==}
dependencies: dependencies:
'@noble/hashes': 1.3.1 '@noble/hashes': 1.3.1
'@noble/secp256k1': 2.0.0 '@noble/secp256k1': 2.0.0
@ -605,11 +605,11 @@ packages:
eslint: 8.43.0 eslint: 8.43.0
eslint-config-prettier: 8.8.0(eslint@8.43.0) eslint-config-prettier: 8.8.0(eslint@8.43.0)
eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.60.1)(eslint@8.43.0) eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.60.1)(eslint@8.43.0)
esm-loader-typescript: 1.0.4 esm-loader-typescript: 1.0.5
eventemitter3: 5.0.1 eventemitter3: 5.0.1
light-bolt11-decoder: 3.0.0 light-bolt11-decoder: 3.0.0
node-fetch: 3.3.1 node-fetch: 3.3.1
nostr-tools: 1.12.0 nostr-tools: 1.12.1
tsd: 0.28.1 tsd: 0.28.1
utf8-buffer: 1.0.0 utf8-buffer: 1.0.0
websocket-polyfill: 0.0.3 websocket-polyfill: 0.0.3
@ -1071,8 +1071,8 @@ packages:
resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==}
dev: false dev: false
/@swc/core-darwin-arm64@1.3.66: /@swc/core-darwin-arm64@1.3.67:
resolution: {integrity: sha512-UijJsvuLy73vxeVYEy7urIHksXS+3BdvJ9s9AY+bRMSQW483NO7RLp8g4FdTyJbRaN0BH15SQnY0dcjQBkVuHw==} resolution: {integrity: sha512-zCT2mCkOBVNf5uJDcQ3A9KDoO1OEaGdfjsRTZTo7sejDd9AXLfJg+xgyCBBrK2jNS/uWcT21IvSv3LqKp4K8pA==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@ -1080,8 +1080,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-darwin-x64@1.3.66: /@swc/core-darwin-x64@1.3.67:
resolution: {integrity: sha512-xGsHKvViQnwTNLF30Y/5OqWdnN6RsiyUI8awZXfz1sHcXCEaLe+v+WLQ+/E8sgw0YUkYVHzzfV/sAN2CezJK5Q==} resolution: {integrity: sha512-hXTVsfTatPEec5gFVyjGj3NccKZsYj/OXyHn6XA+l3Q76lZzGm2ISHdku//XNwXu8OmJ0HhS7LPsC4XXwxXQhg==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@ -1089,8 +1089,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-arm-gnueabihf@1.3.66: /@swc/core-linux-arm-gnueabihf@1.3.67:
resolution: {integrity: sha512-gNbLcSIV2pq90BkMSpzvK4xPXOl8GEF3YR4NaqF0CYSzQsVXXTTqMuX/r26xNYudBKzH0345S1MpoRk2qricnA==} resolution: {integrity: sha512-l8AKL0RkDL5FRTeWMmjoz9zvAc37amxC+0rheaNwE+gZya7ObyNjnIYz5FwN+3y+z6JFU7LS2x/5f6iwruv6pg==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
@ -1098,8 +1098,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-arm64-gnu@1.3.66: /@swc/core-linux-arm64-gnu@1.3.67:
resolution: {integrity: sha512-cJSQ0oplyWbJqy4rzVcnBYLAi6z1QT3QCcR7iAey0aAmCvfRBZJfXlyjggMjn4iosuadkauwCZR1xYNhBDRn7w==} resolution: {integrity: sha512-S8zOB1AXEpb7kmtgMaFNeLAj01VOky4B0RNZ+uJWigdrDiFT67FeZzNHUNmNSOU0QM79G+Lie/xD/beqEw0vDg==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -1107,8 +1107,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-arm64-musl@1.3.66: /@swc/core-linux-arm64-musl@1.3.67:
resolution: {integrity: sha512-GDQZpcB9aGxG9PTA2shdIkoMZlGK5omJ8NR49uoBTtLBVYiGeXAwV0U1Uaw8kXEZj9i7wZDkvjzjSaNH3evRsg==} resolution: {integrity: sha512-Fex8J8ASrt13pmOr2xWh41tEeKWwXYGk3sV8L/aGHiYtIJEUi2f+RtMx3jp7LIdOD8pQptor7i5WBlfR9jhp8A==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -1116,8 +1116,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-x64-gnu@1.3.66: /@swc/core-linux-x64-gnu@1.3.67:
resolution: {integrity: sha512-lg8E4O/Pd9KfK0lajdinVMuGME8dSv7V9arhEpmlfGE2eXSDCWqDn5Htk5QVBstt9lt1lsRhWHJ/YYc2eQY30Q==} resolution: {integrity: sha512-9bz9/bMphrv5vDg0os/d8ve0QgFpDzJgZgHUaHiGwcmfnlgdOSAaYJLIvWdcGTjZuQeV4L0m+iru357D9TXEzA==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -1125,8 +1125,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-linux-x64-musl@1.3.66: /@swc/core-linux-x64-musl@1.3.67:
resolution: {integrity: sha512-lo8ZcAO/zL2pZWH+LZIyge8u2MklaeuT6+FpVVpBFktMVdYXbaVtzpvWbgRFBZHvL3SRDF+u8jxjtkXhvGUpTw==} resolution: {integrity: sha512-ED0H6oLvQmhgo9zs8usmEA/lcZPGTu7K9og9K871b7HhHX0h/R+Xg2pb5KD7S/GyUHpfuopxjVROm+h6X1jMUA==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -1134,8 +1134,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-win32-arm64-msvc@1.3.66: /@swc/core-win32-arm64-msvc@1.3.67:
resolution: {integrity: sha512-cQoVwBuJY5WkHbfpCOlndNwYr1ZThatRjQQvKy540NUIeAEk9Fa6ozlDBtU75UdaWKtUG6YQ/bWz+KTemheVxw==} resolution: {integrity: sha512-J1yFDLgPFeRtA8t5E159OXX+ww1gbkFg70yr4OP7EsOkOD1uMkuTf9yK/woHfsaVJlUYjJHzw7MkUIEgQBucqQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@ -1143,8 +1143,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-win32-ia32-msvc@1.3.66: /@swc/core-win32-ia32-msvc@1.3.67:
resolution: {integrity: sha512-y/FrAIINK4UBeUQQknGlWXEyjo+MBvjF7WkUf2KP7sNr9EHHy8+dXohAGd5Anz0eJrqOM1ZXR/GEjxRp7bGQ1Q==} resolution: {integrity: sha512-bK11/KtasewqHxzkjKUBXRE9MSAidbZCxrgJUd49bItG2N/DHxkwMYu8Xkh5VDHdTYWv/2idYtf/VM9Yi+53qw==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@ -1152,8 +1152,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core-win32-x64-msvc@1.3.66: /@swc/core-win32-x64-msvc@1.3.67:
resolution: {integrity: sha512-yI64ACzS14qFLrfyO12qW+f/UROTotzDeEbuyJAaPD2IZexoT1cICznI3sBmIfrSt33mVuW8eF5m3AG/NUImzw==} resolution: {integrity: sha512-GxzUU3+NA3cPcYxCxtfSQIS2ySD7Z8IZmKTVaWA9GOUQbKLyCE8H5js31u39+0op/1gNgxOgYFDoj2lUyvLCqw==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -1161,8 +1161,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@swc/core@1.3.66: /@swc/core@1.3.67:
resolution: {integrity: sha512-Hpf91kH5ly7fHkWnApwryTQryT+TO4kMMPH3WyciUSQOWLE3UuQz1PtETHQQk7PZ/b1QF0qQurJrgfBr5bSKUA==} resolution: {integrity: sha512-9DROjzfAEt0xt0CDkOYsWpkUPyne8fl5ggWGon049678BOM7p0R0dmaalZGAsKatG5vYP1IWSKWsKhJIubDCsQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
requiresBuild: true requiresBuild: true
peerDependencies: peerDependencies:
@ -1171,16 +1171,16 @@ packages:
'@swc/helpers': '@swc/helpers':
optional: true optional: true
optionalDependencies: optionalDependencies:
'@swc/core-darwin-arm64': 1.3.66 '@swc/core-darwin-arm64': 1.3.67
'@swc/core-darwin-x64': 1.3.66 '@swc/core-darwin-x64': 1.3.67
'@swc/core-linux-arm-gnueabihf': 1.3.66 '@swc/core-linux-arm-gnueabihf': 1.3.67
'@swc/core-linux-arm64-gnu': 1.3.66 '@swc/core-linux-arm64-gnu': 1.3.67
'@swc/core-linux-arm64-musl': 1.3.66 '@swc/core-linux-arm64-musl': 1.3.67
'@swc/core-linux-x64-gnu': 1.3.66 '@swc/core-linux-x64-gnu': 1.3.67
'@swc/core-linux-x64-musl': 1.3.66 '@swc/core-linux-x64-musl': 1.3.67
'@swc/core-win32-arm64-msvc': 1.3.66 '@swc/core-win32-arm64-msvc': 1.3.67
'@swc/core-win32-ia32-msvc': 1.3.66 '@swc/core-win32-ia32-msvc': 1.3.67
'@swc/core-win32-x64-msvc': 1.3.66 '@swc/core-win32-x64-msvc': 1.3.67
dev: true dev: true
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.2): /@tailwindcss/typography@0.5.9(tailwindcss@3.3.2):
@ -1551,7 +1551,7 @@ packages:
peerDependencies: peerDependencies:
vite: ^4 vite: ^4
dependencies: dependencies:
'@swc/core': 1.3.66 '@swc/core': 1.3.67
vite: 4.3.9(@types/node@18.16.18) vite: 4.3.9(@types/node@18.16.18)
transitivePeerDependencies: transitivePeerDependencies:
- '@swc/helpers' - '@swc/helpers'
@ -1796,7 +1796,7 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
caniuse-lite: 1.0.30001509 caniuse-lite: 1.0.30001509
electron-to-chromium: 1.4.444 electron-to-chromium: 1.4.446
node-releases: 2.0.12 node-releases: 2.0.12
update-browserslist-db: 1.0.11(browserslist@4.21.9) update-browserslist-db: 1.0.11(browserslist@4.21.9)
dev: true dev: true
@ -2227,8 +2227,8 @@ packages:
/eastasianwidth@0.2.0: /eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
/electron-to-chromium@1.4.444: /electron-to-chromium@1.4.446:
resolution: {integrity: sha512-/AjkL4syRvOpowXWy3N4OHmVbFdWQpfSKHh0sCVYipDeEAtMce3rLjMJi/27Ia9jNIbw6P1JuPN32pSWybXXEQ==} resolution: {integrity: sha512-4Gnw7ztEQ/E0eOt5JWfPn9jjeupfUlKoeW5ETKP9nLdWj+4spFoS3Stj19fqlKIaX28UQs0fNX+uKEyoLCBnkw==}
dev: true dev: true
/emoji-regex@8.0.0: /emoji-regex@8.0.0:
@ -2567,8 +2567,8 @@ packages:
- supports-color - supports-color
dev: false dev: false
/esm-loader-typescript@1.0.4: /esm-loader-typescript@1.0.5:
resolution: {integrity: sha512-ejo2f+NKIt23jaJKVpjcRQC0xTa6cTqt6VA+z8Ef/A/TCjQ5u7opPO1J4fJFeTiiHEo8JqU92EG4I1KwuXYZMg==} resolution: {integrity: sha512-BeHp2TrYbRL9fUttlyzPQJPTvLDBXXUli09UNoAr87WKi8jedcULlMteNZgl7DtFZ3ZE1Mmv74SwRgwJDWyc0A==}
dependencies: dependencies:
create-esm-loader: 0.2.3 create-esm-loader: 0.2.3
npm-run-all: 4.1.5 npm-run-all: 4.1.5
@ -3832,8 +3832,8 @@ packages:
engines: {node: '>=12.20'} engines: {node: '>=12.20'}
dev: false dev: false
/nostr-tools@1.12.0: /nostr-tools@1.12.1:
resolution: {integrity: sha512-fsIXaNJPKaSrO9MxsCEWbhI4tG4pToQK4D4sgLRD0fRDfZ6ocCg8CLlh9lcNx0o8pVErCMLVASxbJ+w4WNK0MA==} resolution: {integrity: sha512-ZeoV7g3jBUAlb4mKa3C+6hrc84htPkbebMShfGNgV4vAiz18e/sQukUBFL6vb/+sxZy+dBQFkRwsJIaVFs8Gfw==}
dependencies: dependencies:
'@noble/curves': 1.0.0 '@noble/curves': 1.0.0
'@noble/hashes': 1.3.0 '@noble/hashes': 1.3.0
@ -4455,8 +4455,8 @@ packages:
glob: 7.2.3 glob: 7.2.3
dev: false dev: false
/rollup@3.25.3: /rollup@3.26.0:
resolution: {integrity: sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==} resolution: {integrity: sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'} engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
optionalDependencies: optionalDependencies:
@ -5159,7 +5159,7 @@ packages:
vite: '>=2.8' vite: '>=2.8'
dependencies: dependencies:
'@rollup/plugin-virtual': 3.0.1 '@rollup/plugin-virtual': 3.0.1
'@swc/core': 1.3.66 '@swc/core': 1.3.67
uuid: 9.0.0 uuid: 9.0.0
vite: 4.3.9(@types/node@18.16.18) vite: 4.3.9(@types/node@18.16.18)
transitivePeerDependencies: transitivePeerDependencies:
@ -5212,7 +5212,7 @@ packages:
'@types/node': 18.16.18 '@types/node': 18.16.18
esbuild: 0.17.19 esbuild: 0.17.19
postcss: 8.4.24 postcss: 8.4.24
rollup: 3.25.3 rollup: 3.26.0
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2
dev: true dev: true

View File

@ -14,24 +14,6 @@ CREATE TABLE
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
); );
-- create plebs table
CREATE TABLE
plebs (
id INTEGER NOT NULL PRIMARY KEY,
npub TEXT NOT NULL UNIQUE,
name TEXT,
displayName TEXT,
image TEXT,
banner TEXT,
bio TEXT,
nip05 TEXT,
lud06 TEXT,
lud16 TEXT,
about TEXT,
zapService TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- create notes table -- create notes table
CREATE TABLE CREATE TABLE
notes ( notes (

View File

@ -20,7 +20,7 @@ export function ChatMessageForm({
const tags = [["p", receiverPubkey]]; const tags = [["p", receiverPubkey]];
// publish message // publish message
publish({ content: message, kind: 4, tags }); await publish({ content: message, kind: 4, tags });
// reset state // reset state
setValue(""); setValue("");

View File

@ -1,21 +1,17 @@
import { User } from "@app/auth/components/user";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { getPlebs } from "@libs/storage";
import { CancelIcon, PlusIcon } from "@shared/icons"; import { CancelIcon, PlusIcon } from "@shared/icons";
import { DEFAULT_AVATAR } from "@stores/constants"; import { useAccount } from "@utils/hooks/useAccount";
import { useQuery } from "@tanstack/react-query";
import { nip19 } from "nostr-tools";
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
export function NewMessageModal() { export function NewMessageModal() {
const navigate = useNavigate(); const navigate = useNavigate();
const { status, data }: any = useQuery(["plebs"], async () => {
return await getPlebs();
});
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { status, account } = useAccount();
const follows = account ? JSON.parse(account.follows) : [];
const closeModal = () => { const closeModal = () => {
setIsOpen(false); setIsOpen(false);
}; };
@ -24,8 +20,7 @@ export function NewMessageModal() {
setIsOpen(true); setIsOpen(true);
}; };
const openChat = (npub: string) => { const openChat = (pubkey: string) => {
const pubkey = nip19.decode(npub).data;
closeModal(); closeModal();
navigate(`/app/chat/${pubkey}`); navigate(`/app/chat/${pubkey}`);
}; };
@ -99,31 +94,16 @@ export function NewMessageModal() {
{status === "loading" ? ( {status === "loading" ? (
<p>Loading...</p> <p>Loading...</p>
) : ( ) : (
data.map((pleb) => ( follows.map((follow) => (
<div <div
key={pleb.npub} key={follow}
className="group flex items-center justify-between px-4 py-3 hover:bg-zinc-800" className="group flex items-center justify-between px-4 py-3 hover:bg-zinc-800"
> >
<div className="flex items-center gap-2"> <User pubkey={follow} />
<img
alt={pleb.npub}
src={pleb.image || DEFAULT_AVATAR}
className="w-9 h-9 shrink-0 object-cover rounded"
/>
<div className="inline-flex flex-col gap-1">
<h3 className="leading-none max-w-[15rem] line-clamp-1 font-medium text-zinc-100">
{pleb.displayName || pleb.name}
</h3>
<span className="leading-none max-w-[10rem] line-clamp-1 text-sm text-zinc-400">
{pleb.nip05 ||
pleb.npub.substring(0, 16).concat("...")}
</span>
</div>
</div>
<div> <div>
<button <button
type="button" type="button"
onClick={() => openChat(pleb.npub)} onClick={() => openChat(follow)}
className="inline-flex text-sm w-max px-3 py-1.5 rounded border-t border-zinc-600/50 bg-zinc-700 hover:bg-fuchsia-500 transform translate-x-20 group-hover:translate-x-0 transition-transform ease-in-out duration-150" className="inline-flex text-sm w-max px-3 py-1.5 rounded border-t border-zinc-600/50 bg-zinc-700 hover:bg-fuchsia-500 transform translate-x-20 group-hover:translate-x-0 transition-transform ease-in-out duration-150"
> >
Chat Chat

View File

@ -2,18 +2,11 @@ import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants"; import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile"; import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey"; import { shortenKey } from "@utils/shortenKey";
import { nip19 } from "nostr-tools"; import { Link } from "react-router-dom";
import { useNavigate } from "react-router-dom";
export function ChatSidebar({ pubkey }: { pubkey: string }) { export function ChatSidebar({ pubkey }: { pubkey: string }) {
const navigate = useNavigate();
const { user } = useProfile(pubkey); const { user } = useProfile(pubkey);
const viewProfile = () => {
const pubkey = nip19.decode(user.npub).data;
navigate(`/app/user/${pubkey}`);
};
return ( return (
<div className="px-3 py-2"> <div className="px-3 py-2">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
@ -36,13 +29,12 @@ export function ChatSidebar({ pubkey }: { pubkey: string }) {
</div> </div>
<div> <div>
<p className="leading-tight">{user?.bio || user?.about}</p> <p className="leading-tight">{user?.bio || user?.about}</p>
<button <Link
type="button" to={`/app/user/${pubkey}`}
onClick={() => viewProfile()}
className="mt-3 inline-flex w-full h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-zinc-800 text-sm text-zinc-300 hover:text-zinc-100 font-medium" className="mt-3 inline-flex w-full h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-zinc-800 text-sm text-zinc-300 hover:text-zinc-100 font-medium"
> >
View full profile View full profile
</button> </Link>
</div> </div>
</div> </div>
</div> </div>

View File

@ -6,6 +6,7 @@ import {
createNote, createNote,
getChannels, getChannels,
getLastLogin, getLastLogin,
updateLastLogin,
} from "@libs/storage"; } from "@libs/storage";
import { NDKFilter } from "@nostr-dev-kit/ndk"; import { NDKFilter } from "@nostr-dev-kit/ndk";
import { LoaderIcon, LumeIcon } from "@shared/icons"; import { LoaderIcon, LumeIcon } from "@shared/icons";
@ -142,6 +143,7 @@ export function Root() {
const chats = await fetchChats(); const chats = await fetchChats();
// const channels = await fetchChannelMessages(); // const channels = await fetchChannelMessages();
if (chats) { if (chats) {
await updateLastLogin(dateToUnix());
navigate("/app/space", { replace: true }); navigate("/app/space", { replace: true });
} }
} }

View File

@ -11,7 +11,6 @@ import { useVirtualizer } from "@tanstack/react-virtual";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
const ITEM_PER_PAGE = 10; const ITEM_PER_PAGE = 10;
const TIME = Math.floor(Date.now() / 1000);
export function FeedBlock({ params }: { params: any }) { export function FeedBlock({ params }: { params: any }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -21,7 +20,6 @@ export function FeedBlock({ params }: { params: any }) {
queryFn: async ({ pageParam = 0 }) => { queryFn: async ({ pageParam = 0 }) => {
return await getNotesByAuthors( return await getNotesByAuthors(
params.content, params.content,
TIME,
ITEM_PER_PAGE, ITEM_PER_PAGE,
pageParam, pageParam,
); );

View File

@ -9,7 +9,6 @@ import { useVirtualizer } from "@tanstack/react-virtual";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
const ITEM_PER_PAGE = 10; const ITEM_PER_PAGE = 10;
const TIME = Math.floor(Date.now() / 1000);
export function FollowingBlock({ block }: { block: number }) { export function FollowingBlock({ block }: { block: number }) {
// subscribe for live update // subscribe for live update
@ -29,7 +28,7 @@ export function FollowingBlock({ block }: { block: number }) {
}: any = useInfiniteQuery({ }: any = useInfiniteQuery({
queryKey: ["newsfeed-circle"], queryKey: ["newsfeed-circle"],
queryFn: async ({ pageParam = 0 }) => { queryFn: async ({ pageParam = 0 }) => {
return await getNotes(TIME, ITEM_PER_PAGE, pageParam); return await getNotes(ITEM_PER_PAGE, pageParam);
}, },
getNextPageParam: (lastPage) => lastPage.nextCursor, getNextPageParam: (lastPage) => lastPage.nextCursor,
}); });
@ -64,7 +63,7 @@ export function FollowingBlock({ block }: { block: number }) {
const refreshFirstPage = () => { const refreshFirstPage = () => {
// refetch // refetch
refetch({ refetchPage: (page, index) => index === 0 }); refetch({ refetchPage: (_, index: number) => index === 0 });
// scroll to top // scroll to top
rowVirtualizer.scrollToIndex(1); rowVirtualizer.scrollToIndex(1);
// stop notify // stop notify

View File

@ -1,4 +1,3 @@
import { createReplyNote } from "./storage";
import NDK, { import NDK, {
NDKConstructorParams, NDKConstructorParams,
NDKEvent, NDKEvent,
@ -15,21 +14,10 @@ export async function initNDK(relays?: string[]): Promise<NDK> {
const opts: NDKConstructorParams = {}; const opts: NDKConstructorParams = {};
const defaultRelays = new Set(relays || FULL_RELAYS); const defaultRelays = new Set(relays || FULL_RELAYS);
/*
for (const relay of defaultRelays) {
const url = new URL(relay);
url.protocol = url.protocol = url.protocol.replace("wss", "https");
const res = await fetch(url.href, { method: "HEAD", timeout: 5 });
if (!res.ok) {
defaultRelays.delete(relay);
}
}
*/
opts.explicitRelayUrls = [...defaultRelays]; opts.explicitRelayUrls = [...defaultRelays];
const ndk = new NDK(opts); const ndk = new NDK(opts);
await ndk.connect(); await ndk.connect(500);
return ndk; return ndk;
} }
@ -57,15 +45,10 @@ export async function prefetchEvents(
} }
export function usePublish() { export function usePublish() {
const { account } = useAccount();
const ndk = useContext(RelayContext); const ndk = useContext(RelayContext);
const { account } = useAccount();
if (!ndk.signer) { const publish = async ({
const signer = new NDKPrivateKeySigner(account?.privkey);
ndk.signer = signer;
}
const publish = ({
content, content,
kind, kind,
tags, tags,
@ -73,8 +56,9 @@ export function usePublish() {
content: string; content: string;
kind: NDKKind; kind: NDKKind;
tags: string[][]; tags: string[][];
}): NDKEvent => { }): Promise<NDKEvent> => {
const event = new NDKEvent(ndk); const event = new NDKEvent(ndk);
const signer = new NDKPrivateKeySigner(account.privkey);
event.content = content; event.content = content;
event.kind = kind; event.kind = kind;
@ -82,7 +66,8 @@ export function usePublish() {
event.pubkey = account.pubkey; event.pubkey = account.pubkey;
event.tags = tags; event.tags = tags;
event.publish(); await event.sign(signer);
await event.publish();
return event; return event;
}; };

View File

@ -1,6 +1,4 @@
import { NDKTag, NDKUserProfile } from "@nostr-dev-kit/ndk";
import { getParentID } from "@utils/transform"; import { getParentID } from "@utils/transform";
import { nip19 } from "nostr-tools";
import Database from "tauri-plugin-sql-api"; import Database from "tauri-plugin-sql-api";
let db: null | Database = null; let db: null | Database = null;
@ -73,55 +71,6 @@ export async function updateAccount(
); );
} }
// get all plebs
export async function getPlebs() {
const db = await connect();
return await db.select("SELECT * FROM plebs ORDER BY created_at DESC;");
}
// get pleb by pubkey
export async function getPleb(npub: string) {
const db = await connect();
const result = await db.select(`SELECT * FROM plebs WHERE npub = "${npub}";`);
if (result) {
return result[0];
} else {
return null;
}
}
// create pleb
export async function createPleb(key: string, data: NDKUserProfile) {
const db = await connect();
const now = Math.floor(Date.now() / 1000);
let npub: string;
if (key.substring(0, 4) === "npub") {
npub = key;
} else {
npub = nip19.npubEncode(key);
}
return await db.execute(
"INSERT OR REPLACE INTO plebs (npub, name, displayName, image, banner, bio, nip05, lud06, lud16, about, zapService, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
[
npub,
data.name,
data.displayName,
data.image,
data.banner,
data.bio,
data.nip05,
data.lud06,
data.lud16,
data.about,
data.zapService,
now,
],
);
}
// count total notes // count total notes
export async function countTotalChannels() { export async function countTotalChannels() {
const db = await connect(); const db = await connect();
@ -139,14 +88,14 @@ export async function countTotalNotes() {
} }
// get all notes // get all notes
export async function getNotes(time: number, limit: number, offset: number) { export async function getNotes(limit: number, offset: number) {
const db = await connect(); const db = await connect();
const totalNotes = await countTotalNotes(); const totalNotes = await countTotalNotes();
const nextCursor = offset + limit; const nextCursor = offset + limit;
const notes: any = { data: null, nextCursor: 0 }; const notes: any = { data: null, nextCursor: 0 };
const query: any = await db.select( const query: any = await db.select(
`SELECT * FROM notes WHERE created_at <= "${time}" AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`, `SELECT * FROM notes WHERE kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
); );
notes["data"] = query; notes["data"] = query;
@ -169,7 +118,6 @@ export async function getNotesByPubkey(pubkey: string) {
// get all notes by authors // get all notes by authors
export async function getNotesByAuthors( export async function getNotesByAuthors(
authors: string, authors: string,
time: number,
limit: number, limit: number,
offset: number, offset: number,
) { ) {
@ -181,7 +129,7 @@ export async function getNotesByAuthors(
const notes: any = { data: null, nextCursor: 0 }; const notes: any = { data: null, nextCursor: 0 };
const query: any = await db.select( const query: any = await db.select(
`SELECT * FROM notes WHERE created_at <= "${time}" AND pubkey IN (${finalArray}) AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`, `SELECT * FROM notes WHERE pubkey IN (${finalArray}) AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
); );
notes["data"] = query; notes["data"] = query;

View File

@ -97,7 +97,7 @@ export function Post() {
const refID = getRef(); const refID = getRef();
const submit = () => { const submit = async () => {
let tags: string[][] = []; let tags: string[][] = [];
let kind: number; let kind: number;
@ -130,7 +130,7 @@ export function Post() {
const serializedContent = serialize(content); const serializedContent = serialize(content);
// publish message // publish message
publish({ content: serializedContent, kind, tags }); await publish({ content: serializedContent, kind, tags });
// close modal // close modal
toggle(false); toggle(false);

View File

@ -1,38 +1,51 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { usePublish } from "@libs/ndk"; import { usePublish } from "@libs/ndk";
import { getPleb } from "@libs/storage"; import { NDKEvent } from "@nostr-dev-kit/ndk";
import { AvatarUploader } from "@shared/avatarUploader"; import { AvatarUploader } from "@shared/avatarUploader";
import { BannerUploader } from "@shared/bannerUploader"; import { BannerUploader } from "@shared/bannerUploader";
import { CancelIcon, LoaderIcon } from "@shared/icons"; import {
CancelIcon,
CheckCircleIcon,
LoaderIcon,
UnverifiedIcon,
} from "@shared/icons";
import { Image } from "@shared/image"; import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants"; import { DEFAULT_AVATAR } from "@stores/constants";
import { useQueryClient } from "@tanstack/react-query";
import { fetch } from "@tauri-apps/api/http";
import { useAccount } from "@utils/hooks/useAccount"; import { useAccount } from "@utils/hooks/useAccount";
import { Fragment, useState } from "react"; import { Fragment, useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
export function EditProfileModal() { export function EditProfileModal() {
const queryClient = useQueryClient();
const publish = usePublish(); const publish = usePublish();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [picture, setPicture] = useState(DEFAULT_AVATAR); const [picture, setPicture] = useState(DEFAULT_AVATAR);
const [banner, setBanner] = useState(null); const [banner, setBanner] = useState("");
const [nip05, setNIP05] = useState({ verified: false, text: "" });
const { account } = useAccount(); const { account } = useAccount();
const { const {
register, register,
handleSubmit, handleSubmit,
reset, reset,
formState: { isValid }, setError,
formState: { isValid, errors },
} = useForm({ } = useForm({
defaultValues: async () => { defaultValues: async () => {
const res = await getPleb(account.npub); const res: any = queryClient.getQueryData(["user", account.pubkey]);
if (res.picture) { if (res.image) {
setPicture(res.image); setPicture(res.image);
} }
if (res.banner) { if (res.banner) {
setBanner(res.banner); setBanner(res.banner);
} }
if (res.nip05) {
setNIP05((prev) => ({ ...prev, text: res.nip05 }));
}
return res; return res;
}, },
}); });
@ -45,24 +58,72 @@ export function EditProfileModal() {
setIsOpen(true); setIsOpen(true);
}; };
const onSubmit = (data: any) => { const verifyNIP05 = async (data: string) => {
if (data) {
const url = data.split("@");
const username = url[0];
const service = url[1];
const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`;
const res: any = await fetch(verifyURL, {
method: "GET",
timeout: 30,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
});
if (!res.ok) return false;
if (res.data.names[username] === account.pubkey) {
setNIP05((prev) => ({ ...prev, verified: true }));
return true;
} else {
return false;
}
}
};
const onSubmit = async (data: any) => {
// start loading // start loading
setLoading(true); setLoading(true);
// publish let event: NDKEvent;
const event = publish({
content: JSON.stringify({ const content = {
...data, ...data,
username: data.name,
display_name: data.name, display_name: data.name,
bio: data.about, bio: data.about,
image: data.picture, image: data.picture,
}), };
if (data.nip05) {
const verify = await verifyNIP05(data.nip05);
if (verify) {
event = await publish({
content: JSON.stringify({ ...content, nip05: data.nip05 }),
kind: 0, kind: 0,
tags: [], tags: [],
}); });
} else {
setNIP05((prev) => ({ ...prev, verified: false }));
setError("nip05", {
type: "manual",
message: "Can't verify your Lume ID / NIP-05, please check again",
});
}
} else {
event = await publish({
content: JSON.stringify(content),
kind: 0,
tags: [],
});
}
if (event) { if (event.id) {
setTimeout(() => { setTimeout(() => {
// invalid cache
queryClient.invalidateQueries(["user", account.pubkey]);
// reset form // reset form
reset(); reset();
// reset state // reset state
@ -71,9 +132,17 @@ export function EditProfileModal() {
setPicture(DEFAULT_AVATAR); setPicture(DEFAULT_AVATAR);
setBanner(null); setBanner(null);
}, 1200); }, 1200);
} else {
setLoading(false);
} }
}; };
useEffect(() => {
if (!nip05.verified && /\S+@\S+\.\S+/.test(nip05.text)) {
verifyNIP05(nip05.text);
}
}, [nip05.text]);
return ( return (
<> <>
<button <button
@ -179,6 +248,39 @@ export function EditProfileModal() {
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500" className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
/> />
</div> </div>
<div className="flex flex-col gap-1">
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
Lume ID / NIP-05
</label>
<div className="relative">
<input
{...register("nip05", {
required: true,
minLength: 4,
})}
spellCheck={false}
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
/>
<div className="absolute top-1/2 right-2 transform -translate-y-1/2">
{nip05.verified ? (
<span className="inline-flex items-center gap-1 rounded h-6 px-2 bg-green-500 text-sm font-medium">
<CheckCircleIcon className="w-4 h-4 text-white" />
Verified
</span>
) : (
<span className="inline-flex items-center gap-1 rounded h-6 px-2 bg-red-500 text-sm font-medium">
<UnverifiedIcon className="w-4 h-4 text-white" />
Unverified
</span>
)}
</div>
{errors.nip05 && (
<p className="mt-1 text-sm text-red-400">
{errors.nip05.message.toString()}
</p>
)}
</div>
</div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400"> <label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
Bio Bio

View File

@ -38,4 +38,5 @@ export * from "./empty";
export * from "./cmd"; export * from "./cmd";
export * from "./verticalDots"; export * from "./verticalDots";
export * from "./signal"; export * from "./signal";
export * from "./unverified";
// @endindex // @endindex

View File

@ -0,0 +1,23 @@
import { SVGProps } from "react";
export function UnverifiedIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
fill="currentColor"
fillRule="evenodd"
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12zm7.53-3.53a.75.75 0 00-1.06 1.06L10.94 12l-2.47 2.47a.75.75 0 101.06 1.06L12 13.06l2.47 2.47a.75.75 0 101.06-1.06L13.06 12l2.47-2.47a.75.75 0 00-1.06-1.06L12 10.94 9.53 8.47z"
clipRule="evenodd"
/>
</svg>
);
}

View File

@ -1,38 +1,28 @@
import { createPleb, getPleb } from "@libs/storage";
import { RelayContext } from "@shared/relayProvider"; import { RelayContext } from "@shared/relayProvider";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { nip19 } from "nostr-tools";
import { useContext } from "react"; import { useContext } from "react";
export function useProfile(id: string) { export function useProfile(pubkey: string) {
const ndk = useContext(RelayContext); const ndk = useContext(RelayContext);
const { const {
status, status,
data: user, data: user,
error, error,
isFetching, isFetching,
} = useQuery(["user", id], async () => { } = useQuery(
let npub: string; ["user", pubkey],
async () => {
if (id.substring(0, 4) === "npub") { const user = ndk.getUser({ hexpubkey: pubkey });
npub = id;
} else {
npub = nip19.npubEncode(id);
}
const current = Math.floor(Date.now() / 1000);
const result = await getPleb(npub);
if (result && parseInt(result.created_at) + 86400 >= current) {
return result;
} else {
const user = ndk.getUser({ npub });
await user.fetchProfile(); await user.fetchProfile();
await createPleb(id, user.profile);
return user.profile; return user.profile;
} },
}); {
staleTime: Infinity,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
},
);
return { status, user, error, isFetching }; return { status, user, error, isFetching };
} }

View File

@ -24,6 +24,9 @@ export function useSocial() {
}, },
{ {
enabled: account ? true : false, enabled: account ? true : false,
refetchOnReconnect: false,
refetchOnMount: false,
refetchOnWindowFocus: false,
}, },
); );