Skip to content

Commit d348be8

Browse files
authored
Merge pull request #7 from threenine/develop
Strip out NDK
2 parents da82092 + 1cb2968 commit d348be8

File tree

12 files changed

+241
-147
lines changed

12 files changed

+241
-147
lines changed

.junie/guidelines.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* vue
2323
* Typescript
2424
* nostr-tools
25-
* NDK
25+
2626

2727
# Unit Test
2828
* vitest

nuxt.config.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,4 @@ export default defineNuxtConfig({
77
'@nuxt/ui',
88
],
99
compatibilityDate: '2025-08-19',
10-
vite: {
11-
optimizeDeps: {
12-
include: ['tseep'],
13-
},
14-
},
1510
})

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
3535
},
3636
"dependencies": {
37-
"@nostr-dev-kit/ndk": "^2.18.1",
38-
"defu": "^6.1.4"
37+
"defu": "^6.1.4",
38+
"nostr-tools": "^2.19.4"
3939
},
4040
"devDependencies": {
4141
"@nuxt/devtools": "^2.6.2",

src/module.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineNuxtModule, addPlugin, addComponent, addImports, createResolver } from '@nuxt/kit'
1+
import { addComponent, addImports, addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
22
import { defu } from 'defu'
33

44
// Module options TypeScript interface definition
@@ -15,27 +15,27 @@ export default defineNuxtModule<ModuleOptions>({
1515
},
1616
// Default configuration options of the Nuxt module
1717
defaults: {
18-
relays: ['wss://relay.damus.io', 'wss://purplepag.es/'],
18+
relays: ['wss://relay.threenine.services'],
1919
tagStrategy: 'path',
2020
tagPrefix: 'comment:',
2121
},
2222
setup(options, nuxt) {
2323
const resolver = createResolver(import.meta.url)
2424

25-
// Configure Vite to handle tseep and @nostr-dev-kit/ndk properly
25+
// Configure Vite to handle tseep and nostr-tools properly
2626
nuxt.hook('vite:extendConfig', (config) => {
2727
// Ensure optimizeDeps exist
2828
config.optimizeDeps = config.optimizeDeps || {}
2929
config.optimizeDeps.include = config.optimizeDeps.include || []
3030

3131
// Pre-bundle these packages for better ESM/CJS interop
32-
config.optimizeDeps.include.push('tseep', '@nostr-dev-kit/ndk', 'nostr-tools', 'defu')
32+
config.optimizeDeps.include.push('tseep', 'nostr-tools', 'defu')
3333

3434
// For SSR, don't externalize these packages
3535
config.ssr = config.ssr || {}
3636

3737
// Handle the noExternal property correctly based on its current type
38-
const packagesToInclude = ['tseep', '@nostr-dev-kit/ndk', 'nostr-tools', 'defu']
38+
const packagesToInclude = ['tseep', 'nostr-tools', 'defu']
3939

4040
if (!config.ssr.noExternal) {
4141
config.ssr.noExternal = packagesToInclude
@@ -57,12 +57,12 @@ export default defineNuxtModule<ModuleOptions>({
5757
nuxt.hook('nitro:config', (nitroConfig) => {
5858
nitroConfig.externals = nitroConfig.externals || {}
5959
nitroConfig.externals.inline = nitroConfig.externals.inline || []
60-
nitroConfig.externals.inline.push('tseep', '@nostr-dev-kit/ndk', 'nostr-tools', 'defu')
60+
nitroConfig.externals.inline.push('tseep', 'nostr-tools', 'defu')
6161
})
6262

6363
// Build transpilation (you already had this)
6464
nuxt.options.build.transpile = nuxt.options.build.transpile || []
65-
nuxt.options.build.transpile.push('tseep', '@nostr-dev-kit/ndk', 'nostr-tools', 'defu')
65+
nuxt.options.build.transpile.push('tseep', 'nostr-tools', 'defu')
6666

6767
// Expose runtime config to plugin
6868
nuxt.options.runtimeConfig.public.nuxstrComments = defu(nuxt.options.runtimeConfig.public.nuxstrComments || {}, options)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { Event as NToolEvent, Filter } from 'nostr-tools'
2+
import { SimplePool, verifyEvent } from 'nostr-tools'
3+
4+
export class NostrManager {
5+
private static instance: NostrManager
6+
private pool: SimplePool
7+
private relays: string[]
8+
9+
private constructor(relays: string[]) {
10+
this.pool = new SimplePool()
11+
this.relays = relays
12+
}
13+
14+
public static getInstance(relays: string[]): NostrManager {
15+
if (!NostrManager.instance) {
16+
NostrManager.instance = new NostrManager(relays)
17+
}
18+
else {
19+
// Add any new relays to the existing set
20+
relays.forEach((relay) => {
21+
if (!NostrManager.instance.relays.includes(relay)) {
22+
NostrManager.instance.relays.push(relay)
23+
}
24+
})
25+
}
26+
return NostrManager.instance
27+
}
28+
29+
public subscribe(filter: Filter, onEvent: (event: NToolEvent) => void) {
30+
return this.pool.subscribeMany(this.relays, filter, {
31+
onevent(event) {
32+
if (verifyEvent(event)) {
33+
onEvent(event)
34+
}
35+
},
36+
})
37+
}
38+
39+
public async publish(event: NToolEvent): Promise<void> {
40+
await this.pool.publish(this.relays, event)
41+
}
42+
43+
public async getEvent(filter: Filter): Promise<NToolEvent | null> {
44+
return await this.pool.get(this.relays, filter)
45+
}
46+
47+
public async close() {
48+
this.pool.close(this.relays)
49+
}
50+
}

src/runtime/components/CommentAuthor.vue

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
<script setup lang="ts">
2+
import type { Profile } from '../types'
3+
24
const props = defineProps<{
3-
profile: Profile
5+
profile?: Profile
46
createdAt: number
57
}>()
68
</script>
79

810
<template>
9-
<div class="flex items-center gap-3">
11+
<div
12+
v-if="props.profile"
13+
class="flex items-center gap-3"
14+
>
1015
<div
1116
v-if="props.profile.image"
1217
class="flex-shrink-0"
1318
>
1419
<img
1520
:src="props.profile.image"
16-
:alt="props.profile.name || props.profile.display_name || 'User avatar'"
21+
:alt="props.profile.display_name || 'User avatar'"
1722
class="w-6 h-6 rounded-full object-cover"
1823
>
1924
</div>
2025
<div class="flex-1 min-w-0 mb-3 ">
2126
<div class="flex gap-6 items-center">
22-
<span class="text-sm mr-6">{{ props.profile.display_name || props.profile.name || `${props.profile.pubkey.slice(0, 8)}…` }}</span>
27+
<span class="text-sm mr-6">{{ props.profile.display_name || `${props.profile.pubkey.slice(0, 8)}…` }}</span>
2328
<span class="text-xs text-muted-foreground ml-3">&nbsp;&nbsp;</span>
2429
<span class="text-xs text-primary ml-3">{{ new Date(props.createdAt * 1000).toLocaleString() }}</span>
2530
</div>

src/runtime/components/ReplyView.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import type { Comment } from '../types'
3+
24
const props = defineProps<{ replies: Comment[] }>()
35
</script>
46

src/runtime/composables/useComments.ts

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { computed, type Ref, ref, type UnwrapRef } from 'vue'
22
import { useRequestURL, useRoute, useRuntimeConfig } from '#imports'
33
import useNuxstr from './useNuxstr'
4-
import { NDKEvent, type NDKFilter, NDKKind, type NDKSignedEvent, type NDKSubscription } from '@nostr-dev-kit/ndk'
5-
import type { Comment } from '~/src/runtime/types'
4+
import { useNostr } from './useNostr'
5+
import type { Event, Filter } from 'nostr-tools'
6+
7+
import type { Comment } from '../types'
68

79
function useComments(customContentId?: string) {
8-
const { ndk, connect, isLoggedIn, mapComment, pubkey, fetchProfile } = useNuxstr()
10+
const { isLoggedIn, pubkey, fetchProfile } = useNuxstr()
11+
const { subscribe } = useNostr()
912
const route = useRoute()
1013

1114
const config = useRuntimeConfig()
@@ -34,56 +37,65 @@ function useComments(customContentId?: string) {
3437
}
3538

3639
function siteUrl(): string {
37-
const url: URL = useRequestURL()
38-
return `${url.protocol}//${url.host}`
40+
if (import.meta.server) {
41+
const url: URL = useRequestURL()
42+
return `${url.protocol}//${url.host}`
43+
}
44+
return window.location.origin
3945
}
4046

4147
function fullUrl(path: string): string {
4248
return `${siteUrl()}${path}`
4349
}
4450

4551
async function subscribeComments() {
46-
await connect()
47-
const filter: NDKFilter = {
48-
kinds: [NDKKind.GenericReply],
52+
const filter: Filter = {
53+
kinds: [1111], // NDKKind.GenericReply is 22
4954
['#t']: [tagValue()],
5055
limit: 100,
51-
['#k']: ['web'],
52-
['#A']: [fullUrl(contentId.value)],
5356
}
54-
const sub: NDKSubscription = ndk.subscribe(filter)
55-
sub.on('event', async (event: NDKSignedEvent) => {
56-
const comment: Comment = mapComment(event)
57+
58+
subscribe(filter, async (event: Event) => {
59+
if (commentsData.value.some(c => c.id === event.id)) return
60+
const comment: Comment = {
61+
id: event.id,
62+
pubkey: event.pubkey,
63+
created_at: event.created_at,
64+
content: event.content,
65+
profile: undefined,
66+
}
5767
comment.profile = await fetchProfile(event.pubkey)
5868
commentsData.value.push(comment)
5969
})
6070
}
6171

6272
async function postComment(comment: string) {
63-
await connect()
64-
const ndkEvent: NDKEvent = await createCommentEvent(comment)
65-
return await ndkEvent.publish().then(() => true).catch((err: unknown) => {
73+
const { publish } = useNostr()
74+
try {
75+
const event = await createCommentEvent(comment)
76+
const signedEvent = await window.nostr.signEvent(event)
77+
await publish(signedEvent)
78+
return true
79+
}
80+
catch (err: unknown) {
6681
error.value = (err as Error)?.message || String(err)
6782
return false
68-
})
83+
}
6984
}
7085

7186
/// Create a new comment event as defined in NIP 22
72-
async function createCommentEvent(comment: string): Promise<NDKEvent> {
73-
const event: NDKEvent = new NDKEvent(ndk)
74-
event.kind = NDKKind.GenericReply
75-
event.content = comment
76-
event.tags = [
77-
['A', fullUrl(contentId.value)],
78-
['a', fullUrl(contentId.value)],
79-
['I', fullUrl(contentId.value)], //
80-
['i', fullUrl(contentId.value)],
81-
['t', tagValue()],
82-
['k', 'web'], // Defined NIP 73
83-
['K', 'web'], // Defined NIP 73,
84-
['p', pubkey ?? ''],
85-
]
86-
return event
87+
async function createCommentEvent(comment: string) {
88+
return {
89+
kind: 1111, // GenericReply
90+
created_at: Math.floor(Date.now() / 1000),
91+
content: comment,
92+
tags: [
93+
['A', fullUrl(contentId.value)],
94+
['t', tagValue()],
95+
['k', 'web'],
96+
['p', pubkey ?? ''],
97+
],
98+
}
8799
}
88100

89101
return { loading, error, comments, isLoggedIn, subscribeComments, postComment }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useRuntimeConfig } from '#imports'
2+
import type { Filter, Event } from 'nostr-tools'
3+
import { NostrManager } from '../classes/NostrManager'
4+
5+
export const useNostr = (relays?: string[]) => {
6+
const config = useRuntimeConfig()
7+
const opts = (config.public?.nuxstrComments || {}) as {
8+
relays?: string[]
9+
}
10+
11+
const effectiveRelays = relays || opts.relays || []
12+
const nostrManager = NostrManager.getInstance(effectiveRelays)
13+
14+
const subscribe = (filter: Filter, onEvent: (event: Event) => void) => {
15+
return nostrManager.subscribe(filter, onEvent)
16+
}
17+
18+
const publish = (event: Event) => {
19+
console.log('publishing Comment', event)
20+
return nostrManager.publish(event)
21+
}
22+
23+
const getEvent = (filter: Filter) => {
24+
return nostrManager.getEvent(filter)
25+
}
26+
27+
return {
28+
nostrManager,
29+
subscribe,
30+
publish,
31+
getEvent,
32+
}
33+
}

0 commit comments

Comments
 (0)