Browse Source

Remove Applesauce. Only use nostr-tools.

master
Silberengel 1 month ago
parent
commit
845dc60764
  1. 9
      README.md
  2. 1
      README_SETUP.md
  3. 989
      package-lock.json
  4. 1
      package.json
  5. 4
      public/healthz.json
  6. 2
      src/lib/modules/threads/CreateThreadForm.svelte
  7. 2
      src/lib/modules/threads/ThreadList.svelte
  8. 6
      src/lib/services/auth/activity-tracker.ts
  9. 2
      src/lib/services/auth/profile-fetcher.ts
  10. 2
      src/lib/services/auth/relay-list-fetcher.ts
  11. 2
      src/lib/services/auth/user-status-fetcher.ts
  12. 2
      src/lib/services/nostr/auth-handler.ts
  13. 123
      src/lib/services/nostr/nostr-client.ts
  14. 2
      src/routes/+page.svelte
  15. 2
      src/routes/feed/+page.svelte
  16. 2
      src/routes/login/+page.svelte
  17. 2
      src/routes/thread/[id]/+page.svelte
  18. 2
      src/routes/threads/+page.svelte

9
README.md

@ -24,7 +24,7 @@ This is a client from [silberengel@gitcitadel.com](https://jumble.imwald.eu/user
**REQUIRED**: **REQUIRED**:
- Frontend: Svelte 5 (with runes: `$state`, `$derived`, `$effect`) + TypeScript + Vite - Frontend: Svelte 5 (with runes: `$state`, `$derived`, `$effect`) + TypeScript + Vite
- Styling: Tailwind CSS (4chan-style minimal design) - Styling: Tailwind CSS (4chan-style minimal design)
- Nostr Library: [`applesauce-core`](https://github.com/hzrd149/applesauce) - Nostr Library: [`nostr-tools`](https://github.com/nbd-wtf/nostr-tools)
- Markdown: `marked` + `DOMPurify` for sanitization - Markdown: `marked` + `DOMPurify` for sanitization
- Storage: IndexedDB (via `idb` or `localforage`) - Storage: IndexedDB (via `idb` or `localforage`)
- Deployment: Docker + Apache httpd (static serving on port 9876) - Deployment: Docker + Apache httpd (static serving on port 9876)
@ -38,10 +38,7 @@ aitherboard/
│ ├── lib/ │ ├── lib/
│ │ ├── services/ │ │ ├── services/
│ │ │ ├── nostr/ │ │ │ ├── nostr/
│ │ │ │ ├── applesauce-client.ts │ │ │ │ ├── nostr-client.ts
│ │ │ │ ├── relay-pool.ts
│ │ │ │ ├── event-store.ts
│ │ │ │ ├── subscription-manager.ts
│ │ │ │ ├── auth-handler.ts │ │ │ │ ├── auth-handler.ts
│ │ │ │ └── config.ts │ │ │ │ └── config.ts
│ │ │ ├── auth/ │ │ │ ├── auth/
@ -958,7 +955,7 @@ exec httpd -D FOREGROUND
## Links ## Links
- **Nostr Protocol**: https://nostr.com - **Nostr Protocol**: https://nostr.com
- **Applesauce Library**: https://github.com/hzrd149/applesauce - **nostr-tools Library**: https://github.com/nbd-wtf/nostr-tools
- **NIP-7D (Kind 11 Threads)**: [Nostr Improvement Proposals] - **NIP-7D (Kind 11 Threads)**: [Nostr Improvement Proposals]
- **NIP-22 (Kind 1111 Comments)**: [Nostr Improvement Proposals] - **NIP-22 (Kind 1111 Comments)**: [Nostr Improvement Proposals]
- **NIP-57 (Zaps)**: [Nostr Improvement Proposals] - **NIP-57 (Zaps)**: [Nostr Improvement Proposals]

1
README_SETUP.md

@ -55,5 +55,4 @@ docker run -p 9876:9876 aitherboard
- This is a work in progress. Many features are placeholders and need full implementation. - This is a work in progress. Many features are placeholders and need full implementation.
- NIP-49 encryption, event signing, and bech32 encoding need proper cryptographic libraries. - NIP-49 encryption, event signing, and bech32 encoding need proper cryptographic libraries.
- The applesauce-core library integration needs to be completed.
- Full implementation of all modules (comments, zaps, reactions, profiles, feed) is ongoing. - Full implementation of all modules (comments, zaps, reactions, profiles, feed) is ongoing.

989
package-lock.json generated

File diff suppressed because it is too large Load Diff

1
package.json

@ -25,7 +25,6 @@
"dependencies": { "dependencies": {
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"applesauce-core": "github:hzrd149/applesauce",
"dompurify": "^3.0.6", "dompurify": "^3.0.6",
"idb": "^8.0.0", "idb": "^8.0.0",
"marked": "^11.1.1", "marked": "^11.1.1",

4
public/healthz.json

@ -2,7 +2,7 @@
"status": "ok", "status": "ok",
"service": "aitherboard", "service": "aitherboard",
"version": "0.1.0", "version": "0.1.0",
"buildTime": "2026-02-02T13:56:52.264Z", "buildTime": "2026-02-02T14:12:17.639Z",
"gitCommit": "unknown", "gitCommit": "unknown",
"timestamp": 1770040612264 "timestamp": 1770041537639
} }

2
src/lib/modules/threads/CreateThreadForm.svelte

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { sessionManager } from '../../services/auth/session-manager.js'; import { sessionManager } from '../../services/auth/session-manager.js';
import { nostrClient } from '../../services/nostr/applesauce-client.js'; import { nostrClient } from '../../services/nostr/nostr-client.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
let title = $state(''); let title = $state('');

2
src/lib/modules/threads/ThreadList.svelte

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { nostrClient } from '../../services/nostr/applesauce-client.js'; import { nostrClient } from '../../services/nostr/nostr-client.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import ThreadCard from './ThreadCard.svelte'; import ThreadCard from './ThreadCard.svelte';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';

6
src/lib/services/auth/activity-tracker.ts

@ -2,21 +2,19 @@
* Activity tracker - tracks last activity per pubkey * Activity tracker - tracks last activity per pubkey
*/ */
import { nostrClient } from '../nostr/applesauce-client.js'; import { nostrClient } from '../nostr/nostr-client.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
/** /**
* Get last activity timestamp for a pubkey * Get last activity timestamp for a pubkey
*/ */
export async function getLastActivity(pubkey: string): Promise<number | undefined> { export async function getLastActivity(pubkey: string): Promise<number | undefined> {
const eventStore = nostrClient.getEventStore();
// Query for recent events from this pubkey // Query for recent events from this pubkey
const filters = [ const filters = [
{ authors: [pubkey], kinds: [0, 1, 7, 11, 1111], limit: 1 } { authors: [pubkey], kinds: [0, 1, 7, 11, 1111], limit: 1 }
]; ];
const events = eventStore.getByFilters(filters); const events = nostrClient.getByFilters(filters);
if (events.length > 0) { if (events.length > 0) {
// Sort by created_at descending and return the most recent // Sort by created_at descending and return the most recent
const sorted = events.sort((a: NostrEvent, b: NostrEvent) => b.created_at - a.created_at); const sorted = events.sort((a: NostrEvent, b: NostrEvent) => b.created_at - a.created_at);

2
src/lib/services/auth/profile-fetcher.ts

@ -2,7 +2,7 @@
* Profile fetcher (kind 0 events) * Profile fetcher (kind 0 events)
*/ */
import { nostrClient } from '../nostr/applesauce-client.js'; import { nostrClient } from '../nostr/nostr-client.js';
import { cacheProfile, getProfile, getProfiles } from '../cache/profile-cache.js'; import { cacheProfile, getProfile, getProfiles } from '../cache/profile-cache.js';
import { config } from '../nostr/config.js'; import { config } from '../nostr/config.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';

2
src/lib/services/auth/relay-list-fetcher.ts

@ -2,7 +2,7 @@
* Relay list fetcher (kind 10002 and 10432) * Relay list fetcher (kind 10002 and 10432)
*/ */
import { nostrClient } from '../nostr/applesauce-client.js'; import { nostrClient } from '../nostr/nostr-client.js';
import { config } from '../nostr/config.js'; import { config } from '../nostr/config.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';

2
src/lib/services/auth/user-status-fetcher.ts

@ -2,7 +2,7 @@
* User status fetcher (kind 30315, NIP-38) * User status fetcher (kind 30315, NIP-38)
*/ */
import { nostrClient } from '../nostr/applesauce-client.js'; import { nostrClient } from '../nostr/nostr-client.js';
import { config } from '../nostr/config.js'; import { config } from '../nostr/config.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';

2
src/lib/services/nostr/auth-handler.ts

@ -12,7 +12,7 @@ import {
import { decryptPrivateKey } from '../security/key-management.js'; import { decryptPrivateKey } from '../security/key-management.js';
import { sessionManager, type AuthMethod } from '../auth/session-manager.js'; import { sessionManager, type AuthMethod } from '../auth/session-manager.js';
import { fetchRelayLists } from '../auth/relay-list-fetcher.js'; import { fetchRelayLists } from '../auth/relay-list-fetcher.js';
import { nostrClient } from './applesauce-client.js'; import { nostrClient } from './nostr-client.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
// Mute list and blocked relays management // Mute list and blocked relays management

123
src/lib/services/nostr/applesauce-client.ts → src/lib/services/nostr/nostr-client.ts

@ -1,31 +1,24 @@
/** /**
* Applesauce-core client wrapper * Nostr client using nostr-tools
* Main interface for Nostr operations using applesauce-core and nostr-tools * Main interface for Nostr operations using only nostr-tools
*/ */
// @ts-expect-error - applesauce-core types may not be available, but package works at runtime import { Relay, type Filter, matchFilter } from 'nostr-tools';
import { EventStore } from 'applesauce-core';
// @ts-expect-error - applesauce-core types may not be available, but package works at runtime
import type { Filter } from 'applesauce-core/helpers';
import { Relay } from 'nostr-tools/relay';
import { config } from './config.js'; import { config } from './config.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
import { cacheEvent, cacheEvents, getEvent, getEventsByKind, getEventsByPubkey } from '../cache/event-cache.js';
export interface PublishOptions { export interface PublishOptions {
relays?: string[]; relays?: string[];
skipRelayValidation?: boolean; skipRelayValidation?: boolean;
} }
class ApplesauceClient { class NostrClient {
private initialized = false; private initialized = false;
private eventStore: EventStore;
private relays: Map<string, Relay> = new Map(); private relays: Map<string, Relay> = new Map();
private subscriptions: Map<string, { relay: Relay; sub: any }> = new Map(); private subscriptions: Map<string, { relay: Relay; sub: any }> = new Map();
private nextSubId = 1; private nextSubId = 1;
private eventCache: Map<string, NostrEvent> = new Map(); // In-memory cache
constructor() {
this.eventStore = new EventStore();
}
/** /**
* Initialize the client * Initialize the client
@ -54,8 +47,6 @@ class ApplesauceClient {
try { try {
const relay = await Relay.connect(url); const relay = await Relay.connect(url);
this.relays.set(url, relay); this.relays.set(url, relay);
// Events will be added to store via subscriptions
} catch (error) { } catch (error) {
console.error(`Failed to connect to relay ${url}:`, error); console.error(`Failed to connect to relay ${url}:`, error);
throw error; throw error;
@ -73,6 +64,37 @@ class ApplesauceClient {
} }
} }
/**
* Add event to cache
*/
private addToCache(event: NostrEvent): void {
this.eventCache.set(event.id, event);
// Also cache to IndexedDB
cacheEvent(event).catch((error) => {
console.error('Error caching event:', error);
});
}
/**
* Get events from cache that match filters
*/
private getCachedEvents(filters: Filter[]): NostrEvent[] {
const results: NostrEvent[] = [];
const seen = new Set<string>();
for (const filter of filters) {
for (const event of this.eventCache.values()) {
if (seen.has(event.id)) continue;
if (matchFilter(filter, event)) {
results.push(event);
seen.add(event.id);
}
}
}
return results;
}
/** /**
* Publish an event to relays * Publish an event to relays
*/ */
@ -86,8 +108,8 @@ class ApplesauceClient {
failed: [] as Array<{ relay: string; error: string }> failed: [] as Array<{ relay: string; error: string }>
}; };
// Add event to store first // Add event to cache first
this.eventStore.add(event); this.addToCache(event);
// Publish to each relay // Publish to each relay
for (const url of relays) { for (const url of relays) {
@ -176,8 +198,8 @@ class ApplesauceClient {
const client = this; const client = this;
const sub = relay.subscribe(filters, { const sub = relay.subscribe(filters, {
onevent(event: NostrEvent) { onevent(event: NostrEvent) {
// Add to store // Add to cache
client.eventStore.add(event); client.addToCache(event);
// Call callback // Call callback
onEvent(event, url); onEvent(event, url);
}, },
@ -211,9 +233,35 @@ class ApplesauceClient {
): Promise<NostrEvent[]> { ): Promise<NostrEvent[]> {
const { useCache = true, cacheResults = true, onUpdate } = options || {}; const { useCache = true, cacheResults = true, onUpdate } = options || {};
// Query from event store first if cache is enabled // Query from cache first if enabled
if (useCache) { if (useCache) {
const cachedEvents = this.eventStore.getByFilters(filters); // Try in-memory cache first
let cachedEvents = this.getCachedEvents(filters);
// If no results in memory, try IndexedDB
if (cachedEvents.length === 0) {
try {
// Try to get from IndexedDB based on filter
for (const filter of filters) {
if (filter.kinds && filter.kinds.length === 1) {
const dbEvents = await getEventsByKind(filter.kinds[0], filter.limit || 50);
cachedEvents.push(...dbEvents);
}
if (filter.authors && filter.authors.length === 1) {
const dbEvents = await getEventsByPubkey(filter.authors[0], filter.limit || 50);
cachedEvents.push(...dbEvents);
}
}
// Add to in-memory cache
for (const event of cachedEvents) {
this.eventCache.set(event.id, event);
}
} catch (error) {
console.error('Error loading from IndexedDB:', error);
}
}
if (cachedEvents.length > 0) { if (cachedEvents.length > 0) {
// Return cached events immediately // Return cached events immediately
if (onUpdate) { if (onUpdate) {
@ -266,6 +314,14 @@ class ApplesauceClient {
} }
const eventArrayValues = Array.from(eventArray); const eventArrayValues = Array.from(eventArray);
// Cache results
if (options.cacheResults && eventArrayValues.length > 0) {
cacheEvents(eventArrayValues).catch((error) => {
console.error('Error caching events:', error);
});
}
if (options.onUpdate) { if (options.onUpdate) {
options.onUpdate(eventArrayValues); options.onUpdate(eventArrayValues);
} }
@ -307,9 +363,20 @@ class ApplesauceClient {
* Get event by ID * Get event by ID
*/ */
async getEventById(id: string, relays: string[]): Promise<NostrEvent | null> { async getEventById(id: string, relays: string[]): Promise<NostrEvent | null> {
// Try store first // Try in-memory cache first
const event = this.eventStore.getEvent(id); const cached = this.eventCache.get(id);
if (event) return event; if (cached) return cached;
// Try IndexedDB
try {
const dbEvent = await getEvent(id);
if (dbEvent) {
this.eventCache.set(dbEvent.id, dbEvent);
return dbEvent;
}
} catch (error) {
console.error('Error loading from IndexedDB:', error);
}
// Fetch from relays // Fetch from relays
const filters: Filter[] = [{ ids: [id] }]; const filters: Filter[] = [{ ids: [id] }];
@ -318,10 +385,10 @@ class ApplesauceClient {
} }
/** /**
* Get event store * Get events by filters (from cache only)
*/ */
getEventStore(): EventStore { getByFilters(filters: Filter[]): NostrEvent[] {
return this.eventStore; return this.getCachedEvents(filters);
} }
/** /**
@ -358,4 +425,4 @@ class ApplesauceClient {
} }
} }
export const nostrClient = new ApplesauceClient(); export const nostrClient = new NostrClient();

2
src/routes/+page.svelte

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import Header from '../lib/components/layout/Header.svelte'; import Header from '../lib/components/layout/Header.svelte';
import ThreadList from '../lib/modules/threads/ThreadList.svelte'; import ThreadList from '../lib/modules/threads/ThreadList.svelte';
import { nostrClient } from '../lib/services/nostr/applesauce-client.js'; import { nostrClient } from '../lib/services/nostr/nostr-client.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
onMount(async () => { onMount(async () => {

2
src/routes/feed/+page.svelte

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import Header from '../../lib/components/layout/Header.svelte'; import Header from '../../lib/components/layout/Header.svelte';
import { nostrClient } from '../../lib/services/nostr/applesauce-client.js'; import { nostrClient } from '../../lib/services/nostr/nostr-client.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
onMount(async () => { onMount(async () => {

2
src/routes/login/+page.svelte

@ -3,7 +3,7 @@
import { isNIP07Available } from '../../lib/services/auth/nip07-signer.js'; import { isNIP07Available } from '../../lib/services/auth/nip07-signer.js';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { nostrClient } from '../../lib/services/nostr/applesauce-client.js'; import { nostrClient } from '../../lib/services/nostr/nostr-client.js';
onMount(async () => { onMount(async () => {
await nostrClient.initialize(); await nostrClient.initialize();

2
src/routes/thread/[id]/+page.svelte

@ -2,7 +2,7 @@
import Header from '../../../lib/components/layout/Header.svelte'; import Header from '../../../lib/components/layout/Header.svelte';
import ProfileBadge from '../../../lib/components/layout/ProfileBadge.svelte'; import ProfileBadge from '../../../lib/components/layout/ProfileBadge.svelte';
import MarkdownRenderer from '../../../lib/components/content/MarkdownRenderer.svelte'; import MarkdownRenderer from '../../../lib/components/content/MarkdownRenderer.svelte';
import { nostrClient } from '../../../lib/services/nostr/applesauce-client.js'; import { nostrClient } from '../../../lib/services/nostr/nostr-client.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import type { NostrEvent } from '../../../lib/types/nostr.js'; import type { NostrEvent } from '../../../lib/types/nostr.js';
import { page } from '$app/stores'; import { page } from '$app/stores';

2
src/routes/threads/+page.svelte

@ -3,7 +3,7 @@
import ThreadList from '../../lib/modules/threads/ThreadList.svelte'; import ThreadList from '../../lib/modules/threads/ThreadList.svelte';
import CreateThreadForm from '../../lib/modules/threads/CreateThreadForm.svelte'; import CreateThreadForm from '../../lib/modules/threads/CreateThreadForm.svelte';
import { sessionManager } from '../../lib/services/auth/session-manager.js'; import { sessionManager } from '../../lib/services/auth/session-manager.js';
import { nostrClient } from '../../lib/services/nostr/applesauce-client.js'; import { nostrClient } from '../../lib/services/nostr/nostr-client.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let showCreateForm = $state(false); let showCreateForm = $state(false);

Loading…
Cancel
Save