Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cooler useProfiles with localstorage #21

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

temp
14 changes: 6 additions & 8 deletions components/LandingLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Link from "next/link";
import React from "react";
import { Comfortaa, Source_Sans_Pro } from "next/font/google";
import { Bars3Icon } from "@heroicons/react/24/solid";
import { useProfile } from "nostr-react";
import useProfile from "@/hooks/useProfile";
import { usePubkey } from "@/context/pubkey";
import { nip19 } from "nostr-tools";

Expand All @@ -14,21 +14,19 @@ interface LandingLayoutProps {
}

const PubkeyNavMenu = ({ pubkey }: { pubkey: string }) => {
const { data: userData, isLoading } = useProfile({
pubkey,
});
const [profile, isLoading] = useProfile(pubkey)

return (
<>
{!isLoading && (
<li>
{userData?.name
? userData.name
{profile?.name
? profile.name
: nip19.npubEncode(pubkey).slice(0, 12)}
</li>
)}
{userData?.picture ? (
<img src={userData.picture} className="w-8 h-8 rounded-[50%]" />
{profile?.picture ? (
<img src={profile.picture} className="w-8 h-8 rounded-[50%]" />
) : (
<li>
<div className="w-8 h-8 rounded-[50%] bg-gray-700" />
Expand Down
44 changes: 44 additions & 0 deletions hooks/useProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect } from "react"
import { nostrClient } from "@/lib/nostr"
import dexieDb from "@/store/dexieDb"
import { useLiveQuery } from 'dexie-react-hooks'

export interface UserMetadata {
name?: string
pubkey?: string
npub?: string
display_name?: string
picture?: string
about?: string
website?: string
banner?: string
lud06?: string
lud16?: string
nip05?: string
}

export default function useProfile(pubkey: string | null): [UserMetadata | undefined, boolean] {
const [profile, isLoading] = useLiveQuery(async () => {
if (!pubkey) return [undefined, false]

const ret = await dexieDb.users.get(pubkey)
console.debug('live query res: ', ret)

return [ret, false]
},
[pubkey],
[undefined, true] // default result returned on initial render.
)

useEffect(() => {
if (!pubkey) return

nostrClient.addProfileToFetch(pubkey)

return () => {
nostrClient.removeProfileToFetch(pubkey)
}
}, [pubkey])

return [profile, isLoading]
}
90 changes: 90 additions & 0 deletions lib/nostr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { SimplePool, Filter, nip19, Event } from "nostr-tools";
import dexieDb from '@/store/dexieDb'

export interface UserMetadata {
name?: string
display_name?: string
picture?: string
about?: string
website?: string
banner?: string
lud06?: string
lud16?: string
nip05?: string
}

export type UserMetadataStore = UserMetadata & {
pubkey: string
npub: string
created_at: number
}

const defaultProfileRelays = [
"wss://nostr.terminus.money",
"wss://brb.io",
"wss://nostr.wine",
"wss://relay.snort.social",
"wss://gratten.duckdns.org/nostrrelay/relay2",
]

class NostrClient {
pool = new SimplePool()
profileQueue: Set<string> = new Set() // set of hex public keys to query
paused: boolean = false

addProfileToFetch(pubkey: string) {
this.profileQueue.add(pubkey)
this._fetchPubkeys()
}

removeProfileToFetch(pubkey: string) {
this.profileQueue.delete(pubkey)
}
_fetchPubkeys() {
if (this.paused || this.profileQueue.size === 0) return

const filters: Filter[] = [
{
kinds: [0],
authors: Array.from(this.profileQueue),
},
]

console.debug('subscribing for pubkeys: ', Array.from(this.profileQueue))

const sub = this.pool.sub(defaultProfileRelays, filters)

sub.on('event', async (event: Event) => {
console.debug('got event', event)

const metadataToStore: UserMetadataStore = {
...JSON.parse(event.content),
pubkey: event.pubkey,
npub: nip19.npubEncode(event.pubkey),
created_at: event.created_at
}

const existingProfile = await dexieDb.users.get(metadataToStore.pubkey)

if (!existingProfile || (metadataToStore.created_at > existingProfile.created_at)) {
console.debug('storing profile metadata: ', metadataToStore)
await dexieDb.users.put(metadataToStore)
}
})


this.profileQueue.forEach(pubkey => this.profileQueue.delete(pubkey))

// limit amount of subs to one per 500ms
this.paused = true
setTimeout(() => {
this.paused = false

// call it again in case new keys came in...
this._fetchPubkeys()
}, 500)
}

}

export const nostrClient = new NostrClient()
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
"@types/node": "18.15.3",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"dexie": "^3.2.3",
"dexie-react-hooks": "^1.1.3",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"next": "13.2.4",
"nodemon": "^2.0.21",
"nostr-react": "^0.6.4",
"passport-lnurl-auth": "^1.5.1",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down
14 changes: 0 additions & 14 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import { NostrProvider } from "nostr-react";
import { PubkeyProvider } from "@/context/pubkey";
import "@/styles/globals.css";
import type { AppProps } from "next/app";

// TODO: Save default relays in store, allow user to set and remove, retrieve user
// relays in a smart way based off who they are interacting with
// ...or just leave as fixed for hackathon (we have custom relay anyways)
const relays = [
"wss://nostr.terminus.money",
"wss://brb.io",
"wss://nostr.wine",
"wss://relay.snort.social",
"wss://gratten.duckdns.org/nostrrelay/relay2",
];

export default function App({ Component, pageProps }: AppProps) {
return (
<NostrProvider relayUrls={relays} debug={true}>
<PubkeyProvider>
<Component {...pageProps} />
</PubkeyProvider>
</NostrProvider>
);
}
63 changes: 63 additions & 0 deletions pages/testProfiles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import useProfile from "@/hooks/useProfile"
import { nip19 } from "nostr-tools"

// profiles from nostr.directory
const pubkeys = [
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
"82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2",
"00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700",
"04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9",
"6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93",
"1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
"3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24",
"7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194",
"84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240",
"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9",
"85080d3bad70ccdcd7f74c29a44f55bb85cbcd3dd0cbb957da1d215bdb931204",
"5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e",
"c48e29f04b482cc01ca1f9ef8c86ef8318c059e0e9353235162f080f26e14c11",
"bf2376e17ba4ec269d10fcc996a4746b451152be9031fa48e74553dde5526bce",
"c43bbb58e2e6bc2f9455758257f6ba5329107bd4e8274068c2936c69d9980b7d",
"d307643547703537dfdef811c3dea96f1f9e84c8249e200353425924a9908cf8",
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
"803a613997a26e8714116f99aa1f98e8589cb6116e1aaa1fc9c389984fcd9bb8",
"c49d52a573366792b9a6e4851587c28042fb24fa5625c6d67b8c95c8751aca15",
"92de68b21302fa2137b1cbba7259b8ba967b535a05c6d2b0847d9f35ff3cf56a",
"eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f",
"c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0",
"f728d9e6e7048358e70930f5ca64b097770d989ccd86854fe618eda9c8a38106",
]

const Profile = ({pubkey}:{pubkey: string}) => {
const [profile, isLoading] = useProfile(pubkey)
return (
<div className="flex flex-col">
{/* Image */}
{profile?.picture ? (
<img src={profile.picture} className="w-8 h-8 rounded-[50%]" />
) : (
<div className="w-8 h-8 rounded-[50%] bg-gray-700" />
)}

{/* Name */}
{!isLoading && (
<p>
{profile?.name
? profile.name
: nip19.npubEncode(pubkey).slice(0, 12)}
</p>
)}
</div>
)
}

export default function Test() {
return (
<div className="grid grid-cols-12">
{pubkeys.map(pk => {
return <Profile pubkey={pk} />
})}
</div>
)
}
37 changes: 37 additions & 0 deletions store/dexieDb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Dexie, { Table } from 'dexie'

interface UserMetadata {
name?: string
display_name?: string
picture?: string
about?: string
website?: string
banner?: string
lud06?: string
lud16?: string
nip05?: string
}

type UserMetadataStore = UserMetadata & {
pubkey?: string
npub?: string
created_at: number
}

class DexieDB extends Dexie {
users!: Table<UserMetadataStore>

constructor() {
super('DexieDB')
//Writing this because there have been some issues on github where people index images or movies
// without really understanding the purpose of indexing fields.
// A rule of thumb: Are you going to put your property in a where(‘…’) clause?
// If yes, index it, if not, dont. Large indexes will affect database performance and in
// extreme cases make it unstable.
this.version(1).stores({
users: '++pubkey, name, npub, nip05', // Primary key and indexed props
})
}
}

export default new DexieDB()
25 changes: 11 additions & 14 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,16 @@ detective@^5.2.1:
defined "^1.0.0"
minimist "^1.2.6"

dexie-react-hooks@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/dexie-react-hooks/-/dexie-react-hooks-1.1.3.tgz#dfcd723d533172605f06335823b205adf7442cc6"
integrity sha512-bXXE1gfYtfuVYTNiOlyam+YVaO8KaqacgRuxFuP37YtpS6o/jxT6KOl5h+hhqY36s0UavlHWbL+HWJFMcQumIg==

dexie@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.3.tgz#f35c91ca797599df8e771b998e9ae9669c877f8c"
integrity sha512-iHayBd4UYryDCVUNa3PMsJMEnd8yjyh5p7a+RFeC8i8n476BC9wMhVvqiImq5zJZJf5Tuer+s4SSj+AA3x+ZbQ==

didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz"
Expand Down Expand Up @@ -1967,11 +1977,6 @@ isexe@^2.0.0:
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==

jotai@^1.12.1:
version "1.13.1"
resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.13.1.tgz#20cc46454cbb39096b12fddfa635b873b3668236"
integrity sha512-RUmH1S4vLsG3V6fbGlKzGJnLrDcC/HNb5gH2AeA9DzuJknoVxSGvvg8OBB7lke+gDc4oXmdVsaKn/xDUhWZ0vw==

js-sdsl@^4.1.4:
version "4.3.0"
resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz"
Expand Down Expand Up @@ -2293,15 +2298,7 @@ normalize-range@^0.1.2:
resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==

nostr-react@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/nostr-react/-/nostr-react-0.6.4.tgz#03c15f6ac4807efdb3ad3c181457353bedf30d28"
integrity sha512-esRgmhTP5kPQ8ufs8cFAQxxJtMmzuba/k2QfXevG/ejHP3IMa41pb82qi8V0aPzY3KJ0Nr54x0OSa39d2InKzA==
dependencies:
jotai "^1.12.1"
nostr-tools "^1.1.0"

nostr-tools@^1.1.0, nostr-tools@^1.7.5:
nostr-tools@^1.7.5:
version "1.7.5"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.7.5.tgz#349f469ff2877deb99d71c63d4883af93ec9f9a5"
integrity sha512-FFaYOAn9lFyISClbBzPe2eQ2ZiKx8xFviwHmdgTAmuue+eLrtPEI3tCqPtP624HghX/X4VnaixoiMvDB8g2+tQ==
Expand Down