Merge pull request #1018 from PR0M3TH3AN/codex/update-buildrepostevent-and-repostevent-functions

Ensure repost events resolve verified relays
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-30 15:35:13 -04:00
commit 6fce4ecc14
3 files changed
+245 -23

No files matched your search

+192 -4
View File
@@ -520,6 +520,7 @@ export async function repostEvent({
const cachedVideo = client?.allEvents?.get(normalizedId) || null;
const cachedRaw = client?.rawEvents?.get(normalizedId) || null;
let resolvedRawEvent = cachedRaw;
let authorPubkey =
typeof options.authorPubkey === "string" && options.authorPubkey.trim()
@@ -544,10 +545,126 @@ export async function repostEvent({
addressRelay = pointer.relay;
}
const relayCandidates = new Set();
const rememberRelayCandidate = (value) => {
if (typeof value !== "string") {
return;
}
const normalized = value.trim();
if (!normalized) {
return;
}
relayCandidates.add(normalized);
};
const rememberPointerRelay = (pointerCandidate) => {
const normalizedPointer = normalizePointerInput(pointerCandidate);
if (normalizedPointer?.relay) {
rememberRelayCandidate(normalizedPointer.relay);
}
};
const rememberRelayHintsFromObject = (candidate, depth = 0) => {
if (!candidate || typeof candidate !== "object" || depth > 3) {
return;
}
const directKeys = [
"relay",
"relayUrl",
"eventRelay",
"pointerRelay",
"sourceRelay",
"originRelay",
"addressRelay",
];
directKeys.forEach((key) => {
if (typeof candidate[key] === "string") {
rememberRelayCandidate(candidate[key]);
}
});
if (Array.isArray(candidate.relays)) {
candidate.relays.forEach((value) => {
rememberRelayCandidate(value);
});
}
if (candidate.pointer) {
rememberPointerRelay(candidate.pointer);
}
if (candidate.eventPointer) {
rememberPointerRelay(candidate.eventPointer);
}
if (candidate.pointerInfo && depth <= 3) {
rememberRelayHintsFromObject(candidate.pointerInfo, depth + 1);
}
if (Array.isArray(candidate.pointers)) {
candidate.pointers.forEach((pointerCandidate) => {
rememberPointerRelay(pointerCandidate);
});
}
if (candidate.video && depth <= 3) {
rememberRelayHintsFromObject(candidate.video, depth + 1);
}
};
let eventRelay =
typeof options.eventRelay === "string" ? options.eventRelay.trim() : "";
if (!eventRelay && pointer?.type === "e" && pointer.relay) {
eventRelay = pointer.relay;
if (eventRelay) {
rememberRelayCandidate(eventRelay);
}
if (pointer) {
rememberPointerRelay(pointer);
if (!eventRelay && pointer.type === "e" && pointer.relay) {
eventRelay = pointer.relay;
}
}
rememberRelayHintsFromObject(options);
rememberRelayHintsFromObject(pointer);
rememberRelayHintsFromObject(cachedVideo);
rememberRelayHintsFromObject(cachedVideo?.pointerInfo);
rememberRelayHintsFromObject(cachedRaw);
if (addressRelay) {
rememberRelayCandidate(addressRelay);
}
const deriveRelayFromRawEvent = (event) => {
if (!event || typeof event !== "object") {
return "";
}
const directRelay =
typeof event.relay === "string" ? event.relay.trim() : "";
if (directRelay) {
return directRelay;
}
const relayCollections = [event.relays, event.seenOn, event.seen_on];
for (const collection of relayCollections) {
if (!Array.isArray(collection)) {
continue;
}
for (const entry of collection) {
if (typeof entry === "string" && entry.trim()) {
return entry.trim();
}
}
}
return "";
};
const cachedRawRelay = deriveRelayFromRawEvent(cachedRaw);
if (cachedRawRelay) {
rememberRelayCandidate(cachedRawRelay);
if (!eventRelay) {
eventRelay = cachedRawRelay;
}
}
let targetKind = Number.isFinite(options.kind)
@@ -642,7 +759,79 @@ export async function repostEvent({
: client?.relays,
);
const relays = relaysOverride.length ? relaysOverride : Array.from(RELAY_URLS);
const chosenPublishRelay = relays.length ? relays[0] : "";
const probeRelays = (() => {
const order = [];
if (eventRelay) {
order.push(eventRelay);
}
relayCandidates.forEach((relayUrl) => {
if (relayUrl && !order.includes(relayUrl)) {
order.push(relayUrl);
}
});
relays.forEach((relayUrl) => {
if (relayUrl && !order.includes(relayUrl)) {
order.push(relayUrl);
}
});
return order;
})();
if ((!eventRelay || !resolvedRawEvent) && typeof client?.fetchRawEventById === "function") {
for (const relayUrl of probeRelays) {
if (!relayUrl) {
continue;
}
if (!resolvedRawEvent) {
try {
const fetched = await client.fetchRawEventById(normalizedId, {
relays: [relayUrl],
});
if (fetched) {
resolvedRawEvent = fetched;
}
} catch (error) {
devLogger.warn(
`[nostr] Failed to fetch ${normalizedId} from ${relayUrl} while preparing repost`,
error,
);
}
}
if (!eventRelay && resolvedRawEvent) {
eventRelay = relayUrl;
rememberRelayCandidate(relayUrl);
}
if (eventRelay && resolvedRawEvent) {
break;
}
}
}
if (resolvedRawEvent && !eventRelay) {
const rawRelay = deriveRelayFromRawEvent(resolvedRawEvent);
if (rawRelay) {
eventRelay = rawRelay;
rememberRelayCandidate(rawRelay);
}
}
if (!eventRelay) {
devLogger.warn(
`[nostr] Repost aborted: missing source relay for ${normalizedId}`,
{ candidates: probeRelays },
);
return {
ok: false,
error: "missing-event-relay",
details: { relays: probeRelays },
};
}
devLogger.log(
`[nostr] Resolved repost target relay ${eventRelay} for ${normalizedId}`,
);
let actorPubkey =
typeof options.actorPubkey === "string" && options.actorPubkey.trim()
@@ -698,7 +887,6 @@ export async function repostEvent({
created_at: createdAt,
eventId: normalizedId,
eventRelay,
publishRelay: chosenPublishRelay,
address,
addressRelay,
authorPubkey,
+6 -6
View File
@@ -651,7 +651,6 @@ export function buildRepostEvent({
created_at,
eventId = "",
eventRelay = "",
publishRelay = "",
address = "",
addressRelay = "",
authorPubkey = "",
@@ -665,11 +664,12 @@ export function buildRepostEvent({
const normalizedEventId = typeof eventId === "string" ? eventId.trim() : "";
const normalizedEventRelay =
typeof eventRelay === "string" ? eventRelay.trim() : "";
const normalizedPublishRelay =
typeof publishRelay === "string" ? publishRelay.trim() : "";
const resolvedEventRelay = normalizedEventRelay || normalizedPublishRelay;
if (normalizedEventId && resolvedEventRelay) {
tags.push(["e", normalizedEventId, resolvedEventRelay]);
if (normalizedEventId) {
if (!normalizedEventRelay) {
throw new Error("missing-event-relay");
}
tags.push(["e", normalizedEventId, normalizedEventRelay]);
}
const normalizedAddress = typeof address === "string" ? address.trim() : "";
+47 -13
View File
@@ -67,6 +67,10 @@ function createPublishClient({ actorPubkey = "", sessionPubkey = "", failPublish
ensurePool: async () => pool,
ensureSessionActor: async () => sessionPubkey,
ensureExtensionPermissions: async () => ({ ok: true }),
fetchRawEventById: async (id, { relays: requestedRelays } = {}) => {
void requestedRelays;
return client.rawEvents.get(id) || null;
},
countEventsAcrossRelays: async () => ({ total: 0, perRelay: [] }),
};
@@ -100,20 +104,20 @@ function createPublishClient({ actorPubkey = "", sessionPubkey = "", failPublish
assert.deepEqual(event.tags[2], ["p", "deadbeef"]);
})();
(function testBuildRepostEventFallsBackToPublishRelay() {
(function testBuildRepostEventRequiresRelay() {
const createdAt = 1_700_000_001;
const event = buildRepostEvent({
pubkey: "actorpubkey",
created_at: createdAt,
eventId: "event456",
publishRelay: "wss://relay.example",
authorPubkey: "cafebabe",
targetKind: 30078,
});
assert.equal(event.kind, 16);
assert.deepEqual(event.tags[0], ["e", "event456", "wss://relay.example"]);
assert.deepEqual(event.tags[1], ["p", "cafebabe"]);
assert.throws(
() =>
buildRepostEvent({
pubkey: "actorpubkey",
created_at: createdAt,
eventId: "event456",
authorPubkey: "cafebabe",
targetKind: 30078,
}),
/missing-event-relay/,
);
})();
await (async function testRepostEventUsesSessionActorAndDerivesAddress() {
@@ -122,7 +126,11 @@ await (async function testRepostEventUsesSessionActorAndDerivesAddress() {
const sessionPubkey = "a".repeat(64);
const { client } = createPublishClient({ sessionPubkey });
client.rawEvents.set(eventId, { id: eventId, pubkey: authorPubkey });
client.rawEvents.set(eventId, {
id: eventId,
pubkey: authorPubkey,
relays: ["wss://relay.example"],
});
client.allEvents.set(eventId, {
id: eventId,
pubkey: authorPubkey,
@@ -194,6 +202,32 @@ await (async function testRepostEventHandlesPublishFailure() {
assert.ok(result.details instanceof Error);
})();
await (async function testRepostEventFailsWhenRelayMissing() {
const eventId = "repost-no-relay";
const authorPubkey = "c".repeat(64);
const sessionPubkey = "e".repeat(64);
const { client } = createPublishClient({ sessionPubkey });
client.fetchRawEventById = async () => null;
const result = await repostEvent({
client,
eventId,
options: {
pointer: ["e", eventId],
authorPubkey,
},
resolveActiveSigner: resolveActiveSignerStub,
shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub,
signEventWithPrivateKey: signEventWithPrivateKeyStub,
eventToAddressPointer: eventToAddressPointerStub,
});
assert.equal(result.ok, false);
assert.equal(result.error, "missing-event-relay");
assert.ok(Array.isArray(result.details?.relays));
})();
await (async function testMirrorEventIncludesHostedMetadata() {
const eventId = "mirror-event";
const actorPubkey = "1".repeat(64);