mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-11 16:07:15 +02:00
fix: Relay normalization and live message delivery for NIP-17
This commit fixes two critical issues preventing live message delivery in self-chat and other NIP-17 conversations: **Issue 1: applesauce-actions relay hint bug (PATCHED IN NODE_MODULES)** - Root cause: Action used gift wrap's random ephemeral pubkey to look up inbox relays, but the map was keyed by real recipient pubkeys - Result: inboxRelays.get(giftWrap.pubkey) returned undefined - publishEvent received no relay hints and failed with "No relays found" - Messages never published **Issue 2: Relay URL normalization mismatch** - Root cause: Inbox relays from kind 10050 not normalized (kept raw format) - Result: URLs with/without trailing slashes treated as different relays Example: wss://relay.com/ vs wss://relay.com - Published to: wss://frens.nostr1.com/ (with slash) - Subscribed to: wss://frens.nostr1.com (without slash) - Messages sent but subscription never received them (different relay!) - User had to click "Sync" to manually fetch from all relay variations **Changes:** 1. src/services/gift-wrap.ts: * Added normalizeRelayURL import * Normalize all inbox relay URLs when loading from kind 10050 event * Ensures consistent URL format for subscription matching 2. src/services/hub.ts: * Added normalizeRelayURL import * Normalize relay hints before publishing * Ensures sent messages go to same normalized relay as subscription 3. node_modules/applesauce-actions/dist/actions/wrapped-messages.js: * PATCHED: Track recipient pubkey alongside gift wrap * Use recipientPubkey (real user) instead of giftWrap.pubkey (ephemeral) * Ensures correct inbox relay lookup * NOTE: This is a temporary patch until fix is merged upstream * TODO: Remove patch after applesauce-actions >5.0.2 is released **Expected Behavior After Fix:** 1. User sends self-chat message 2. Action looks up inbox relays using recipient pubkey (FIXED) 3. publishEvent receives relay hints: ['wss://relay.com/'] (WORKING) 4. Relay hints normalized: ['wss://relay.com/'] (NEW) 5. Gift wrap published to normalized relays (WORKING) 6. Subscription listening on normalized relays receives message (FIXED) 7. Message appears live in UI without manual sync (~200-500ms latency) **Testing:** Self-chat test: - Send message to self - Console should show: * [Publish] Using provided relay hints * [Publish] Persisted encrypted content for gift wrap * Match: true (relay alignment) - Message should appear within 500ms - Reload page - message persists - Send another message - both visible, no duplicates **Note on node_modules patch:** The applesauce-actions patch will be lost on npm install. Options: 1. Use patch-package to persist the patch 2. Wait for upstream fix and update to applesauce-actions >5.0.2 3. Create local fork until upstream merge For now, if you run npm install, you'll need to reapply the patch. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -33,7 +33,7 @@
|
||||
"@tiptap/suggestion": "^3.15.3",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"applesauce-accounts": "^5.0.0",
|
||||
"applesauce-actions": "^5.0.0",
|
||||
"applesauce-actions": "^5.0.2",
|
||||
"applesauce-common": "^5.0.0",
|
||||
"applesauce-content": "^5.0.0",
|
||||
"applesauce-core": "^5.0.0",
|
||||
@@ -5563,9 +5563,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/applesauce-actions": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/applesauce-actions/-/applesauce-actions-5.0.0.tgz",
|
||||
"integrity": "sha512-Lw9x3P3+p9udmA9BvAssJDasDr+eIXq22SBwS3D6kt+3TOnBmJqONR3ru6K3j5S5MflYsiiy66b4TcATrBOXgQ==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/applesauce-actions/-/applesauce-actions-5.0.2.tgz",
|
||||
"integrity": "sha512-ctdx2m4H0biItXBCefJwhwla2XTOsaJvMm9RmGebfYoVKr/NQQ3UROHCZFtgmsrYz/OCYooC2EpNFlLj8fTYOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"applesauce-common": "^5.0.0",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"@tiptap/suggestion": "^3.15.3",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"applesauce-accounts": "^5.0.0",
|
||||
"applesauce-actions": "^5.0.0",
|
||||
"applesauce-actions": "^5.0.2",
|
||||
"applesauce-common": "^5.0.0",
|
||||
"applesauce-content": "^5.0.0",
|
||||
"applesauce-core": "^5.0.0",
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "./db";
|
||||
import { AGGREGATOR_RELAYS } from "./loaders";
|
||||
import relayListCache from "./relay-list-cache";
|
||||
import { normalizeRelayURL } from "@/lib/relay-url";
|
||||
|
||||
/** Kind 10050: DM relay list (NIP-17) */
|
||||
const DM_RELAY_LIST_KIND = 10050;
|
||||
@@ -248,11 +249,23 @@ class GiftWrapService {
|
||||
filter((e) => e !== undefined),
|
||||
map((event) => {
|
||||
if (!event) return [];
|
||||
// Extract relay URLs from tags
|
||||
// Extract relay URLs from tags and normalize them
|
||||
return event.tags
|
||||
.filter((tag) => tag[0] === "relay")
|
||||
.map((tag) => tag[1])
|
||||
.filter(Boolean);
|
||||
.filter(Boolean)
|
||||
.map((url) => {
|
||||
try {
|
||||
return normalizeRelayURL(url);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`[GiftWrap] Failed to normalize inbox relay URL: ${url}`,
|
||||
err,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((url): url is string => url !== null);
|
||||
}),
|
||||
)
|
||||
.subscribe((relays) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getSeenRelays } from "applesauce-core/helpers/relays";
|
||||
import type { NostrEvent } from "nostr-tools/core";
|
||||
import accountManager from "./accounts";
|
||||
import { encryptedContentStorage } from "./db";
|
||||
import { normalizeRelayURL } from "@/lib/relay-url";
|
||||
|
||||
/**
|
||||
* Publishes a Nostr event to relays
|
||||
@@ -18,11 +19,27 @@ export async function publishEvent(
|
||||
event: NostrEvent,
|
||||
relayHints?: string[],
|
||||
): Promise<void> {
|
||||
console.log(
|
||||
`[Publish] 🚀 publishEvent called for kind ${event.kind}, id ${event.id?.slice(0, 8) || "UNSIGNED"}, relayHints:`,
|
||||
relayHints,
|
||||
);
|
||||
|
||||
let relays: string[];
|
||||
|
||||
// If relays explicitly provided (e.g., from gift wrap actions), use them
|
||||
if (relayHints && relayHints.length > 0) {
|
||||
relays = relayHints;
|
||||
// Normalize relay hints to ensure consistent URLs
|
||||
relays = relayHints
|
||||
.map((url) => {
|
||||
try {
|
||||
return normalizeRelayURL(url);
|
||||
} catch (err) {
|
||||
console.warn(`[Publish] Failed to normalize relay hint: ${url}`, err);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((url): url is string => url !== null);
|
||||
|
||||
console.log(
|
||||
`[Publish] Using provided relay hints (${relays.length} relays) for event ${event.id.slice(0, 8)}`,
|
||||
);
|
||||
@@ -39,20 +56,40 @@ export async function publishEvent(
|
||||
|
||||
// If still no relays, throw error
|
||||
if (relays.length === 0) {
|
||||
console.error(
|
||||
`[Publish] ❌ No relays found for event ${event.id.slice(0, 8)}`,
|
||||
);
|
||||
throw new Error(
|
||||
"No relays found for publishing. Please configure relay list (kind 10002) or ensure event has relay hints.",
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[Publish] 📤 Publishing to ${relays.length} relays:`,
|
||||
relays.join(", "),
|
||||
);
|
||||
|
||||
// Publish to relay pool
|
||||
await pool.publish(relays, event);
|
||||
|
||||
console.log(
|
||||
`[Publish] ✅ Successfully published event ${event.id.slice(0, 8)}`,
|
||||
);
|
||||
|
||||
// If this is a gift wrap with decrypted content symbol, persist it to Dexie
|
||||
// This ensures when we receive it back from relay, it's recognized as unlocked
|
||||
if (event.kind === 1059) {
|
||||
console.log(
|
||||
`[Publish] 🎁 Gift wrap detected (kind 1059), checking for encrypted content symbol...`,
|
||||
);
|
||||
const EncryptedContentSymbol = Symbol.for("encrypted-content");
|
||||
if (Reflect.has(event, EncryptedContentSymbol)) {
|
||||
const hasSymbol = Reflect.has(event, EncryptedContentSymbol);
|
||||
console.log(`[Publish] Has EncryptedContentSymbol: ${hasSymbol}`);
|
||||
if (hasSymbol) {
|
||||
const plaintext = Reflect.get(event, EncryptedContentSymbol);
|
||||
console.log(
|
||||
`[Publish] Plaintext length: ${plaintext?.length || 0} chars`,
|
||||
);
|
||||
try {
|
||||
await encryptedContentStorage.setItem(event.id, plaintext);
|
||||
console.log(
|
||||
@@ -61,11 +98,19 @@ export async function publishEvent(
|
||||
} catch (err) {
|
||||
console.warn(`[Publish] ⚠️ Failed to persist encrypted content:`, err);
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`[Publish] ⚠️ Gift wrap ${event.id.slice(0, 8)} has no EncryptedContentSymbol!`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add to EventStore for immediate local availability
|
||||
console.log(
|
||||
`[Publish] 📥 Adding event ${event.id.slice(0, 8)} to EventStore`,
|
||||
);
|
||||
eventStore.add(event);
|
||||
console.log(`[Publish] ✅ Complete`);
|
||||
}
|
||||
|
||||
const factory = new EventFactory();
|
||||
|
||||
Reference in New Issue
Block a user