mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #1391 from PR0M3TH3AN/codex/prioritize-kind-10050-relays-in-dms
Prioritize NIP-17 relay hints for DM send/subscribe
This commit is contained in:
5 files changed
+194
-80
No files matched your search
@@ -3223,11 +3223,9 @@ class Application {
|
||||
: [];
|
||||
|
||||
if (enabled && !relayHints.length) {
|
||||
this.showError("Recipient has not shared NIP-17 relay hints yet.");
|
||||
if (controller?.setPrivacyToggleState) {
|
||||
controller.setPrivacyToggleState(false);
|
||||
}
|
||||
return;
|
||||
this.showStatus(
|
||||
"Privacy warning: this recipient has not shared NIP-17 relays, so we'll use your default relays.",
|
||||
);
|
||||
}
|
||||
|
||||
this.showStatus(
|
||||
|
||||
+44
-60
@@ -113,6 +113,10 @@ import {
|
||||
} from "./dmDecryptWorkerClient.js";
|
||||
import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import { updateConversationFromMessage, writeMessages } from "../storage/dmDb.js";
|
||||
import {
|
||||
DM_RELAY_WARNING_FALLBACK,
|
||||
resolveDmRelaySelection,
|
||||
} from "../services/dmNostrService.js";
|
||||
import {
|
||||
DEFAULT_NIP07_ENCRYPTION_METHODS,
|
||||
DEFAULT_NIP07_PERMISSION_METHODS,
|
||||
@@ -4480,68 +4484,14 @@ export class NostrClient {
|
||||
const signerCapabilities = resolveSignerCapabilities(signer);
|
||||
const useNip17 = Boolean(resolvedOptions.useNip17);
|
||||
|
||||
const resolveNip17RelayList = async (pubkey, relayHints) => {
|
||||
const direct = sanitizeRelayList(relayHints);
|
||||
if (direct.length) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
const relayCandidates = sanitizeRelayList(
|
||||
const resolveNip17RelaySelection = async (pubkey, relayHints) => {
|
||||
const discoveryRelays = sanitizeRelayList(
|
||||
Array.isArray(this.readRelays) && this.readRelays.length
|
||||
? this.readRelays
|
||||
: Array.isArray(this.relays) && this.relays.length
|
||||
? this.relays
|
||||
: RELAY_URLS,
|
||||
);
|
||||
|
||||
if (!relayCandidates.length || !this.pool) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
await this.ensurePool();
|
||||
const events = await this.pool.list(relayCandidates, [
|
||||
{ kinds: [10050], authors: [pubkey], limit: 1 },
|
||||
]);
|
||||
const sorted = Array.isArray(events)
|
||||
? events
|
||||
.filter((entry) => entry && entry.pubkey === pubkey)
|
||||
.sort((a, b) => (b?.created_at || 0) - (a?.created_at || 0))
|
||||
: [];
|
||||
if (!sorted.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tags = Array.isArray(sorted[0]?.tags) ? sorted[0].tags : [];
|
||||
const relayList = tags
|
||||
.filter((tag) => Array.isArray(tag) && tag[0] === "relay")
|
||||
.map((tag) => (typeof tag[1] === "string" ? tag[1].trim() : ""))
|
||||
.filter(Boolean);
|
||||
return sanitizeRelayList(relayList);
|
||||
} catch (error) {
|
||||
devLogger.warn("[nostr] Failed to load NIP-17 relay list.", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
if (useNip17) {
|
||||
if (!signerCapabilities.nip44 || typeof signer.nip44Encrypt !== "function") {
|
||||
return { ok: false, error: "nip44-unsupported" };
|
||||
}
|
||||
|
||||
const recipientRelays = await resolveNip17RelayList(
|
||||
targetHex,
|
||||
resolvedOptions.recipientRelayHints,
|
||||
);
|
||||
|
||||
if (!recipientRelays.length) {
|
||||
return { ok: false, error: "nip17-relays-missing" };
|
||||
}
|
||||
|
||||
const senderRelays = await resolveNip17RelayList(
|
||||
actorHex,
|
||||
resolvedOptions.senderRelayHints,
|
||||
);
|
||||
const fallbackRelays = sanitizeRelayList(
|
||||
Array.isArray(this.writeRelays) && this.writeRelays.length
|
||||
? this.writeRelays
|
||||
@@ -4549,7 +4499,41 @@ export class NostrClient {
|
||||
? this.relays
|
||||
: RELAY_URLS,
|
||||
);
|
||||
const senderRelayTargets = senderRelays.length ? senderRelays : fallbackRelays;
|
||||
|
||||
return resolveDmRelaySelection({
|
||||
pubkey,
|
||||
relayHints,
|
||||
discoveryRelays,
|
||||
fallbackRelays,
|
||||
pool: this.pool,
|
||||
log: { dev: devLogger, user: userLogger },
|
||||
});
|
||||
};
|
||||
|
||||
if (useNip17) {
|
||||
if (!signerCapabilities.nip44 || typeof signer.nip44Encrypt !== "function") {
|
||||
return { ok: false, error: "nip44-unsupported" };
|
||||
}
|
||||
|
||||
const recipientSelection = await resolveNip17RelaySelection(
|
||||
targetHex,
|
||||
resolvedOptions.recipientRelayHints,
|
||||
);
|
||||
|
||||
if (!recipientSelection.relays.length) {
|
||||
return { ok: false, error: "nip17-relays-unavailable" };
|
||||
}
|
||||
|
||||
const senderSelection = await resolveNip17RelaySelection(
|
||||
actorHex,
|
||||
resolvedOptions.senderRelayHints,
|
||||
);
|
||||
const senderRelayTargets = senderSelection.relays;
|
||||
const relayWarning =
|
||||
recipientSelection.warning === DM_RELAY_WARNING_FALLBACK ||
|
||||
senderSelection.warning === DM_RELAY_WARNING_FALLBACK
|
||||
? DM_RELAY_WARNING_FALLBACK
|
||||
: null;
|
||||
|
||||
const tools = await ensureNostrTools();
|
||||
const getPublicKey =
|
||||
@@ -4690,7 +4674,7 @@ export class NostrClient {
|
||||
try {
|
||||
recipientGiftWrap = await buildGiftWrapForRecipient(
|
||||
targetHex,
|
||||
recipientRelays[0] || "",
|
||||
recipientSelection.relays[0] || "",
|
||||
);
|
||||
} catch (error) {
|
||||
devLogger.warn("[nostr] Failed to build NIP-17 recipient wrap.", error);
|
||||
@@ -4726,7 +4710,7 @@ export class NostrClient {
|
||||
}
|
||||
|
||||
const recipientPublishResults = await Promise.all(
|
||||
recipientRelays.map((url) =>
|
||||
recipientSelection.relays.map((url) =>
|
||||
publishEventToRelay(this.pool, url, recipientGiftWrap.wrap),
|
||||
),
|
||||
);
|
||||
@@ -4776,7 +4760,7 @@ export class NostrClient {
|
||||
devLogger.warn("[nostr] Failed to persist DM publish state.", error);
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
return relayWarning ? { ok: true, warning: relayWarning } : { ok: true };
|
||||
}
|
||||
|
||||
const encryptionErrors = [];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ensureNostrTools, resolveSimplePoolConstructor, shimLegacySimplePoolMethods } from "../nostr/toolkit.js";
|
||||
import { sanitizeRelayList } from "../nostr/nip46Client.js";
|
||||
import { publishEventToRelay } from "../nostrPublish.js";
|
||||
import logger from "../utils/logger.js";
|
||||
|
||||
@@ -9,6 +10,7 @@ const DEFAULT_BACKOFF = {
|
||||
};
|
||||
|
||||
const MAX_SEEN_EVENT_IDS = 5000;
|
||||
export const DM_RELAY_WARNING_FALLBACK = "dm-relays-fallback";
|
||||
|
||||
function normalizeRelayList(relays) {
|
||||
if (!Array.isArray(relays)) {
|
||||
@@ -44,6 +46,77 @@ function normalizeLogger(candidate) {
|
||||
return logger;
|
||||
}
|
||||
|
||||
function extractRelayHintsFromEvent(event) {
|
||||
const tags = Array.isArray(event?.tags) ? event.tags : [];
|
||||
return sanitizeRelayList(
|
||||
tags
|
||||
.filter((tag) => Array.isArray(tag) && tag[0] === "relay")
|
||||
.map((tag) => (typeof tag[1] === "string" ? tag[1].trim() : "")),
|
||||
);
|
||||
}
|
||||
|
||||
export async function resolveDmRelaySelection({
|
||||
pubkey,
|
||||
relayHints = [],
|
||||
discoveryRelays = [],
|
||||
fallbackRelays = [],
|
||||
pool = null,
|
||||
log = null,
|
||||
} = {}) {
|
||||
const resolvedLogger = normalizeLogger(log || logger);
|
||||
const normalizedPubkey = normalizePubkey(pubkey);
|
||||
const hintedRelays = sanitizeRelayList(Array.isArray(relayHints) ? relayHints : []);
|
||||
|
||||
if (hintedRelays.length) {
|
||||
return { relays: hintedRelays, source: "hints", warning: null };
|
||||
}
|
||||
|
||||
const discoveryList = sanitizeRelayList(
|
||||
Array.isArray(discoveryRelays) ? discoveryRelays : [],
|
||||
);
|
||||
const fallbackList = sanitizeRelayList(
|
||||
Array.isArray(fallbackRelays) && fallbackRelays.length
|
||||
? fallbackRelays
|
||||
: discoveryRelays,
|
||||
);
|
||||
|
||||
if (
|
||||
normalizedPubkey &&
|
||||
pool &&
|
||||
typeof pool.list === "function" &&
|
||||
discoveryList.length
|
||||
) {
|
||||
try {
|
||||
const events = await pool.list(discoveryList, [
|
||||
{ kinds: [10050], authors: [normalizedPubkey], limit: 1 },
|
||||
]);
|
||||
const sorted = Array.isArray(events)
|
||||
? events
|
||||
.filter((entry) => entry && entry.pubkey === normalizedPubkey)
|
||||
.sort((a, b) => (b?.created_at || 0) - (a?.created_at || 0))
|
||||
: [];
|
||||
if (sorted.length) {
|
||||
const relays = extractRelayHintsFromEvent(sorted[0]);
|
||||
if (relays.length) {
|
||||
return { relays, source: "kind-10050", warning: null };
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
resolvedLogger.dev.warn("[dmNostrService] Failed to fetch DM relay hints.", error);
|
||||
}
|
||||
}
|
||||
|
||||
if (fallbackList.length) {
|
||||
return {
|
||||
relays: fallbackList,
|
||||
source: "fallback",
|
||||
warning: DM_RELAY_WARNING_FALLBACK,
|
||||
};
|
||||
}
|
||||
|
||||
return { relays: [], source: "none", warning: null };
|
||||
}
|
||||
|
||||
function clampNumber(value, minimum, maximum) {
|
||||
if (!Number.isFinite(value)) {
|
||||
return minimum;
|
||||
|
||||
@@ -2,8 +2,12 @@ import { nostrClient } from "../nostrClientFacade.js";
|
||||
import { convertEventToVideo } from "../nostr/index.js";
|
||||
import { accessControl } from "../accessControl.js";
|
||||
import { ALLOW_NSFW_CONTENT } from "../config.js";
|
||||
import { userLogger } from "../utils/logger.js";
|
||||
import { devLogger, userLogger } from "../utils/logger.js";
|
||||
import moderationService from "./moderationService.js";
|
||||
import {
|
||||
DM_RELAY_WARNING_FALLBACK,
|
||||
resolveDmRelaySelection,
|
||||
} from "./dmNostrService.js";
|
||||
import {
|
||||
loadDirectMessageSnapshot,
|
||||
saveDirectMessageSnapshot,
|
||||
@@ -874,7 +878,7 @@ export class NostrService {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
ensureDirectMessageSubscription({ actorPubkey, relays, ...handlers } = {}) {
|
||||
async ensureDirectMessageSubscription({ actorPubkey, relays, ...handlers } = {}) {
|
||||
if (this.dmSubscription) {
|
||||
return this.dmSubscription;
|
||||
}
|
||||
@@ -885,11 +889,40 @@ export class NostrService {
|
||||
return null;
|
||||
}
|
||||
|
||||
let resolvedRelays = Array.isArray(relays) ? relays : null;
|
||||
let relaySelection = null;
|
||||
|
||||
if (!resolvedRelays) {
|
||||
const discoveryRelays = Array.isArray(this.nostrClient?.readRelays)
|
||||
? this.nostrClient.readRelays
|
||||
: Array.isArray(this.nostrClient?.relays)
|
||||
? this.nostrClient.relays
|
||||
: [];
|
||||
|
||||
let pool = this.nostrClient?.pool || null;
|
||||
if (!pool && typeof this.nostrClient?.ensurePool === "function") {
|
||||
try {
|
||||
pool = await this.nostrClient.ensurePool();
|
||||
} catch (error) {
|
||||
devLogger.warn("[nostrService] Failed to initialize DM relay pool.", error);
|
||||
}
|
||||
}
|
||||
|
||||
relaySelection = await resolveDmRelaySelection({
|
||||
pubkey: normalizedActor,
|
||||
discoveryRelays,
|
||||
fallbackRelays: discoveryRelays,
|
||||
pool,
|
||||
log: { dev: devLogger, user: userLogger },
|
||||
});
|
||||
resolvedRelays = relaySelection.relays;
|
||||
}
|
||||
|
||||
try {
|
||||
const subscription = this.nostrClient.subscribeDirectMessages(
|
||||
normalizedActor,
|
||||
{
|
||||
relays,
|
||||
relays: resolvedRelays,
|
||||
onEvent: handlers.onEvent,
|
||||
onMessage: (message, context = {}) => {
|
||||
this.applyDirectMessage(message, {
|
||||
@@ -957,6 +990,14 @@ export class NostrService {
|
||||
this.dmSubscription = subscription;
|
||||
this.dmActorPubkey = normalizedActor;
|
||||
this.emit("directMessages:subscribed", { subscription });
|
||||
if (relaySelection?.warning === DM_RELAY_WARNING_FALLBACK) {
|
||||
this.emit("directMessages:relayWarning", {
|
||||
actorPubkey: normalizedActor,
|
||||
warning: relaySelection.warning,
|
||||
relays: resolvedRelays,
|
||||
source: relaySelection.source,
|
||||
});
|
||||
}
|
||||
return subscription;
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
|
||||
@@ -1752,6 +1752,9 @@ export class ProfileModalController {
|
||||
subscribe("directMessages:failure", (detail) => {
|
||||
this.handleDirectMessagesError(detail);
|
||||
});
|
||||
subscribe("directMessages:relayWarning", (detail) => {
|
||||
this.handleDirectMessagesRelayWarning(detail);
|
||||
});
|
||||
|
||||
this.directMessagesUnsubscribes = unsubscribes;
|
||||
|
||||
@@ -1995,7 +1998,7 @@ export class ProfileModalController {
|
||||
}
|
||||
}
|
||||
|
||||
ensureDirectMessageSubscription(actorPubkey = null) {
|
||||
async ensureDirectMessageSubscription(actorPubkey = null) {
|
||||
if (
|
||||
!this.nostrService ||
|
||||
typeof this.nostrService.ensureDirectMessageSubscription !== "function"
|
||||
@@ -2028,7 +2031,7 @@ export class ProfileModalController {
|
||||
|
||||
let subscription = null;
|
||||
try {
|
||||
subscription = this.nostrService.ensureDirectMessageSubscription({
|
||||
subscription = await this.nostrService.ensureDirectMessageSubscription({
|
||||
actorPubkey: normalizedActor,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -2099,7 +2102,7 @@ export class ProfileModalController {
|
||||
}
|
||||
|
||||
this.setMessagesLoadingState("loading");
|
||||
this.ensureDirectMessageSubscription(normalized);
|
||||
void this.ensureDirectMessageSubscription(normalized);
|
||||
this.updateMessagesReloadState();
|
||||
|
||||
if (this.getActivePane() === "messages") {
|
||||
@@ -2329,10 +2332,9 @@ export class ProfileModalController {
|
||||
: [];
|
||||
|
||||
if (enabled && !relayHints.length) {
|
||||
this.showError("Recipient has not shared NIP-17 relay hints yet.");
|
||||
this.setPrivacyToggleState(false);
|
||||
this.dmPrivacyToggleTouched = false;
|
||||
return;
|
||||
this.showStatus(
|
||||
"Privacy warning: this recipient has not shared NIP-17 relays, so we'll use your default relays.",
|
||||
);
|
||||
}
|
||||
|
||||
this.dmPrivacyToggleTouched = true;
|
||||
@@ -2356,6 +2358,8 @@ export class ProfileModalController {
|
||||
return "Your signer does not support NIP-44 encryption required for NIP-17.";
|
||||
case "nip17-relays-missing":
|
||||
return "Recipient has not shared NIP-17 relay hints yet.";
|
||||
case "nip17-relays-unavailable":
|
||||
return "No DM relays are available to deliver this message.";
|
||||
case "nip17-keygen-failed":
|
||||
return "We couldn’t create secure wrapper keys for NIP-17 delivery.";
|
||||
case "extension-permission-denied":
|
||||
@@ -2411,10 +2415,9 @@ export class ProfileModalController {
|
||||
: false;
|
||||
|
||||
if (useNip17 && !recipientRelayHints.length) {
|
||||
this.showError("Recipient has not shared NIP-17 relay hints yet.");
|
||||
this.setPrivacyToggleState(false);
|
||||
this.dmPrivacyToggleTouched = false;
|
||||
return;
|
||||
this.showStatus(
|
||||
"Privacy warning: this recipient has not shared NIP-17 relays, so we'll use your default relays.",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -2447,6 +2450,11 @@ export class ProfileModalController {
|
||||
if (result?.ok) {
|
||||
input.value = "";
|
||||
this.showSuccess("Message sent.");
|
||||
if (result?.warning === "dm-relays-fallback") {
|
||||
this.showStatus(
|
||||
"Privacy warning: this message used default relays because no NIP-17 relay list was found.",
|
||||
);
|
||||
}
|
||||
void this.populateProfileMessages({ force: true, reason: "send-message" });
|
||||
return;
|
||||
}
|
||||
@@ -2949,7 +2957,7 @@ export class ProfileModalController {
|
||||
}
|
||||
}
|
||||
|
||||
this.ensureDirectMessageSubscription(actor);
|
||||
void this.ensureDirectMessageSubscription(actor);
|
||||
}
|
||||
|
||||
resumeProfileMessages() {
|
||||
@@ -3051,6 +3059,16 @@ export class ProfileModalController {
|
||||
this.updateMessagesReloadState();
|
||||
}
|
||||
|
||||
handleDirectMessagesRelayWarning(detail = {}) {
|
||||
if (detail?.warning !== "dm-relays-fallback") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showStatus(
|
||||
"Privacy warning: direct messages are using your default relays because no NIP-17 relay list is available.",
|
||||
);
|
||||
}
|
||||
|
||||
registerEventListeners() {
|
||||
if (this.closeButton instanceof HTMLElement) {
|
||||
this.closeButton.addEventListener("click", () => {
|
||||
|
||||
Reference in new issue
Block a user