feat: Custom self-chat implementation bypassing applesauce-actions

Replaced action-based self-chat with direct implementation using
publishEventToRelays. This eliminates the dependency on patched
node_modules and provides a more robust solution.

**Why This Change:**

The previous approach relied on patching applesauce-actions in node_modules
to fix the relay hint bug. This patch would be lost on every npm install,
making it fragile and non-reproducible.

**New Self-Chat Flow:**

1. Create rumor using WrappedMessageBlueprint (kind 14)
2. Wrap rumor using GiftWrapBlueprint (kind 1059)
3. Persist encrypted content to Dexie BEFORE publishing
4. Publish directly to inbox relays using publishEventToRelays
5. Add to EventStore for immediate local availability

**Benefits:**

-  No dependency on node_modules patches
-  Guaranteed to work across npm installs
-  Direct control over relay selection
-  Encrypted content persisted before publish (no race conditions)
-  Clean separation: self-chat vs group chat logic

**Non-Self-Chat:**

Still uses SendWrappedMessage action (with patched applesauce for now).
Future work: Extend custom implementation to all message types.

**Testing:**

Self-chat messages should now:
- Appear live within 500ms (no manual sync)
- Persist across page reloads
- Match subscribed relay URLs exactly
- Include proper console logging at each step

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gómez
2026-01-16 14:25:52 +01:00
parent b6dad2429c
commit 024be8d7ea

View File

@@ -25,11 +25,16 @@ import eventStore from "@/services/event-store";
import pool from "@/services/relay-pool";
import { AGGREGATOR_RELAYS } from "@/services/loaders";
import relayListCache from "@/services/relay-list-cache";
import { hub } from "@/services/hub";
import { hub, publishEventToRelays } from "@/services/hub";
import {
SendWrappedMessage,
ReplyToWrappedMessage,
} from "applesauce-actions/actions/wrapped-messages";
import {
WrappedMessageBlueprint,
GiftWrapBlueprint,
} from "applesauce-common/blueprints";
import { encryptedContentStorage } from "@/services/db";
/** Kind 14: Private direct message (NIP-17) */
const PRIVATE_DM_KIND = 14;
@@ -657,29 +662,84 @@ export class Nip17Adapter extends ChatProtocolAdapter {
const others = participantPubkeys.filter((p) => p !== activePubkey);
const isSelfChat = others.length === 0;
// For self-chat, pass [activePubkey] instead of [] so the message is sent
const recipients = isSelfChat ? [activePubkey] : others;
// Debug relay alignment for self-chat
if (isSelfChat) {
// Custom self-chat implementation that bypasses applesauce-actions
// and directly publishes to inbox relays using publishEventToRelays.
// This eliminates the dependency on patched node_modules.
const ownInboxRelays =
conversation.metadata?.participantInboxRelays?.[activePubkey] || [];
const subscribedRelays = giftWrapService.inboxRelays$.value;
console.log(
`[NIP-17] 🔍 Self-chat relay check:`,
`\n - Own inbox relays (will send to): ${ownInboxRelays.join(", ")}`,
`\n - Subscribed relays (receiving from): ${subscribedRelays.join(", ")}`,
`\n - Match: ${ownInboxRelays.length === subscribedRelays.length && ownInboxRelays.every((r) => subscribedRelays.includes(r))}`,
);
}
await hub.run(SendWrappedMessage, recipients, content, actionOpts);
if (ownInboxRelays.length === 0) {
throw new Error(
"No inbox relays configured. Please publish a kind 10050 event with your DM relays.",
);
}
// 1. Create the rumor (unsigned kind 14)
const factory = hub["factory"]; // Access private factory
const rumor = await factory.create(
WrappedMessageBlueprint,
[activePubkey],
content,
actionOpts,
);
if (isSelfChat) {
console.log(
`[NIP-17] ✅ Message sent successfully to self (saved messages)`,
`[NIP-17] Created rumor ${rumor.id.slice(0, 8)} for self-chat`,
);
// 2. Wrap in gift wrap addressed to self
const giftWrap = await factory.create(
GiftWrapBlueprint,
activePubkey,
rumor,
actionOpts,
);
console.log(
`[NIP-17] Created gift wrap ${giftWrap.id.slice(0, 8)} for self`,
);
// 3. Persist encrypted content before publishing
const EncryptedContentSymbol = Symbol.for("encrypted-content");
if (Reflect.has(giftWrap, EncryptedContentSymbol)) {
const plaintext = Reflect.get(giftWrap, EncryptedContentSymbol);
try {
await encryptedContentStorage.setItem(giftWrap.id, plaintext);
console.log(
`[NIP-17] ✅ Persisted encrypted content for gift wrap ${giftWrap.id.slice(0, 8)}`,
);
} catch (err) {
console.warn(
`[NIP-17] ⚠️ Failed to persist encrypted content:`,
err,
);
}
}
// 4. Publish directly to inbox relays (bypasses action)
await publishEventToRelays(giftWrap, ownInboxRelays);
// 5. Add to EventStore for immediate local availability
eventStore.add(giftWrap);
console.log(
`[NIP-17] ✅ Message sent successfully to self (saved messages) via direct publish`,
);
} else {
// Non-self-chat: use the action (still relies on patched applesauce)
const recipients = others;
await hub.run(SendWrappedMessage, recipients, content, actionOpts);
console.log(
`[NIP-17] ✅ Message sent successfully to ${recipients.length} recipients (+ self for cross-device sync)`,
);