96 changed files with 7696 additions and 151 deletions
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
{ |
||||
"css.lint.unknownAtRules": "ignore", |
||||
"scss.lint.unknownAtRules": "ignore", |
||||
"less.lint.unknownAtRules": "ignore", |
||||
"css.validate": false, |
||||
"scss.validate": false, |
||||
"less.validate": false |
||||
} |
||||
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
||||
|
||||
<!-- Favicon --> |
||||
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" /> |
||||
<link rel="apple-touch-icon" href="%sveltekit.assets%/favicon.svg" /> |
||||
|
||||
<!-- Primary Meta Tags --> |
||||
<title>Aitherboard - Decentralized Messageboard on Nostr</title> |
||||
<meta name="title" content="Aitherboard - Decentralized Messageboard on Nostr" /> |
||||
<meta name="description" content="A decentralized messageboard built on the Nostr protocol. Create threads, comment, react, and zap in a censorship-resistant environment." /> |
||||
<meta name="keywords" content="nostr, decentralized, messageboard, forum, social media, censorship resistant" /> |
||||
<meta name="author" content="silberengel@gitcitadel.com" /> |
||||
|
||||
<!-- Open Graph / Facebook --> |
||||
<meta property="og:type" content="website" /> |
||||
<meta property="og:url" content="https://aitherboard.com/" /> |
||||
<meta property="og:title" content="Aitherboard - Decentralized Messageboard on Nostr" /> |
||||
<meta property="og:description" content="A decentralized messageboard built on the Nostr protocol. Create threads, comment, react, and zap in a censorship-resistant environment." /> |
||||
<meta property="og:image" content="%sveltekit.assets%/aither.png" /> |
||||
<meta property="og:image:width" content="1200" /> |
||||
<meta property="og:image:height" content="630" /> |
||||
<meta property="og:image:alt" content="Aitherboard - Decentralized Messageboard on Nostr" /> |
||||
<meta property="og:site_name" content="Aitherboard" /> |
||||
<meta property="og:locale" content="en_US" /> |
||||
|
||||
<!-- Twitter --> |
||||
<meta name="twitter:card" content="summary_large_image" /> |
||||
<meta name="twitter:url" content="https://aitherboard.com/" /> |
||||
<meta name="twitter:title" content="Aitherboard - Decentralized Messageboard on Nostr" /> |
||||
<meta name="twitter:description" content="A decentralized messageboard built on the Nostr protocol. Create threads, comment, react, and zap in a censorship-resistant environment." /> |
||||
<meta name="twitter:image" content="%sveltekit.assets%/aither.png" /> |
||||
<meta name="twitter:image:alt" content="Aitherboard - Decentralized Messageboard on Nostr" /> |
||||
|
||||
<!-- Additional Meta Tags --> |
||||
<meta name="theme-color" content="#f1f5f9" /> |
||||
<meta name="color-scheme" content="light dark" /> |
||||
|
||||
<!-- PWA Manifest --> |
||||
<link rel="manifest" href="%sveltekit.assets%/manifest.json" /> |
||||
|
||||
%sveltekit.head% |
||||
</head> |
||||
<body data-sveltekit-preload-data="hover"> |
||||
<div style="display: contents">%sveltekit.body%</div> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/** |
||||
* Activity tracker - tracks last activity per pubkey |
||||
*/ |
||||
import { eventStore } from '../nostr/event-store.js'; |
||||
/** |
||||
* Get last activity timestamp for a pubkey |
||||
*/ |
||||
export function getLastActivity(pubkey) { |
||||
return eventStore.getLastActivity(pubkey); |
||||
} |
||||
/** |
||||
* Get activity status color |
||||
* Red: ≥168 hours (7 days) |
||||
* Yellow: ≥48 hours (2 days) but <168 hours |
||||
* Green: <48 hours |
||||
*/ |
||||
export function getActivityStatus(pubkey) { |
||||
const lastActivity = getLastActivity(pubkey); |
||||
if (!lastActivity) |
||||
return null; |
||||
const now = Math.floor(Date.now() / 1000); |
||||
const hoursSince = (now - lastActivity) / 3600; |
||||
if (hoursSince >= 168) |
||||
return 'red'; |
||||
if (hoursSince >= 48) |
||||
return 'yellow'; |
||||
return 'green'; |
||||
} |
||||
//# sourceMappingURL=activity-tracker.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"activity-tracker.js","sourceRoot":"","sources":["activity-tracker.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,OAAO,UAAU,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,MAAM,YAAY,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAE/B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,CAAC,GAAG,GAAG,YAAY,CAAC,GAAG,IAAI,CAAC;IAE/C,IAAI,UAAU,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC;IACpC,IAAI,UAAU,IAAI,EAAE;QAAE,OAAO,QAAQ,CAAC;IACtC,OAAO,OAAO,CAAC;AACjB,CAAC"} |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
/** |
||||
* Anonymous signer (generated keys, NIP-49 encrypted) |
||||
*/ |
||||
import { generatePrivateKey } from '../security/key-management.js'; |
||||
import { storeAnonymousKey, getAnonymousKey } from '../cache/anonymous-key-store.js'; |
||||
import { getPublicKeyFromNsec } from './nsec-signer.js'; |
||||
import { signEventWithNsec } from './nsec-signer.js'; |
||||
/** |
||||
* Generate and store anonymous key |
||||
*/ |
||||
export async function generateAnonymousKey(password) { |
||||
const nsec = generatePrivateKey(); |
||||
const pubkey = await getPublicKeyFromNsec(nsec); |
||||
// Store encrypted
|
||||
await storeAnonymousKey(nsec, password, pubkey); |
||||
return { pubkey, nsec }; |
||||
} |
||||
/** |
||||
* Get stored anonymous key |
||||
*/ |
||||
export async function getStoredAnonymousKey(pubkey, password) { |
||||
return getAnonymousKey(pubkey, password); |
||||
} |
||||
/** |
||||
* Sign event with anonymous key |
||||
*/ |
||||
export async function signEventWithAnonymous(event, pubkey, password) { |
||||
const nsec = await getStoredAnonymousKey(pubkey, password); |
||||
if (!nsec) { |
||||
throw new Error('Anonymous key not found'); |
||||
} |
||||
// For anonymous keys, we need the ncryptsec format
|
||||
// This is simplified - in practice we'd store ncryptsec and decrypt it
|
||||
// For now, assume we have the plain nsec after decryption
|
||||
return signEventWithNsec(event, nsec, password); |
||||
} |
||||
/** |
||||
* Generate anonymous handle |
||||
*/ |
||||
export function generateAnonymousHandle(pubkey) { |
||||
// Use last 6 characters of pubkey for uniqueness
|
||||
const suffix = pubkey.slice(-6); |
||||
return `Aitherite${suffix}`; |
||||
} |
||||
//# sourceMappingURL=anonymous-signer.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"anonymous-signer.js","sourceRoot":"","sources":["anonymous-signer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IAIzD,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAEhD,kBAAkB;IAClB,MAAM,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAEhD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAc,EACd,QAAgB;IAEhB,OAAO,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAqC,EACrC,MAAc,EACd,QAAgB;IAEhB,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IAED,mDAAmD;IACnD,uEAAuE;IACvE,0DAA0D;IAC1D,OAAO,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,iDAAiD;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChC,OAAO,YAAY,MAAM,EAAE,CAAC;AAC9B,CAAC"} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/** |
||||
* NIP-46 Bunker signer (remote signer) |
||||
*/ |
||||
/** |
||||
* Connect to bunker signer |
||||
*/ |
||||
export async function connectBunker(bunkerUri) { |
||||
// Parse bunker:// URI
|
||||
// Format: bunker://<pubkey>@<relay>?token=<token>
|
||||
const match = bunkerUri.match(/^bunker:\/\/([^@]+)@([^?]+)(?:\?token=([^&]+))?$/); |
||||
if (!match) { |
||||
throw new Error('Invalid bunker URI'); |
||||
} |
||||
const [, pubkey, relay, token] = match; |
||||
return { |
||||
bunkerUrl: relay, |
||||
pubkey, |
||||
token |
||||
}; |
||||
} |
||||
/** |
||||
* Sign event with bunker |
||||
*/ |
||||
export async function signEventWithBunker(event, connection) { |
||||
// Placeholder - would:
|
||||
// 1. Send NIP-46 request to bunker
|
||||
// 2. Wait for response
|
||||
// 3. Return signed event
|
||||
throw new Error('Bunker signing not yet implemented'); |
||||
} |
||||
//# sourceMappingURL=bunker-signer.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"bunker-signer.js","sourceRoot":"","sources":["bunker-signer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,SAAiB;IACnD,sBAAsB;IACtB,kDAAkD;IAClD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;IAClF,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC;IAEvC,OAAO;QACL,SAAS,EAAE,KAAK;QAChB,MAAM;QACN,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAqC,EACrC,UAA4B;IAE5B,uBAAuB;IACvB,mCAAmC;IACnC,uBAAuB;IACvB,yBAAyB;IAEzB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;AACxD,CAAC"} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
/** |
||||
* NIP-07 signer (browser extension) |
||||
*/ |
||||
/** |
||||
* Check if NIP-07 is available |
||||
*/ |
||||
export function isNIP07Available() { |
||||
return typeof window !== 'undefined' && 'nostr' in window; |
||||
} |
||||
/** |
||||
* Get NIP-07 signer |
||||
*/ |
||||
export function getNIP07Signer() { |
||||
if (!isNIP07Available()) |
||||
return null; |
||||
const nostr = window.nostr; |
||||
return nostr || null; |
||||
} |
||||
/** |
||||
* Sign event with NIP-07 |
||||
*/ |
||||
export async function signEventWithNIP07(event) { |
||||
const signer = getNIP07Signer(); |
||||
if (!signer) { |
||||
throw new Error('NIP-07 not available'); |
||||
} |
||||
return signer.signEvent(event); |
||||
} |
||||
/** |
||||
* Get public key with NIP-07 |
||||
*/ |
||||
export async function getPublicKeyWithNIP07() { |
||||
const signer = getNIP07Signer(); |
||||
if (!signer) { |
||||
throw new Error('NIP-07 not available'); |
||||
} |
||||
return signer.getPublicKey(); |
||||
} |
||||
//# sourceMappingURL=nip07-signer.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"nip07-signer.js","sourceRoot":"","sources":["nip07-signer.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,IAAI,MAAM,CAAC;AAC5D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,gBAAgB,EAAE;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,KAAK,GAAI,MAAkC,CAAC,KAAK,CAAC;IACxD,OAAO,KAAK,IAAI,IAAI,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAqC;IAErC,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,MAAM,CAAC,YAAY,EAAE,CAAC;AAC/B,CAAC"} |
||||
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
/** |
||||
* Nsec signer (direct private key, NIP-49 encrypted) |
||||
*/ |
||||
import { decryptPrivateKey } from '../security/key-management.js'; |
||||
/** |
||||
* Sign event with nsec (private key) |
||||
* This is a placeholder - full implementation requires: |
||||
* - secp256k1 cryptography library |
||||
* - Event ID computation (SHA256) |
||||
* - Signature computation |
||||
*/ |
||||
export async function signEventWithNsec(event, ncryptsec, password) { |
||||
// Decrypt private key
|
||||
const nsec = await decryptPrivateKey(ncryptsec, password); |
||||
// Compute event ID (SHA256 of serialized event)
|
||||
const serialized = JSON.stringify([ |
||||
0, |
||||
event.pubkey, |
||||
event.created_at, |
||||
event.kind, |
||||
event.tags, |
||||
event.content |
||||
]); |
||||
const encoder = new TextEncoder(); |
||||
const data = encoder.encode(serialized); |
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data); |
||||
const hashArray = Array.from(new Uint8Array(hashBuffer)); |
||||
const id = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); |
||||
// TEMPORARY: Generate a deterministic signature-like string
|
||||
// This is NOT a valid secp256k1 signature but has the correct length
|
||||
// Production code MUST compute actual secp256k1 signature
|
||||
const sigData = encoder.encode(nsec + id); |
||||
const sigHashBuffer = await crypto.subtle.digest('SHA-256', sigData); |
||||
const sigHashArray = Array.from(new Uint8Array(sigHashBuffer)); |
||||
const sigHash = sigHashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); |
||||
// Double the hash to get 128 chars (64 * 2)
|
||||
const sig = (sigHash + sigHash).slice(0, 128); |
||||
return { |
||||
...event, |
||||
id, |
||||
sig |
||||
}; |
||||
} |
||||
/** |
||||
* Get public key from private key |
||||
* |
||||
* TEMPORARY: Uses SHA256 hash of private key to generate a deterministic pubkey. |
||||
* This is NOT a valid secp256k1 public key derivation but provides unique pubkeys. |
||||
* Production code MUST use proper secp256k1 point multiplication. |
||||
*/ |
||||
export async function getPublicKeyFromNsec(nsec) { |
||||
// TEMPORARY: Generate deterministic pubkey from private key hash
|
||||
// This ensures each private key gets a unique (but not cryptographically valid) pubkey
|
||||
const encoder = new TextEncoder(); |
||||
const data = encoder.encode(nsec); |
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data); |
||||
const hashArray = Array.from(new Uint8Array(hashBuffer)); |
||||
const hash = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); |
||||
// Double the hash to get 64 chars (32 * 2)
|
||||
return (hash + hash).slice(0, 64); |
||||
} |
||||
//# sourceMappingURL=nsec-signer.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"nsec-signer.js","sourceRoot":"","sources":["nsec-signer.ts"],"names":[],"mappings":"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"} |
||||
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
/** |
||||
* Profile fetcher (kind 0 events) |
||||
*/ |
||||
import { nostrClient } from '../nostr/applesauce-client.js'; |
||||
import { cacheProfile, getProfile, getProfiles } from '../cache/profile-cache.js'; |
||||
import { config } from '../nostr/config.js'; |
||||
/** |
||||
* Parse profile from kind 0 event |
||||
*/ |
||||
export function parseProfile(event) { |
||||
const profile = {}; |
||||
// Try to parse from tags first (preferred)
|
||||
const nameTag = event.tags.find((t) => t[0] === 'name'); |
||||
if (nameTag && nameTag[1]) |
||||
profile.name = nameTag[1]; |
||||
const aboutTag = event.tags.find((t) => t[0] === 'about'); |
||||
if (aboutTag && aboutTag[1]) |
||||
profile.about = aboutTag[1]; |
||||
const pictureTag = event.tags.find((t) => t[0] === 'picture'); |
||||
if (pictureTag && pictureTag[1]) |
||||
profile.picture = pictureTag[1]; |
||||
// Multiple tags for website, nip05, lud16
|
||||
profile.website = event.tags.filter((t) => t[0] === 'website').map((t) => t[1]).filter(Boolean); |
||||
profile.nip05 = event.tags.filter((t) => t[0] === 'nip05').map((t) => t[1]).filter(Boolean); |
||||
profile.lud16 = event.tags.filter((t) => t[0] === 'lud16').map((t) => t[1]).filter(Boolean); |
||||
// Fallback to JSON content if tags not found
|
||||
if (!profile.name || !profile.about) { |
||||
try { |
||||
const json = JSON.parse(event.content); |
||||
if (json.name && !profile.name) |
||||
profile.name = json.name; |
||||
if (json.about && !profile.about) |
||||
profile.about = json.about; |
||||
if (json.picture && !profile.picture) |
||||
profile.picture = json.picture; |
||||
if (json.website && profile.website.length === 0) { |
||||
profile.website = Array.isArray(json.website) ? json.website : [json.website]; |
||||
} |
||||
if (json.nip05 && profile.nip05.length === 0) { |
||||
profile.nip05 = Array.isArray(json.nip05) ? json.nip05 : [json.nip05]; |
||||
} |
||||
if (json.lud16 && profile.lud16.length === 0) { |
||||
profile.lud16 = Array.isArray(json.lud16) ? json.lud16 : [json.lud16]; |
||||
} |
||||
} |
||||
catch { |
||||
// Invalid JSON, ignore
|
||||
} |
||||
} |
||||
return profile; |
||||
} |
||||
/** |
||||
* Fetch profile for a pubkey |
||||
*/ |
||||
export async function fetchProfile(pubkey, relays) { |
||||
// Try cache first
|
||||
const cached = await getProfile(pubkey); |
||||
if (cached) { |
||||
return parseProfile(cached.event); |
||||
} |
||||
// Fetch from relays
|
||||
const relayList = relays || [ |
||||
...config.defaultRelays, |
||||
...config.profileRelays |
||||
]; |
||||
const events = await nostrClient.fetchEvents([{ kinds: [0], authors: [pubkey], limit: 1 }], relayList, { useCache: true, cacheResults: true }); |
||||
if (events.length === 0) |
||||
return null; |
||||
const event = events[0]; |
||||
await cacheProfile(event); |
||||
return parseProfile(event); |
||||
} |
||||
/** |
||||
* Fetch multiple profiles |
||||
*/ |
||||
export async function fetchProfiles(pubkeys, relays) { |
||||
const profiles = new Map(); |
||||
// Check cache first
|
||||
const cached = await getProfiles(pubkeys); |
||||
for (const [pubkey, cachedProfile] of cached.entries()) { |
||||
profiles.set(pubkey, parseProfile(cachedProfile.event)); |
||||
} |
||||
// Fetch missing profiles
|
||||
const missing = pubkeys.filter((p) => !profiles.has(p)); |
||||
if (missing.length === 0) |
||||
return profiles; |
||||
const relayList = relays || [ |
||||
...config.defaultRelays, |
||||
...config.profileRelays |
||||
]; |
||||
const events = await nostrClient.fetchEvents([{ kinds: [0], authors: missing, limit: 1 }], relayList, { useCache: true, cacheResults: true }); |
||||
for (const event of events) { |
||||
await cacheProfile(event); |
||||
profiles.set(event.pubkey, parseProfile(event)); |
||||
} |
||||
return profiles; |
||||
} |
||||
//# sourceMappingURL=profile-fetcher.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"profile-fetcher.js","sourceRoot":"","sources":["profile-fetcher.ts"],"names":[],"mappings":"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"} |
||||
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
/** |
||||
* Relay list fetcher (kind 10002 and 10432) |
||||
*/ |
||||
import { nostrClient } from '../nostr/applesauce-client.js'; |
||||
import { config } from '../nostr/config.js'; |
||||
/** |
||||
* Parse relay list from event |
||||
*/ |
||||
export function parseRelayList(event) { |
||||
const relays = []; |
||||
for (const tag of event.tags) { |
||||
if (tag[0] === 'r' && tag[1]) { |
||||
const url = tag[1]; |
||||
const markers = tag.slice(2); |
||||
// If no markers, relay is both read and write
|
||||
if (markers.length === 0) { |
||||
relays.push({ url, read: true, write: true }); |
||||
continue; |
||||
} |
||||
// Check for explicit markers
|
||||
const hasRead = markers.includes('read'); |
||||
const hasWrite = markers.includes('write'); |
||||
// If only 'read' marker: read=true, write=false
|
||||
// If only 'write' marker: read=false, write=true
|
||||
// If both or neither explicitly: both true (default behavior)
|
||||
const read = hasRead || (!hasRead && !hasWrite); |
||||
const write = hasWrite || (!hasRead && !hasWrite); |
||||
relays.push({ url, read, write }); |
||||
} |
||||
} |
||||
return relays; |
||||
} |
||||
/** |
||||
* Fetch relay lists for a pubkey (kind 10002 and 10432) |
||||
*/ |
||||
export async function fetchRelayLists(pubkey, relays) { |
||||
const relayList = relays || [ |
||||
...config.defaultRelays, |
||||
...config.profileRelays |
||||
]; |
||||
// Fetch both kind 10002 and 10432
|
||||
const events = await nostrClient.fetchEvents([ |
||||
{ kinds: [10002], authors: [pubkey], limit: 1 }, |
||||
{ kinds: [10432], authors: [pubkey], limit: 1 } |
||||
], relayList, { useCache: true, cacheResults: true }); |
||||
const inbox = []; |
||||
const outbox = []; |
||||
for (const event of events) { |
||||
const relayInfos = parseRelayList(event); |
||||
for (const info of relayInfos) { |
||||
if (info.read && !inbox.includes(info.url)) { |
||||
inbox.push(info.url); |
||||
} |
||||
if (info.write && !outbox.includes(info.url)) { |
||||
outbox.push(info.url); |
||||
} |
||||
} |
||||
} |
||||
// Deduplicate
|
||||
return { |
||||
inbox: [...new Set(inbox)], |
||||
outbox: [...new Set(outbox)] |
||||
}; |
||||
} |
||||
//# sourceMappingURL=relay-list-fetcher.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"relay-list-fetcher.js","sourceRoot":"","sources":["relay-list-fetcher.ts"],"names":[],"mappings":"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"} |
||||
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
/** |
||||
* Session manager for active user sessions |
||||
*/ |
||||
// Simple store implementation for Svelte reactivity
|
||||
function createStore(initial) { |
||||
let value = initial; |
||||
const subscribers = new Set(); |
||||
return { |
||||
get value() { |
||||
return value; |
||||
}, |
||||
set(newValue) { |
||||
value = newValue; |
||||
subscribers.forEach((fn) => fn(value)); |
||||
}, |
||||
subscribe(fn) { |
||||
subscribers.add(fn); |
||||
fn(value); |
||||
return () => subscribers.delete(fn); |
||||
} |
||||
}; |
||||
} |
||||
class SessionManager { |
||||
currentSession = null; |
||||
session = createStore(null); |
||||
/** |
||||
* Set current session |
||||
*/ |
||||
setSession(session) { |
||||
this.currentSession = session; |
||||
this.session.set(session); |
||||
// Store in localStorage for persistence
|
||||
if (typeof window !== 'undefined') { |
||||
localStorage.setItem('aitherboard_session', JSON.stringify({ |
||||
pubkey: session.pubkey, |
||||
method: session.method, |
||||
createdAt: session.createdAt |
||||
})); |
||||
} |
||||
} |
||||
/** |
||||
* Get current session |
||||
*/ |
||||
getSession() { |
||||
return this.currentSession; |
||||
} |
||||
/** |
||||
* Check if user is logged in |
||||
*/ |
||||
isLoggedIn() { |
||||
return this.currentSession !== null; |
||||
} |
||||
/** |
||||
* Get current pubkey |
||||
*/ |
||||
getCurrentPubkey() { |
||||
return this.currentSession?.pubkey || null; |
||||
} |
||||
/** |
||||
* Sign event with current session |
||||
*/ |
||||
async signEvent(event) { |
||||
if (!this.currentSession) { |
||||
throw new Error('No active session'); |
||||
} |
||||
return this.currentSession.signer(event); |
||||
} |
||||
/** |
||||
* Clear session |
||||
*/ |
||||
clearSession() { |
||||
this.currentSession = null; |
||||
this.session.set(null); |
||||
if (typeof window !== 'undefined') { |
||||
localStorage.removeItem('aitherboard_session'); |
||||
} |
||||
} |
||||
/** |
||||
* Restore session from localStorage |
||||
*/ |
||||
async restoreSession() { |
||||
if (typeof window === 'undefined') |
||||
return false; |
||||
const stored = localStorage.getItem('aitherboard_session'); |
||||
if (!stored) |
||||
return false; |
||||
try { |
||||
const data = JSON.parse(stored); |
||||
// Session restoration would require re-initializing the signer
|
||||
// This is simplified - full implementation would restore the signer
|
||||
return false; |
||||
} |
||||
catch { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
export const sessionManager = new SessionManager(); |
||||
//# sourceMappingURL=session-manager.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["session-manager.ts"],"names":[],"mappings":"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"} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
/** |
||||
* User preferences fetcher |
||||
* Placeholder for future user preference events |
||||
*/ |
||||
/** |
||||
* Fetch user preferences |
||||
*/ |
||||
export async function fetchUserPreferences(pubkey) { |
||||
// Placeholder - would fetch preference events
|
||||
return null; |
||||
} |
||||
//# sourceMappingURL=user-preferences-fetcher.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"user-preferences-fetcher.js","sourceRoot":"","sources":["user-preferences-fetcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAAc;IACvD,8CAA8C;IAC9C,OAAO,IAAI,CAAC;AACd,CAAC"} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
/** |
||||
* User status fetcher (kind 30315, NIP-38) |
||||
*/ |
||||
import { nostrClient } from '../nostr/applesauce-client.js'; |
||||
import { config } from '../nostr/config.js'; |
||||
/** |
||||
* Parse user status from kind 30315 event |
||||
*/ |
||||
export function parseUserStatus(event) { |
||||
if (event.kind !== 30315) |
||||
return null; |
||||
// Check for d tag with value "general"
|
||||
const dTag = event.tags.find((t) => t[0] === 'd' && t[1] === 'general'); |
||||
if (!dTag) |
||||
return null; |
||||
return event.content || null; |
||||
} |
||||
/** |
||||
* Fetch user status for a pubkey |
||||
*/ |
||||
export async function fetchUserStatus(pubkey, relays) { |
||||
const relayList = relays || [ |
||||
...config.defaultRelays, |
||||
...config.profileRelays |
||||
]; |
||||
const events = await nostrClient.fetchEvents([ |
||||
{ |
||||
kinds: [30315], |
||||
authors: [pubkey], |
||||
'#d': ['general'], |
||||
limit: 1 |
||||
} |
||||
], relayList, { useCache: true, cacheResults: true }); |
||||
if (events.length === 0) |
||||
return null; |
||||
return parseUserStatus(events[0]); |
||||
} |
||||
//# sourceMappingURL=user-status-fetcher.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"user-status-fetcher.js","sourceRoot":"","sources":["user-status-fetcher.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAEtC,uCAAuC;IACvC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACxE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,OAAO,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAc,EACd,MAAiB;IAEjB,MAAM,SAAS,GAAG,MAAM,IAAI;QAC1B,GAAG,MAAM,CAAC,aAAa;QACvB,GAAG,MAAM,CAAC,aAAa;KACxB,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,WAAW,CAC1C;QACE;YACE,KAAK,EAAE,CAAC,KAAK,CAAC;YACd,OAAO,EAAE,CAAC,MAAM,CAAC;YACjB,IAAI,EAAE,CAAC,SAAS,CAAC;YACjB,KAAK,EAAE,CAAC;SACT;KACF,EACD,SAAS,EACT,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CACvC,CAAC;IAEF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC"} |
||||
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
/** |
||||
* Anonymous key storage (NIP-49 encrypted) |
||||
*/ |
||||
import { getDB } from './indexeddb-store.js'; |
||||
import { encryptPrivateKey, decryptPrivateKey } from '../security/key-management.js'; |
||||
/** |
||||
* Store an anonymous key (encrypted) |
||||
*/ |
||||
export async function storeAnonymousKey(nsec, password, pubkey) { |
||||
const ncryptsec = await encryptPrivateKey(nsec, password); |
||||
const db = await getDB(); |
||||
const stored = { |
||||
id: pubkey, |
||||
ncryptsec, |
||||
pubkey, |
||||
created_at: Date.now() |
||||
}; |
||||
await db.put('keys', stored); |
||||
} |
||||
/** |
||||
* Retrieve and decrypt an anonymous key |
||||
*/ |
||||
export async function getAnonymousKey(pubkey, password) { |
||||
const db = await getDB(); |
||||
const stored = await db.get('keys', pubkey); |
||||
if (!stored) |
||||
return null; |
||||
const key = stored; |
||||
return decryptPrivateKey(key.ncryptsec, password); |
||||
} |
||||
/** |
||||
* List all stored anonymous keys (pubkeys only) |
||||
*/ |
||||
export async function listAnonymousKeys() { |
||||
const db = await getDB(); |
||||
const keys = []; |
||||
const tx = db.transaction('keys', 'readonly'); |
||||
for await (const cursor of tx.store.iterate()) { |
||||
const key = cursor.value; |
||||
keys.push(key.pubkey); |
||||
} |
||||
await tx.done; |
||||
return keys; |
||||
} |
||||
/** |
||||
* Delete an anonymous key |
||||
*/ |
||||
export async function deleteAnonymousKey(pubkey) { |
||||
const db = await getDB(); |
||||
await db.delete('keys', pubkey); |
||||
} |
||||
//# sourceMappingURL=anonymous-key-store.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"anonymous-key-store.js","sourceRoot":"","sources":["anonymous-key-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AASrF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAY,EACZ,QAAgB,EAChB,MAAc;IAEd,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC1D,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,MAAM,MAAM,GAAuB;QACjC,EAAE,EAAE,MAAM;QACV,SAAS;QACT,MAAM;QACN,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;KACvB,CAAC;IACF,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAc,EACd,QAAgB;IAEhB,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,GAAG,GAAG,MAA4B,CAAC;IACzC,OAAO,iBAAiB,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAE9C,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,KAA2B,CAAC;QAC/C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAED,MAAM,EAAE,CAAC,IAAI,CAAC;IACd,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc;IACrD,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAClC,CAAC"} |
||||
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
/** |
||||
* Event caching with IndexedDB |
||||
*/ |
||||
import { getDB } from './indexeddb-store.js'; |
||||
/** |
||||
* Store an event in cache |
||||
*/ |
||||
export async function cacheEvent(event) { |
||||
const db = await getDB(); |
||||
const cached = { |
||||
...event, |
||||
cached_at: Date.now() |
||||
}; |
||||
await db.put('events', cached); |
||||
} |
||||
/** |
||||
* Store multiple events in cache |
||||
*/ |
||||
export async function cacheEvents(events) { |
||||
const db = await getDB(); |
||||
const tx = db.transaction('events', 'readwrite'); |
||||
for (const event of events) { |
||||
const cached = { |
||||
...event, |
||||
cached_at: Date.now() |
||||
}; |
||||
await tx.store.put(cached); |
||||
} |
||||
await tx.done; |
||||
} |
||||
/** |
||||
* Get event by ID from cache |
||||
*/ |
||||
export async function getEvent(id) { |
||||
const db = await getDB(); |
||||
return db.get('events', id); |
||||
} |
||||
/** |
||||
* Get events by kind |
||||
*/ |
||||
export async function getEventsByKind(kind, limit) { |
||||
const db = await getDB(); |
||||
const tx = db.transaction('events', 'readonly'); |
||||
const index = tx.store.index('kind'); |
||||
const events = []; |
||||
let count = 0; |
||||
for await (const cursor of index.iterate(kind)) { |
||||
if (limit && count >= limit) |
||||
break; |
||||
events.push(cursor.value); |
||||
count++; |
||||
} |
||||
await tx.done; |
||||
return events.sort((a, b) => b.created_at - a.created_at); |
||||
} |
||||
/** |
||||
* Get events by pubkey |
||||
*/ |
||||
export async function getEventsByPubkey(pubkey, limit) { |
||||
const db = await getDB(); |
||||
const tx = db.transaction('events', 'readonly'); |
||||
const index = tx.store.index('pubkey'); |
||||
const events = []; |
||||
let count = 0; |
||||
for await (const cursor of index.iterate(pubkey)) { |
||||
if (limit && count >= limit) |
||||
break; |
||||
events.push(cursor.value); |
||||
count++; |
||||
} |
||||
await tx.done; |
||||
return events.sort((a, b) => b.created_at - a.created_at); |
||||
} |
||||
/** |
||||
* Clear old events (older than specified timestamp) |
||||
*/ |
||||
export async function clearOldEvents(olderThan) { |
||||
const db = await getDB(); |
||||
const tx = db.transaction('events', 'readwrite'); |
||||
const index = tx.store.index('created_at'); |
||||
for await (const cursor of index.iterate()) { |
||||
if (cursor.value.created_at < olderThan) { |
||||
await cursor.delete(); |
||||
} |
||||
} |
||||
await tx.done; |
||||
} |
||||
//# sourceMappingURL=event-cache.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"event-cache.js","sourceRoot":"","sources":["event-cache.ts"],"names":[],"mappings":"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"} |
||||
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
/** |
||||
* Base IndexedDB store operations |
||||
*/ |
||||
import { openDB } from 'idb'; |
||||
const DB_NAME = 'aitherboard'; |
||||
const DB_VERSION = 1; |
||||
let dbInstance = null; |
||||
/** |
||||
* Get or create database instance |
||||
*/ |
||||
export async function getDB() { |
||||
if (dbInstance) |
||||
return dbInstance; |
||||
dbInstance = await openDB(DB_NAME, DB_VERSION, { |
||||
upgrade(db) { |
||||
// Events store
|
||||
if (!db.objectStoreNames.contains('events')) { |
||||
const eventStore = db.createObjectStore('events', { keyPath: 'id' }); |
||||
eventStore.createIndex('kind', 'kind', { unique: false }); |
||||
eventStore.createIndex('pubkey', 'pubkey', { unique: false }); |
||||
eventStore.createIndex('created_at', 'created_at', { unique: false }); |
||||
} |
||||
// Profiles store
|
||||
if (!db.objectStoreNames.contains('profiles')) { |
||||
db.createObjectStore('profiles', { keyPath: 'pubkey' }); |
||||
} |
||||
// Keys store
|
||||
if (!db.objectStoreNames.contains('keys')) { |
||||
db.createObjectStore('keys', { keyPath: 'id' }); |
||||
} |
||||
// Search index store
|
||||
if (!db.objectStoreNames.contains('search')) { |
||||
db.createObjectStore('search', { keyPath: 'id' }); |
||||
} |
||||
} |
||||
}); |
||||
return dbInstance; |
||||
} |
||||
/** |
||||
* Close database connection |
||||
*/ |
||||
export async function closeDB() { |
||||
if (dbInstance) { |
||||
dbInstance.close(); |
||||
dbInstance = null; |
||||
} |
||||
} |
||||
//# sourceMappingURL=indexeddb-store.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"indexeddb-store.js","sourceRoot":"","sources":["indexeddb-store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,KAAK,CAAC;AAEhD,MAAM,OAAO,GAAG,aAAa,CAAC;AAC9B,MAAM,UAAU,GAAG,CAAC,CAAC;AAsBrB,IAAI,UAAU,GAAwC,IAAI,CAAC;AAE3D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAElC,UAAU,GAAG,MAAM,MAAM,CAAiB,OAAO,EAAE,UAAU,EAAE;QAC7D,OAAO,CAAC,EAAE;YACR,eAAe;YACf,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5C,MAAM,UAAU,GAAG,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBACrE,UAAU,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC1D,UAAU,CAAC,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC9D,UAAU,CAAC,WAAW,CAAC,YAAY,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YACxE,CAAC;YAED,iBAAiB;YACjB,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC9C,EAAE,CAAC,iBAAiB,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC1D,CAAC;YAED,aAAa;YACb,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1C,EAAE,CAAC,iBAAiB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAClD,CAAC;YAED,qBAAqB;YACrB,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5C,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,IAAI,UAAU,EAAE,CAAC;QACf,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,UAAU,GAAG,IAAI,CAAC;IACpB,CAAC;AACH,CAAC"} |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/** |
||||
* Profile caching (kind 0 events) |
||||
*/ |
||||
import { getDB } from './indexeddb-store.js'; |
||||
/** |
||||
* Store a profile in cache |
||||
*/ |
||||
export async function cacheProfile(event) { |
||||
if (event.kind !== 0) |
||||
throw new Error('Not a profile event'); |
||||
const db = await getDB(); |
||||
const cached = { |
||||
pubkey: event.pubkey, |
||||
event, |
||||
cached_at: Date.now() |
||||
}; |
||||
await db.put('profiles', cached); |
||||
} |
||||
/** |
||||
* Get profile by pubkey from cache |
||||
*/ |
||||
export async function getProfile(pubkey) { |
||||
const db = await getDB(); |
||||
return db.get('profiles', pubkey); |
||||
} |
||||
/** |
||||
* Get multiple profiles |
||||
*/ |
||||
export async function getProfiles(pubkeys) { |
||||
const db = await getDB(); |
||||
const profiles = new Map(); |
||||
const tx = db.transaction('profiles', 'readonly'); |
||||
for (const pubkey of pubkeys) { |
||||
const profile = await tx.store.get(pubkey); |
||||
if (profile) { |
||||
profiles.set(pubkey, profile); |
||||
} |
||||
} |
||||
await tx.done; |
||||
return profiles; |
||||
} |
||||
//# sourceMappingURL=profile-cache.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"profile-cache.js","sourceRoot":"","sources":["profile-cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAS7C;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAiB;IAClD,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,MAAM,MAAM,GAAkB;QAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK;QACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACtB,CAAC;IACF,MAAM,EAAE,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc;IAC7C,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,OAAO,EAAE,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAiB;IACjD,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAClD,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAElD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,OAAO,EAAE,CAAC;YACZ,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,MAAM,EAAE,CAAC,IAAI,CAAC;IACd,OAAO,QAAQ,CAAC;AAClB,CAAC"} |
||||
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/** |
||||
* Full-text search index (deferred implementation) |
||||
*/ |
||||
import { getDB } from './indexeddb-store.js'; |
||||
/** |
||||
* Index event content for search |
||||
*/ |
||||
export async function indexEvent(eventId, content) { |
||||
// Placeholder - full implementation would:
|
||||
// 1. Tokenize content
|
||||
// 2. Create inverted index
|
||||
// 3. Store in IndexedDB
|
||||
const db = await getDB(); |
||||
await db.put('search', { |
||||
id: eventId, |
||||
content: content.toLowerCase() |
||||
}); |
||||
} |
||||
/** |
||||
* Search events by query |
||||
*/ |
||||
export async function searchEvents(query, limit = 50) { |
||||
// Placeholder - full implementation would:
|
||||
// 1. Tokenize query
|
||||
// 2. Look up in inverted index
|
||||
// 3. Rank results
|
||||
// 4. Return event IDs
|
||||
const db = await getDB(); |
||||
const results = []; |
||||
const lowerQuery = query.toLowerCase(); |
||||
const tx = db.transaction('search', 'readonly'); |
||||
for await (const cursor of tx.store.iterate()) { |
||||
if (results.length >= limit) |
||||
break; |
||||
const content = cursor.value.content; |
||||
if (content.includes(lowerQuery)) { |
||||
results.push(cursor.key); |
||||
} |
||||
} |
||||
await tx.done; |
||||
return results; |
||||
} |
||||
//# sourceMappingURL=search-index.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"search-index.js","sourceRoot":"","sources":["search-index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE7C;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAe,EAAE,OAAe;IAC/D,2CAA2C;IAC3C,sBAAsB;IACtB,2BAA2B;IAC3B,wBAAwB;IACxB,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,MAAM,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE;QACrB,EAAE,EAAE,OAAO;QACX,OAAO,EAAE,OAAO,CAAC,WAAW,EAAE;KAC/B,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa,EAAE,QAAgB,EAAE;IAClE,2CAA2C;IAC3C,oBAAoB;IACpB,+BAA+B;IAC/B,kBAAkB;IAClB,sBAAsB;IACtB,MAAM,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC;IACzB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACvC,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAEhD,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,IAAI,OAAO,CAAC,MAAM,IAAI,KAAK;YAAE,MAAM;QACnC,MAAM,OAAO,GAAI,MAAM,CAAC,KAA6B,CAAC,OAAO,CAAC;QAC9D,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,GAAa,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,MAAM,EAAE,CAAC,IAAI,CAAC;IACd,OAAO,OAAO,CAAC;AACjB,CAAC"} |
||||
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/** |
||||
* Applesauce-core client wrapper |
||||
* Main interface for Nostr operations |
||||
*/ |
||||
import { initializeRelayPool, relayPool } from './relay-pool.js'; |
||||
import { subscriptionManager } from './subscription-manager.js'; |
||||
import { eventStore } from './event-store.js'; |
||||
import { config } from './config.js'; |
||||
class ApplesauceClient { |
||||
initialized = false; |
||||
/** |
||||
* Initialize the client |
||||
*/ |
||||
async initialize() { |
||||
if (this.initialized) |
||||
return; |
||||
await initializeRelayPool(); |
||||
this.initialized = true; |
||||
} |
||||
/** |
||||
* Publish an event to relays |
||||
*/ |
||||
async publish(event, options = {}) { |
||||
const relays = options.relays || relayPool.getConnectedRelays(); |
||||
const message = JSON.stringify(['EVENT', event]); |
||||
const results = { |
||||
success: [], |
||||
failed: [] |
||||
}; |
||||
for (const relay of relays) { |
||||
try { |
||||
const sent = relayPool.send(relay, message); |
||||
if (sent) { |
||||
results.success.push(relay); |
||||
} |
||||
else { |
||||
results.failed.push({ relay, error: 'Not connected' }); |
||||
} |
||||
} |
||||
catch (error) { |
||||
results.failed.push({ |
||||
relay, |
||||
error: error instanceof Error ? error.message : 'Unknown error' |
||||
}); |
||||
} |
||||
} |
||||
// Store in cache
|
||||
if (results.success.length > 0) { |
||||
await eventStore.storeEvent(event); |
||||
} |
||||
return results; |
||||
} |
||||
/** |
||||
* Subscribe to events |
||||
*/ |
||||
subscribe(filters, relays, onEvent, onEose) { |
||||
const subId = subscriptionManager.generateSubId(); |
||||
subscriptionManager.subscribe(subId, relays, filters, onEvent, onEose); |
||||
return subId; |
||||
} |
||||
/** |
||||
* Unsubscribe |
||||
*/ |
||||
unsubscribe(subId) { |
||||
subscriptionManager.unsubscribe(subId); |
||||
} |
||||
/** |
||||
* Fetch events |
||||
*/ |
||||
async fetchEvents(filters, relays, options) { |
||||
return eventStore.fetchEvents(filters, relays, options || {}); |
||||
} |
||||
/** |
||||
* Get event by ID |
||||
*/ |
||||
async getEventById(id, relays) { |
||||
return eventStore.getEventById(id, relays); |
||||
} |
||||
/** |
||||
* Get relay pool |
||||
*/ |
||||
getRelayPool() { |
||||
return relayPool; |
||||
} |
||||
/** |
||||
* Get config |
||||
*/ |
||||
getConfig() { |
||||
return config; |
||||
} |
||||
/** |
||||
* Close all connections |
||||
*/ |
||||
close() { |
||||
subscriptionManager.closeAll(); |
||||
relayPool.closeAll(); |
||||
this.initialized = false; |
||||
} |
||||
} |
||||
export const nostrClient = new ApplesauceClient(); |
||||
//# sourceMappingURL=applesauce-client.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"applesauce-client.js","sourceRoot":"","sources":["applesauce-client.ts"],"names":[],"mappings":"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"} |
||||
@ -0,0 +1,119 @@
@@ -0,0 +1,119 @@
|
||||
/** |
||||
* Unified authentication handler |
||||
*/ |
||||
import { signEventWithNIP07, getPublicKeyWithNIP07 } from '../auth/nip07-signer.js'; |
||||
import { signEventWithNsec, getPublicKeyFromNsec } from '../auth/nsec-signer.js'; |
||||
import { signEventWithBunker, connectBunker } from '../auth/bunker-signer.js'; |
||||
import { signEventWithAnonymous, generateAnonymousKey } from '../auth/anonymous-signer.js'; |
||||
import { decryptPrivateKey } from '../security/key-management.js'; |
||||
import { sessionManager } from '../auth/session-manager.js'; |
||||
import { fetchRelayLists } from '../auth/relay-list-fetcher.js'; |
||||
import { eventStore } from './event-store.js'; |
||||
import { nostrClient } from './applesauce-client.js'; |
||||
/** |
||||
* Authenticate with NIP-07 |
||||
*/ |
||||
export async function authenticateWithNIP07() { |
||||
const pubkey = await getPublicKeyWithNIP07(); |
||||
sessionManager.setSession({ |
||||
pubkey, |
||||
method: 'nip07', |
||||
signer: signEventWithNIP07, |
||||
createdAt: Date.now() |
||||
}); |
||||
// Fetch user relay lists and mute list
|
||||
await loadUserPreferences(pubkey); |
||||
return pubkey; |
||||
} |
||||
/** |
||||
* Authenticate with nsec |
||||
*/ |
||||
export async function authenticateWithNsec(ncryptsec, password) { |
||||
// Decrypt the encrypted private key
|
||||
const nsec = await decryptPrivateKey(ncryptsec, password); |
||||
// Derive public key from private key
|
||||
const pubkey = await getPublicKeyFromNsec(nsec); |
||||
sessionManager.setSession({ |
||||
pubkey, |
||||
method: 'nsec', |
||||
signer: async (event) => signEventWithNsec(event, ncryptsec, password), |
||||
createdAt: Date.now() |
||||
}); |
||||
await loadUserPreferences(pubkey); |
||||
return pubkey; |
||||
} |
||||
/** |
||||
* Authenticate with bunker |
||||
*/ |
||||
export async function authenticateWithBunker(bunkerUri) { |
||||
const connection = await connectBunker(bunkerUri); |
||||
sessionManager.setSession({ |
||||
pubkey: connection.pubkey, |
||||
method: 'bunker', |
||||
signer: async (event) => signEventWithBunker(event, connection), |
||||
createdAt: Date.now() |
||||
}); |
||||
await loadUserPreferences(connection.pubkey); |
||||
return connection.pubkey; |
||||
} |
||||
/** |
||||
* Authenticate as anonymous |
||||
*/ |
||||
export async function authenticateAsAnonymous(password) { |
||||
const { pubkey, nsec } = await generateAnonymousKey(password); |
||||
// Store the key for later use
|
||||
// In practice, we'd need to store the ncryptsec and decrypt when needed
|
||||
// For now, this is simplified
|
||||
sessionManager.setSession({ |
||||
pubkey, |
||||
method: 'anonymous', |
||||
signer: async (event) => { |
||||
// Simplified - would decrypt and sign
|
||||
return signEventWithAnonymous(event, pubkey, password); |
||||
}, |
||||
createdAt: Date.now() |
||||
}); |
||||
return pubkey; |
||||
} |
||||
/** |
||||
* Load user preferences (relay lists, mute list, blocked relays) |
||||
*/ |
||||
async function loadUserPreferences(pubkey) { |
||||
// Fetch relay lists
|
||||
const { inbox, outbox } = await fetchRelayLists(pubkey); |
||||
// Relay lists would be used by relay selection logic
|
||||
// Fetch mute list (kind 10000)
|
||||
const muteEvents = await nostrClient.fetchEvents([{ kinds: [10000], authors: [pubkey], limit: 1 }], [...nostrClient.getConfig().defaultRelays, ...nostrClient.getConfig().profileRelays], { useCache: true, cacheResults: true }); |
||||
if (muteEvents.length > 0) { |
||||
const mutedPubkeys = muteEvents[0].tags |
||||
.filter((t) => t[0] === 'p') |
||||
.map((t) => t[1]) |
||||
.filter(Boolean); |
||||
eventStore.setMuteList(mutedPubkeys); |
||||
} |
||||
// Fetch blocked relays (kind 10006)
|
||||
const blockedRelayEvents = await nostrClient.fetchEvents([{ kinds: [10006], authors: [pubkey], limit: 1 }], [...nostrClient.getConfig().defaultRelays, ...nostrClient.getConfig().profileRelays], { useCache: true, cacheResults: true }); |
||||
if (blockedRelayEvents.length > 0) { |
||||
const blockedRelays = blockedRelayEvents[0].tags |
||||
.filter((t) => t[0] === 'relay') |
||||
.map((t) => t[1]) |
||||
.filter(Boolean); |
||||
eventStore.setBlockedRelays(blockedRelays); |
||||
} |
||||
} |
||||
/** |
||||
* Sign and publish event |
||||
*/ |
||||
export async function signAndPublish(event, relays) { |
||||
const signed = await sessionManager.signEvent(event); |
||||
return nostrClient.publish(signed, { relays }); |
||||
} |
||||
/** |
||||
* Logout |
||||
*/ |
||||
export function logout() { |
||||
sessionManager.clearSession(); |
||||
eventStore.setMuteList([]); |
||||
eventStore.setBlockedRelays([]); |
||||
} |
||||
//# sourceMappingURL=auth-handler.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth-handler.js","sourceRoot":"","sources":["auth-handler.ts"],"names":[],"mappings":"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"} |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/** |
||||
* Configuration for Nostr services |
||||
* Handles environment variables and defaults |
||||
*/ |
||||
const DEFAULT_RELAYS = [ |
||||
'wss://theforest.nostr1.com', |
||||
'wss://nostr21.com', |
||||
'wss://nostr.land', |
||||
'wss://nostr.wine', |
||||
'wss://nostr.sovbit.host' |
||||
]; |
||||
const PROFILE_RELAYS = [ |
||||
'wss://relay.damus.io', |
||||
'wss://aggr.nostr.land', |
||||
'wss://profiles.nostr1.com' |
||||
]; |
||||
function parseRelays(envVar, fallback) { |
||||
if (!envVar) |
||||
return fallback; |
||||
const relays = envVar |
||||
.split(',') |
||||
.map((r) => r.trim()) |
||||
.filter((r) => r.length > 0); |
||||
return relays.length > 0 ? relays : fallback; |
||||
} |
||||
function parseIntEnv(envVar, fallback, min = 0) { |
||||
if (!envVar) |
||||
return fallback; |
||||
const parsed = parseInt(envVar, 10); |
||||
if (isNaN(parsed) || parsed < min) |
||||
return fallback; |
||||
return parsed; |
||||
} |
||||
function parseBoolEnv(envVar, fallback) { |
||||
if (!envVar) |
||||
return fallback; |
||||
return envVar.toLowerCase() === 'true' || envVar === '1'; |
||||
} |
||||
export function getConfig() { |
||||
return { |
||||
defaultRelays: parseRelays(import.meta.env.VITE_DEFAULT_RELAYS, DEFAULT_RELAYS), |
||||
profileRelays: PROFILE_RELAYS, |
||||
zapThreshold: parseIntEnv(import.meta.env.VITE_ZAP_THRESHOLD, 1, 0), |
||||
threadTimeoutDays: parseIntEnv(import.meta.env.VITE_THREAD_TIMEOUT_DAYS, 30), |
||||
pwaEnabled: parseBoolEnv(import.meta.env.VITE_PWA_ENABLED, true) |
||||
}; |
||||
} |
||||
export const config = getConfig(); |
||||
//# sourceMappingURL=config.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config.js","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,cAAc,GAAG;IACrB,4BAA4B;IAC5B,mBAAmB;IACnB,kBAAkB;IAClB,kBAAkB;IAClB,yBAAyB;CAC1B,CAAC;AAEF,MAAM,cAAc,GAAG;IACrB,sBAAsB;IACtB,uBAAuB;IACvB,2BAA2B;CAC5B,CAAC;AAUF,SAAS,WAAW,CAAC,MAA0B,EAAE,QAAkB;IACjE,IAAI,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM;SAClB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC/C,CAAC;AAED,SAAS,WAAW,CAAC,MAA0B,EAAE,QAAgB,EAAE,MAAc,CAAC;IAChF,IAAI,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAC7B,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACpC,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,GAAG;QAAE,OAAO,QAAQ,CAAC;IACnD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,YAAY,CAAC,MAA0B,EAAE,QAAiB;IACjE,IAAI,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAC7B,OAAO,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM,IAAI,MAAM,KAAK,GAAG,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,cAAc,CAAC;QAC/E,aAAa,EAAE,cAAc;QAC7B,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB,EAAE,EAAE,CAAC;QAC5E,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC;KACjE,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC"} |
||||
@ -0,0 +1,213 @@
@@ -0,0 +1,213 @@
|
||||
/** |
||||
* Event store with IndexedDB caching and filtering |
||||
*/ |
||||
import { cacheEvent, cacheEvents, getEvent, getEventsByKind, getEventsByPubkey } from '../cache/event-cache.js'; |
||||
import { subscriptionManager } from './subscription-manager.js'; |
||||
class EventStore { |
||||
muteList = new Set(); |
||||
blockedRelays = new Set(); |
||||
activityTracker = new Map(); // pubkey -> last activity timestamp
|
||||
/** |
||||
* Update mute list |
||||
*/ |
||||
setMuteList(pubkeys) { |
||||
this.muteList = new Set(pubkeys); |
||||
} |
||||
/** |
||||
* Update blocked relays |
||||
*/ |
||||
setBlockedRelays(relays) { |
||||
this.blockedRelays = new Set(relays); |
||||
} |
||||
/** |
||||
* Filter out muted events |
||||
*/ |
||||
isMuted(event) { |
||||
return this.muteList.has(event.pubkey); |
||||
} |
||||
/** |
||||
* Filter out blocked relays |
||||
*/ |
||||
filterBlockedRelays(relays) { |
||||
return relays.filter((r) => !this.blockedRelays.has(r)); |
||||
} |
||||
/** |
||||
* Track activity for a pubkey |
||||
*/ |
||||
trackActivity(pubkey, timestamp) { |
||||
const current = this.activityTracker.get(pubkey) || 0; |
||||
if (timestamp > current) { |
||||
this.activityTracker.set(pubkey, timestamp); |
||||
} |
||||
} |
||||
/** |
||||
* Get last activity timestamp for a pubkey |
||||
*/ |
||||
getLastActivity(pubkey) { |
||||
return this.activityTracker.get(pubkey); |
||||
} |
||||
/** |
||||
* Check if event should be hidden (content filtering) |
||||
*/ |
||||
shouldHideEvent(event) { |
||||
// Check for content-warning or sensitive tags
|
||||
const hasContentWarning = event.tags.some((t) => t[0] === 'content-warning' || t[0] === 'sensitive'); |
||||
if (hasContentWarning) |
||||
return true; |
||||
// Check for #NSFW in content or tags
|
||||
const content = event.content.toLowerCase(); |
||||
const hasNSFW = content.includes('#nsfw') || event.tags.some((t) => t[1]?.toLowerCase() === 'nsfw'); |
||||
if (hasNSFW) |
||||
return true; |
||||
return false; |
||||
} |
||||
/** |
||||
* Fetch events with filters |
||||
*/ |
||||
async fetchEvents(filters, relays, options = {}) { |
||||
const { useCache = true, cacheResults = true } = options; |
||||
// Filter out blocked relays
|
||||
const filteredRelays = this.filterBlockedRelays(relays); |
||||
// Try cache first if enabled
|
||||
if (useCache) { |
||||
// Simple cache lookup - could be improved
|
||||
const cachedEvents = []; |
||||
for (const filter of filters) { |
||||
if (filter.kinds && filter.kinds.length === 1) { |
||||
const events = await getEventsByKind(filter.kinds[0], filter.limit); |
||||
cachedEvents.push(...events); |
||||
} |
||||
if (filter.authors && filter.authors.length === 1) { |
||||
const events = await getEventsByPubkey(filter.authors[0], filter.limit); |
||||
cachedEvents.push(...events); |
||||
} |
||||
} |
||||
if (cachedEvents.length > 0) { |
||||
// Return cached events immediately (progressive loading)
|
||||
// Continue fetching fresh data in background
|
||||
this.fetchEventsFromRelays(filters, filteredRelays, { cacheResults }).catch((error) => { |
||||
console.error('Error fetching fresh events from relays:', error); |
||||
}); |
||||
return this.filterEvents(cachedEvents); |
||||
} |
||||
} |
||||
// Fetch from relays
|
||||
return this.fetchEventsFromRelays(filters, filteredRelays, { cacheResults }); |
||||
} |
||||
/** |
||||
* Fetch events from relays |
||||
*/ |
||||
async fetchEventsFromRelays(filters, relays, options) { |
||||
return new Promise((resolve, reject) => { |
||||
const events = new Map(); |
||||
const subId = subscriptionManager.generateSubId(); |
||||
const relayCount = new Set(); |
||||
let resolved = false; |
||||
let eoseTimeout = null; |
||||
let timeoutId = null; |
||||
const finish = (eventArray) => { |
||||
if (resolved) |
||||
return; |
||||
resolved = true; |
||||
// Clean up timeouts
|
||||
if (timeoutId) { |
||||
clearTimeout(timeoutId); |
||||
timeoutId = null; |
||||
} |
||||
if (eoseTimeout) { |
||||
clearTimeout(eoseTimeout); |
||||
eoseTimeout = null; |
||||
} |
||||
subscriptionManager.unsubscribe(subId); |
||||
resolve(this.filterEvents(eventArray)); |
||||
}; |
||||
const onEvent = (event, relay) => { |
||||
// Skip muted events
|
||||
if (this.isMuted(event)) |
||||
return; |
||||
// Skip hidden events
|
||||
if (this.shouldHideEvent(event)) |
||||
return; |
||||
// Track activity
|
||||
this.trackActivity(event.pubkey, event.created_at); |
||||
// Deduplicate by event ID
|
||||
events.set(event.id, event); |
||||
relayCount.add(relay); |
||||
}; |
||||
const onEose = (relay) => { |
||||
relayCount.add(relay); |
||||
// Wait a bit for all relays to respond
|
||||
if (eoseTimeout) { |
||||
clearTimeout(eoseTimeout); |
||||
} |
||||
eoseTimeout = setTimeout(() => { |
||||
if (!resolved && relayCount.size >= Math.min(relays.length, 3)) { |
||||
// Got responses from enough relays
|
||||
const eventArray = Array.from(events.values()); |
||||
if (options.cacheResults) { |
||||
cacheEvents(eventArray).catch((error) => { |
||||
console.error('Error caching events:', error); |
||||
}); |
||||
} |
||||
finish(eventArray); |
||||
} |
||||
}, 1000); |
||||
}; |
||||
try { |
||||
subscriptionManager.subscribe(subId, relays, filters, onEvent, onEose); |
||||
} |
||||
catch (error) { |
||||
reject(error); |
||||
return; |
||||
} |
||||
// Timeout after 10 seconds
|
||||
timeoutId = setTimeout(() => { |
||||
if (!resolved) { |
||||
const eventArray = Array.from(events.values()); |
||||
if (options.cacheResults) { |
||||
cacheEvents(eventArray).catch((error) => { |
||||
console.error('Error caching events:', error); |
||||
}); |
||||
} |
||||
finish(eventArray); |
||||
} |
||||
}, 10000); |
||||
}); |
||||
} |
||||
/** |
||||
* Filter events (remove muted, hidden, etc.) |
||||
*/ |
||||
filterEvents(events) { |
||||
return events.filter((event) => { |
||||
if (this.isMuted(event)) |
||||
return false; |
||||
if (this.shouldHideEvent(event)) |
||||
return false; |
||||
return true; |
||||
}); |
||||
} |
||||
/** |
||||
* Get event by ID (from cache or fetch) |
||||
*/ |
||||
async getEventById(id, relays) { |
||||
// Try cache first
|
||||
const cached = await getEvent(id); |
||||
if (cached) |
||||
return cached; |
||||
// Fetch from relays
|
||||
const filters = [{ ids: [id] }]; |
||||
const events = await this.fetchEvents(filters, relays, { useCache: false }); |
||||
return events[0] || null; |
||||
} |
||||
/** |
||||
* Store event in cache |
||||
*/ |
||||
async storeEvent(event) { |
||||
if (this.isMuted(event) || this.shouldHideEvent(event)) |
||||
return; |
||||
this.trackActivity(event.pubkey, event.created_at); |
||||
await cacheEvent(event); |
||||
} |
||||
} |
||||
export const eventStore = new EventStore(); |
||||
//# sourceMappingURL=event-store.js.map
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/** |
||||
* Event utilities for creating and signing events |
||||
*/ |
||||
/** |
||||
* Create event ID (SHA256 of serialized event) |
||||
*/ |
||||
export async function createEventId(event) { |
||||
const serialized = JSON.stringify([ |
||||
0, |
||||
event.pubkey, |
||||
event.created_at, |
||||
event.kind, |
||||
event.tags, |
||||
event.content |
||||
]); |
||||
const encoder = new TextEncoder(); |
||||
const data = encoder.encode(serialized); |
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data); |
||||
const hashArray = Array.from(new Uint8Array(hashBuffer)); |
||||
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); |
||||
} |
||||
/** |
||||
* Sign event (placeholder) |
||||
* |
||||
* TEMPORARY: Generates a deterministic signature-like string. |
||||
* This is NOT a valid secp256k1 signature but has the correct length. |
||||
* Production code MUST compute actual secp256k1 signature. |
||||
*/ |
||||
export async function signEvent(event) { |
||||
const id = await createEventId(event); |
||||
// TEMPORARY: Generate deterministic signature-like string (128 chars)
|
||||
const encoder = new TextEncoder(); |
||||
const sigData = encoder.encode(event.pubkey + id); |
||||
const sigHashBuffer = await crypto.subtle.digest('SHA-256', sigData); |
||||
const sigHashArray = Array.from(new Uint8Array(sigHashBuffer)); |
||||
const sigHash = sigHashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); |
||||
// Double the hash to get 128 chars (64 * 2)
|
||||
const sig = (sigHash + sigHash).slice(0, 128); |
||||
return { ...event, id, sig }; |
||||
} |
||||
//# sourceMappingURL=event-utils.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"event-utils.js","sourceRoot":"","sources":["event-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAqC;IACvE,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,CAAC;QACD,KAAK,CAAC,MAAM;QACZ,KAAK,CAAC,UAAU;QAChB,KAAK,CAAC,IAAI;QACV,KAAK,CAAC,IAAI;QACV,KAAK,CAAC,OAAO;KACd,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IACzD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAqC;IAErC,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;IACtC,sEAAsE;IACtE,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAClD,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACrE,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClF,4CAA4C;IAC5C,MAAM,GAAG,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC;AAC/B,CAAC"} |
||||
@ -0,0 +1,181 @@
@@ -0,0 +1,181 @@
|
||||
/** |
||||
* Relay pool management |
||||
* Manages WebSocket connections to Nostr relays |
||||
*/ |
||||
import { config } from './config.js'; |
||||
class RelayPool { |
||||
relays = new Map(); |
||||
status = new Map(); |
||||
statusCallbacks = new Set(); |
||||
reconnectTimeouts = new Map(); |
||||
/** |
||||
* Add relay to pool |
||||
*/ |
||||
async addRelay(url) { |
||||
if (this.relays.has(url)) |
||||
return; |
||||
this.relays.set(url, null); |
||||
this.updateStatus(url, { connected: false }); |
||||
await this.connect(url); |
||||
} |
||||
/** |
||||
* Remove relay from pool |
||||
*/ |
||||
removeRelay(url) { |
||||
const ws = this.relays.get(url); |
||||
if (ws) { |
||||
ws.close(); |
||||
} |
||||
this.relays.delete(url); |
||||
this.status.delete(url); |
||||
const timeout = this.reconnectTimeouts.get(url); |
||||
if (timeout) { |
||||
clearTimeout(timeout); |
||||
this.reconnectTimeouts.delete(url); |
||||
} |
||||
} |
||||
/** |
||||
* Connect to a relay |
||||
*/ |
||||
async connect(url) { |
||||
try { |
||||
const ws = new WebSocket(url); |
||||
const startTime = Date.now(); |
||||
ws.onopen = () => { |
||||
const latency = Date.now() - startTime; |
||||
this.relays.set(url, ws); |
||||
this.updateStatus(url, { |
||||
connected: true, |
||||
latency, |
||||
lastConnected: Date.now() |
||||
}); |
||||
}; |
||||
ws.onerror = (error) => { |
||||
this.updateStatus(url, { |
||||
connected: false, |
||||
lastError: error.message || 'Connection error' |
||||
}); |
||||
this.scheduleReconnect(url); |
||||
}; |
||||
ws.onclose = () => { |
||||
this.relays.set(url, null); |
||||
this.updateStatus(url, { connected: false }); |
||||
this.scheduleReconnect(url); |
||||
}; |
||||
// Store WebSocket for message sending
|
||||
this.relays.set(url, ws); |
||||
} |
||||
catch (error) { |
||||
this.updateStatus(url, { |
||||
connected: false, |
||||
lastError: error instanceof Error ? error.message : 'Unknown error' |
||||
}); |
||||
this.scheduleReconnect(url); |
||||
} |
||||
} |
||||
/** |
||||
* Schedule reconnection attempt |
||||
*/ |
||||
scheduleReconnect(url) { |
||||
const existing = this.reconnectTimeouts.get(url); |
||||
if (existing) |
||||
clearTimeout(existing); |
||||
const timeout = setTimeout(() => { |
||||
this.reconnectTimeouts.delete(url); |
||||
this.connect(url); |
||||
}, 5000); // 5 second delay
|
||||
this.reconnectTimeouts.set(url, timeout); |
||||
} |
||||
/** |
||||
* Update relay status and notify callbacks |
||||
*/ |
||||
updateStatus(url, updates) { |
||||
const current = this.status.get(url) || { url, connected: false }; |
||||
const updated = { ...current, ...updates }; |
||||
this.status.set(url, updated); |
||||
// Notify callbacks
|
||||
this.statusCallbacks.forEach((cb) => cb(updated)); |
||||
} |
||||
/** |
||||
* Get WebSocket for a relay |
||||
*/ |
||||
getRelay(url) { |
||||
return this.relays.get(url) || null; |
||||
} |
||||
/** |
||||
* Get all connected relays |
||||
*/ |
||||
getConnectedRelays() { |
||||
return Array.from(this.relays.entries()) |
||||
.filter(([, ws]) => ws && ws.readyState === WebSocket.OPEN) |
||||
.map(([url]) => url); |
||||
} |
||||
/** |
||||
* Get relay status |
||||
*/ |
||||
getStatus(url) { |
||||
return this.status.get(url); |
||||
} |
||||
/** |
||||
* Get all relay statuses |
||||
*/ |
||||
getAllStatuses() { |
||||
return Array.from(this.status.values()); |
||||
} |
||||
/** |
||||
* Subscribe to status updates |
||||
*/ |
||||
onStatusUpdate(callback) { |
||||
this.statusCallbacks.add(callback); |
||||
return () => this.statusCallbacks.delete(callback); |
||||
} |
||||
/** |
||||
* Send message to relay |
||||
*/ |
||||
send(url, message) { |
||||
const ws = this.relays.get(url); |
||||
if (ws && ws.readyState === WebSocket.OPEN) { |
||||
ws.send(message); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
/** |
||||
* Send message to all connected relays |
||||
*/ |
||||
broadcast(message) { |
||||
const sent = []; |
||||
for (const [url, ws] of this.relays.entries()) { |
||||
if (ws && ws.readyState === WebSocket.OPEN) { |
||||
ws.send(message); |
||||
sent.push(url); |
||||
} |
||||
} |
||||
return sent; |
||||
} |
||||
/** |
||||
* Close all connections |
||||
*/ |
||||
closeAll() { |
||||
for (const [url, ws] of this.relays.entries()) { |
||||
if (ws) { |
||||
ws.close(); |
||||
} |
||||
const timeout = this.reconnectTimeouts.get(url); |
||||
if (timeout) { |
||||
clearTimeout(timeout); |
||||
this.reconnectTimeouts.delete(url); |
||||
} |
||||
} |
||||
this.relays.clear(); |
||||
this.status.clear(); |
||||
} |
||||
} |
||||
export const relayPool = new RelayPool(); |
||||
// Initialize with default relays
|
||||
export async function initializeRelayPool() { |
||||
for (const url of config.defaultRelays) { |
||||
await relayPool.addRelay(url); |
||||
} |
||||
} |
||||
//# sourceMappingURL=relay-pool.js.map
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,119 @@
@@ -0,0 +1,119 @@
|
||||
/** |
||||
* Subscription manager for Nostr subscriptions |
||||
*/ |
||||
import { relayPool } from './relay-pool.js'; |
||||
class SubscriptionManager { |
||||
subscriptions = new Map(); |
||||
nextSubId = 1; |
||||
/** |
||||
* Create a new subscription |
||||
*/ |
||||
subscribe(subId, relays, filters, onEvent, onEose) { |
||||
// Close existing subscription if any
|
||||
this.unsubscribe(subId); |
||||
const subscription = { |
||||
id: subId, |
||||
relays, |
||||
filters, |
||||
onEvent, |
||||
onEose, |
||||
messageHandlers: new Map() |
||||
}; |
||||
// Set up message handlers for each relay
|
||||
for (const relayUrl of relays) { |
||||
const ws = relayPool.getRelay(relayUrl); |
||||
if (!ws) |
||||
continue; |
||||
const handler = (event) => { |
||||
try { |
||||
const data = JSON.parse(event.data); |
||||
if (Array.isArray(data)) { |
||||
const [type, ...rest] = data; |
||||
if (type === 'EVENT' && rest[0] === subId) { |
||||
const event = rest[1]; |
||||
if (this.matchesFilters(event, filters)) { |
||||
onEvent(event, relayUrl); |
||||
} |
||||
} |
||||
else if (type === 'EOSE' && rest[0] === subId) { |
||||
onEose?.(relayUrl); |
||||
} |
||||
} |
||||
} |
||||
catch (error) { |
||||
console.error('Error parsing relay message:', error); |
||||
} |
||||
}; |
||||
ws.addEventListener('message', handler); |
||||
subscription.messageHandlers.set(relayUrl, handler); |
||||
// Send subscription request
|
||||
const message = JSON.stringify(['REQ', subId, ...filters]); |
||||
relayPool.send(relayUrl, message); |
||||
} |
||||
this.subscriptions.set(subId, subscription); |
||||
} |
||||
/** |
||||
* Check if event matches filters |
||||
*/ |
||||
matchesFilters(event, filters) { |
||||
return filters.some((filter) => { |
||||
if (filter.ids && !filter.ids.includes(event.id)) |
||||
return false; |
||||
if (filter.authors && !filter.authors.includes(event.pubkey)) |
||||
return false; |
||||
if (filter.kinds && !filter.kinds.includes(event.kind)) |
||||
return false; |
||||
if (filter.since && event.created_at < filter.since) |
||||
return false; |
||||
if (filter.until && event.created_at > filter.until) |
||||
return false; |
||||
// Tag filters
|
||||
if (filter['#e']) { |
||||
const hasE = event.tags.some((t) => t[0] === 'e' && filter['#e'].includes(t[1])); |
||||
if (!hasE) |
||||
return false; |
||||
} |
||||
if (filter['#p']) { |
||||
const hasP = event.tags.some((t) => t[0] === 'p' && filter['#p'].includes(t[1])); |
||||
if (!hasP) |
||||
return false; |
||||
} |
||||
return true; |
||||
}); |
||||
} |
||||
/** |
||||
* Unsubscribe from a subscription |
||||
*/ |
||||
unsubscribe(subId) { |
||||
const subscription = this.subscriptions.get(subId); |
||||
if (!subscription) |
||||
return; |
||||
// Remove message handlers
|
||||
for (const [relayUrl, handler] of subscription.messageHandlers.entries()) { |
||||
const ws = relayPool.getRelay(relayUrl); |
||||
if (ws) { |
||||
ws.removeEventListener('message', handler); |
||||
} |
||||
// Send close message
|
||||
const message = JSON.stringify(['CLOSE', subId]); |
||||
relayPool.send(relayUrl, message); |
||||
} |
||||
this.subscriptions.delete(subId); |
||||
} |
||||
/** |
||||
* Generate a unique subscription ID |
||||
*/ |
||||
generateSubId() { |
||||
return `sub_${this.nextSubId++}_${Date.now()}`; |
||||
} |
||||
/** |
||||
* Close all subscriptions |
||||
*/ |
||||
closeAll() { |
||||
for (const subId of this.subscriptions.keys()) { |
||||
this.unsubscribe(subId); |
||||
} |
||||
} |
||||
} |
||||
export const subscriptionManager = new SubscriptionManager(); |
||||
//# sourceMappingURL=subscription-manager.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"subscription-manager.js","sourceRoot":"","sources":["subscription-manager.ts"],"names":[],"mappings":"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"} |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/** |
||||
* Bech32 utilities for NIP-19 encoding/decoding |
||||
*/ |
||||
/** |
||||
* Decode a bech32 string (simplified - full implementation would use bech32 library) |
||||
* This is a placeholder - in production, use a proper bech32 library |
||||
*/ |
||||
export function decodeBech32(bech32) { |
||||
try { |
||||
const prefix = bech32.split('1')[0]; |
||||
if (!prefix) |
||||
return null; |
||||
// Basic validation - full implementation needed
|
||||
if (prefix === 'npub' || prefix === 'nsec' || prefix === 'note') { |
||||
return { |
||||
type: prefix, |
||||
data: new Uint8Array(32) // Placeholder
|
||||
}; |
||||
} |
||||
return null; |
||||
} |
||||
catch { |
||||
return null; |
||||
} |
||||
} |
||||
/** |
||||
* Encode data to bech32 format |
||||
*/ |
||||
export function encodeBech32(type, data, relay) { |
||||
// Placeholder - full implementation needed with bech32 library
|
||||
// For now, return hex representation
|
||||
return `${type}1${Array.from(data) |
||||
.map((b) => b.toString(16).padStart(2, '0')) |
||||
.join('')}`;
|
||||
} |
||||
/** |
||||
* Validate bech32 string format |
||||
*/ |
||||
export function isValidBech32(bech32) { |
||||
return /^(npub|nsec|note|nevent|naddr|nprofile)1[a-z0-9]+$/.test(bech32); |
||||
} |
||||
//# sourceMappingURL=bech32-utils.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"bech32-utils.js","sourceRoot":"","sources":["bech32-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAEzB,gDAAgD;QAChD,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAChE,OAAO;gBACL,IAAI,EAAE,MAAkC;gBACxC,IAAI,EAAE,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,cAAc;aACxC,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAgB,EAAE,KAAc;IACzE,+DAA+D;IAC/D,qCAAqC;IACrC,OAAO,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;SAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,oDAAoD,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC3E,CAAC"} |
||||
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
/** |
||||
* Event validation utilities |
||||
*/ |
||||
/** |
||||
* Validate event structure |
||||
*/ |
||||
export function isValidEvent(event) { |
||||
if (!event || typeof event !== 'object') |
||||
return false; |
||||
const e = event; |
||||
return (typeof e.kind === 'number' && |
||||
typeof e.pubkey === 'string' && |
||||
typeof e.created_at === 'number' && |
||||
typeof e.content === 'string' && |
||||
typeof e.id === 'string' && |
||||
typeof e.sig === 'string' && |
||||
Array.isArray(e.tags) && |
||||
e.pubkey.length === 64 && |
||||
e.id.length === 64 && |
||||
e.sig.length === 128); |
||||
} |
||||
/** |
||||
* Check if event has required tags for a kind |
||||
*/ |
||||
export function hasRequiredTags(event, kind) { |
||||
switch (kind) { |
||||
case 0: |
||||
// Kind 0 can have tags or JSON content
|
||||
return true; |
||||
case 11: |
||||
// Thread - should have title tag
|
||||
return true; |
||||
case 1111: |
||||
// Comment - should have K and E tags
|
||||
return event.tags.some((t) => t[0] === 'K' || t[0] === 'E'); |
||||
default: |
||||
return true; |
||||
} |
||||
} |
||||
/** |
||||
* Validate event signature (placeholder - would need crypto library) |
||||
*/ |
||||
export function isValidSignature(event) { |
||||
// Placeholder - full implementation would verify signature
|
||||
// using secp256k1 cryptography
|
||||
return event.sig.length === 128; |
||||
} |
||||
//# sourceMappingURL=event-validator.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"event-validator.js","sourceRoot":"","sources":["event-validator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAEtD,MAAM,CAAC,GAAG,KAAgC,CAAC;IAE3C,OAAO,CACL,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAC1B,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAC5B,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ;QAChC,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;QAC7B,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ;QACxB,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QACrB,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,EAAE;QACtB,CAAC,CAAC,EAAE,CAAC,MAAM,KAAK,EAAE;QAClB,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,CACrB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,KAAiB,EAAE,IAAY;IAC7D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,CAAC;YACJ,uCAAuC;YACvC,OAAO,IAAI,CAAC;QACd,KAAK,EAAE;YACL,iCAAiC;YACjC,OAAO,IAAI,CAAC;QACd,KAAK,IAAI;YACP,qCAAqC;YACrC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;QAC9D;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,2DAA2D;IAC3D,+BAA+B;IAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC;AAClC,CAAC"} |
||||
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
/** |
||||
* Key management with NIP-49 encryption |
||||
* All private keys MUST be encrypted before storage |
||||
*/ |
||||
/** |
||||
* Encrypt a private key using NIP-49 (password-based encryption) |
||||
* This is a placeholder - full implementation requires: |
||||
* - scrypt for key derivation |
||||
* - AES-256-GCM for encryption |
||||
* - Base64 encoding |
||||
* |
||||
* WARNING: Current implementation stores keys in plaintext format. |
||||
* This is insecure and should only be used for development. |
||||
* Production code MUST implement proper NIP-49 encryption. |
||||
*/ |
||||
export async function encryptPrivateKey(nsec, password) { |
||||
// TEMPORARY: Store as base64-encoded plaintext with a marker
|
||||
// This allows the system to function while proper crypto is implemented
|
||||
// Full NIP-49 implementation would:
|
||||
// 1. Derive key from password using scrypt
|
||||
// 2. Generate random salt and nonce
|
||||
// 3. Encrypt nsec with AES-256-GCM
|
||||
// 4. Encode as ncryptsec format
|
||||
const encoded = btoa(JSON.stringify({ nsec, password })); |
||||
return `ncryptsec1${encoded}`; |
||||
} |
||||
/** |
||||
* Decrypt a private key using NIP-49 |
||||
* |
||||
* WARNING: Current implementation reads plaintext keys. |
||||
* This is insecure and should only be used for development. |
||||
* Production code MUST implement proper NIP-49 decryption. |
||||
*/ |
||||
export async function decryptPrivateKey(ncryptsec, password) { |
||||
// TEMPORARY: Decode from base64 plaintext format
|
||||
// This allows the system to function while proper crypto is implemented
|
||||
// Full NIP-49 implementation would:
|
||||
// 1. Decode ncryptsec format
|
||||
// 2. Derive key from password using scrypt
|
||||
// 3. Decrypt with AES-256-GCM
|
||||
// 4. Return plain nsec
|
||||
if (!ncryptsec.startsWith('ncryptsec1')) { |
||||
throw new Error('Invalid ncryptsec format'); |
||||
} |
||||
try { |
||||
const decoded = JSON.parse(atob(ncryptsec.slice(11))); |
||||
if (decoded.password !== password) { |
||||
throw new Error('Invalid password'); |
||||
} |
||||
return decoded.nsec; |
||||
} |
||||
catch (error) { |
||||
throw new Error('Failed to decrypt private key: ' + (error instanceof Error ? error.message : 'Unknown error')); |
||||
} |
||||
} |
||||
/** |
||||
* Generate a new private key |
||||
*/ |
||||
export function generatePrivateKey() { |
||||
// Placeholder - would use crypto.getRandomValues to generate 32 random bytes
|
||||
// then encode as hex
|
||||
const array = new Uint8Array(32); |
||||
crypto.getRandomValues(array); |
||||
return Array.from(array) |
||||
.map((b) => b.toString(16).padStart(2, '0')) |
||||
.join(''); |
||||
} |
||||
//# sourceMappingURL=key-management.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"key-management.js","sourceRoot":"","sources":["key-management.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY,EAAE,QAAgB;IACpE,6DAA6D;IAC7D,wEAAwE;IACxE,oCAAoC;IACpC,2CAA2C;IAC3C,oCAAoC;IACpC,mCAAmC;IACnC,gCAAgC;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IACzD,OAAO,aAAa,OAAO,EAAE,CAAC;AAChC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAiB,EAAE,QAAgB;IACzE,iDAAiD;IACjD,wEAAwE;IACxE,oCAAoC;IACpC,6BAA6B;IAC7B,2CAA2C;IAC3C,8BAA8B;IAC9B,uBAAuB;IACvB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACtD,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,CAAC;IACtB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;IAClH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,6EAA6E;IAC7E,qBAAqB;IACrB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/** |
||||
* HTML sanitization using DOMPurify |
||||
*/ |
||||
import DOMPurify from 'dompurify'; |
||||
/** |
||||
* Sanitize HTML content |
||||
*/ |
||||
export function sanitizeHtml(dirty) { |
||||
return DOMPurify.sanitize(dirty, { |
||||
ALLOWED_TAGS: [ |
||||
'p', |
||||
'br', |
||||
'strong', |
||||
'em', |
||||
'u', |
||||
's', |
||||
'code', |
||||
'pre', |
||||
'a', |
||||
'ul', |
||||
'ol', |
||||
'li', |
||||
'blockquote', |
||||
'h1', |
||||
'h2', |
||||
'h3', |
||||
'h4', |
||||
'h5', |
||||
'h6', |
||||
'img', |
||||
'video', |
||||
'audio' |
||||
], |
||||
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'controls', 'preload'], |
||||
ALLOW_DATA_ATTR: false |
||||
}); |
||||
} |
||||
/** |
||||
* Sanitize markdown-rendered HTML |
||||
*/ |
||||
export function sanitizeMarkdown(html) { |
||||
return sanitizeHtml(html); |
||||
} |
||||
//# sourceMappingURL=sanitizer.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sanitizer.js","sourceRoot":"","sources":["sanitizer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,SAAS,MAAM,WAAW,CAAC;AAElC;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE;QAC/B,YAAY,EAAE;YACZ,GAAG;YACH,IAAI;YACJ,QAAQ;YACR,IAAI;YACJ,GAAG;YACH,GAAG;YACH,MAAM;YACN,KAAK;YACL,GAAG;YACH,IAAI;YACJ,IAAI;YACJ,IAAI;YACJ,YAAY;YACZ,IAAI;YACJ,IAAI;YACJ,IAAI;YACJ,IAAI;YACJ,IAAI;YACJ,IAAI;YACJ,KAAK;YACL,OAAO;YACP,OAAO;SACR;QACD,YAAY,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC;QAC7E,eAAe,EAAE,KAAK;KACvB,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC"} |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
/** |
||||
* Nostr type definitions |
||||
*/ |
||||
export {}; |
||||
//# sourceMappingURL=nostr.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"nostr.js","sourceRoot":"","sources":["nostr.ts"],"names":[],"mappings":"AAAA;;GAEG"} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv { |
||||
readonly VITE_DEFAULT_RELAYS?: string; |
||||
readonly VITE_ZAP_THRESHOLD?: string; |
||||
readonly VITE_THREAD_TIMEOUT_DAYS?: string; |
||||
readonly VITE_PWA_ENABLED?: string; |
||||
} |
||||
|
||||
interface ImportMeta { |
||||
readonly env: ImportMetaEnv; |
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
# Static Assets |
||||
|
||||
This directory contains static assets for Aitherboard. |
||||
|
||||
## Files |
||||
|
||||
- `favicon.svg` - SVG favicon (works in modern browsers) |
||||
- `og-image.svg` - OpenGraph social sharing image (1200x630px) |
||||
- `manifest.json` - PWA manifest file |
||||
|
||||
## Production Notes |
||||
|
||||
For better compatibility, consider: |
||||
|
||||
1. **Favicon**: Generate a `.ico` file from `favicon.svg` for older browsers |
||||
- Use a tool like [RealFaviconGenerator](https://realfavicongenerator.net/) or ImageMagick |
||||
- Add `<link rel="icon" href="/favicon.ico" sizes="32x32">` to app.html |
||||
|
||||
2. **OpenGraph Image**: Convert `og-image.svg` to PNG format for better social media compatibility |
||||
- Some platforms (Facebook, LinkedIn) prefer PNG over SVG |
||||
- Use ImageMagick: `convert og-image.svg -resize 1200x630 og-image.png` |
||||
- Update app.html to reference `og-image.png` instead of `og-image.svg` |
||||
|
||||
3. **Apple Touch Icon**: Generate PNG versions for iOS devices |
||||
- Sizes: 180x180, 152x152, 144x144, 120x120, 114x114, 76x76, 72x72, 60x60, 57x57 |
||||
|
After Width: | Height: | Size: 284 KiB |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
{ |
||||
"name": "Aitherboard", |
||||
"short_name": "Aitherboard", |
||||
"description": "A decentralized messageboard built on the Nostr protocol", |
||||
"start_url": "/", |
||||
"display": "standalone", |
||||
"background_color": "#f1f5f9", |
||||
"theme_color": "#f1f5f9", |
||||
"orientation": "portrait-primary", |
||||
"icons": [ |
||||
{ |
||||
"src": "/favicon.svg", |
||||
"sizes": "any", |
||||
"type": "image/svg+xml", |
||||
"purpose": "any maskable" |
||||
} |
||||
] |
||||
} |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite'; |
||||
import { defineConfig } from 'vite'; |
||||
import { execSync } from 'child_process'; |
||||
export default defineConfig({ |
||||
plugins: [ |
||||
sveltekit(), |
||||
{ |
||||
name: 'generate-healthz', |
||||
buildStart() { |
||||
try { |
||||
execSync('node scripts/generate-healthz.js', { stdio: 'inherit' }); |
||||
} |
||||
catch (error) { |
||||
console.warn('Failed to generate healthz.json:', error); |
||||
} |
||||
} |
||||
} |
||||
], |
||||
server: { |
||||
port: 5173, |
||||
strictPort: false |
||||
}, |
||||
build: { |
||||
target: 'esnext', |
||||
sourcemap: true |
||||
} |
||||
}); |
||||
//# sourceMappingURL=vite.config.js.map
|
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"vite.config.js","sourceRoot":"","sources":["vite.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,eAAe,YAAY,CAAC;IAC1B,OAAO,EAAE;QACP,SAAS,EAAE;QACX;YACE,IAAI,EAAE,kBAAkB;YACxB,UAAU;gBACR,IAAI,CAAC;oBACH,QAAQ,CAAC,kCAAkC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;gBACrE,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,IAAI,CAAC,kCAAkC,EAAE,KAAK,CAAC,CAAC;gBAC1D,CAAC;YACH,CAAC;SACF;KACF;IACD,MAAM,EAAE;QACN,IAAI,EAAE,IAAI;QACV,UAAU,EAAE,KAAK;KAClB;IACD,KAAK,EAAE;QACL,MAAM,EAAE,QAAQ;QAChB,SAAS,EAAE,IAAI;KAChB;CACF,CAAC,CAAC"} |
||||
Loading…
Reference in new issue