fix(nostr): preserve reply event context (#110085)

This commit is contained in:
mushuiyu886
2026-07-18 06:57:31 +01:00
committed by GitHub
parent 7a61f728bf
commit 8178cf1038
2 changed files with 53 additions and 3 deletions
+45 -2
View File
@@ -12,8 +12,10 @@ const mockState = vi.hoisted(() => ({
onclose?: (reason: string[]) => void;
} | null,
subscribeMany: vi.fn(),
publish: vi.fn((_relays: string[], _event: unknown) => [Promise.resolve("ok")]),
close: vi.fn(),
subscriptionClose: vi.fn(),
finalizeEvent: vi.fn((event: unknown) => event),
verifyEvent: vi.fn(() => true),
decrypt: vi.fn(() => "plaintext"),
publishProfile: vi.fn(async () => ({
@@ -42,7 +44,9 @@ vi.mock("nostr-tools", () => {
};
}
publish = vi.fn(async () => {});
publish(relays: string[], event: unknown) {
return mockState.publish(relays, event);
}
close(relays: string[]) {
mockState.close(relays);
@@ -51,7 +55,7 @@ vi.mock("nostr-tools", () => {
return {
SimplePool: MockSimplePool,
finalizeEvent: vi.fn((event: unknown) => event),
finalizeEvent: mockState.finalizeEvent,
getPublicKey: vi.fn(() => BOT_PUBKEY),
verifyEvent: mockState.verifyEvent,
nip19: {
@@ -101,8 +105,11 @@ describe("startNostrBus inbound guards", () => {
beforeEach(() => {
mockState.handlers = null;
mockState.subscribeMany.mockClear();
mockState.publish.mockReset();
mockState.publish.mockReturnValue([Promise.resolve("ok")]);
mockState.close.mockClear();
mockState.subscriptionClose.mockReset();
mockState.finalizeEvent.mockClear();
mockState.verifyEvent.mockClear();
mockState.verifyEvent.mockReturnValue(true);
mockState.decrypt.mockClear();
@@ -194,6 +201,42 @@ describe("startNostrBus inbound guards", () => {
bus.close();
});
it("links authorization replies to the inbound NIP-04 event", async () => {
const inboundEventId = "c".repeat(64);
const senderPubkey = "a".repeat(64);
const authorizeSender = vi.fn(async ({ reply }: { reply: (text: string) => Promise<void> }) => {
await reply("pairing reply");
return "pairing" as const;
});
const key = TEST_HEX_PRIVATE_KEY;
const bus = await startNostrBus({
privateKey: key,
relays: ["wss://relay.example"],
onMessage: vi.fn(async () => {}),
authorizeSender,
onMetric: () => {},
});
await emitEvent(
createEvent({
id: inboundEventId,
pubkey: senderPubkey,
}),
);
expect(mockState.finalizeEvent).toHaveBeenCalledWith(
expect.objectContaining({
tags: [
["p", senderPubkey],
["e", inboundEventId],
],
}),
expect.any(Uint8Array),
);
bus.close();
});
it("rejects invalid signatures before sender authorization", async () => {
mockState.verifyEvent.mockReturnValueOnce(false);
const onMessage = vi.fn(async () => {});
+8 -1
View File
@@ -514,6 +514,7 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
circuitBreakers,
healthTracker,
onError,
event.id,
);
};
@@ -742,13 +743,19 @@ async function sendEncryptedDm(
circuitBreakers: Map<string, CircuitBreaker>,
healthTracker: RelayHealthTracker,
onError?: (error: Error, context: string) => void,
replyToEventId?: string,
): Promise<void> {
const ciphertext = encrypt(sk, toPubkey, text);
// NIP-04 uses an e tag to keep a reply attached to its verified inbound event.
const tags = [["p", toPubkey]];
if (replyToEventId) {
tags.push(["e", replyToEventId]);
}
const reply = finalizeEvent(
{
kind: 4,
content: ciphertext,
tags: [["p", toPubkey]],
tags,
created_at: Math.floor(Date.now() / 1000),
},
sk,