|
|
|
@ -4,6 +4,8 @@ import { filterRelaysForEventPublish } from '@/lib/relay-publish-filter' |
|
|
|
import { dedupeNormalizeRelayUrlsOrdered } from '@/lib/relay-url-priority' |
|
|
|
import { dedupeNormalizeRelayUrlsOrdered } from '@/lib/relay-url-priority' |
|
|
|
import { collectWriteOutboxUrlsFromRelayList } from '@/lib/viewer-write-outboxes' |
|
|
|
import { collectWriteOutboxUrlsFromRelayList } from '@/lib/viewer-write-outboxes' |
|
|
|
import logger from '@/lib/logger' |
|
|
|
import logger from '@/lib/logger' |
|
|
|
|
|
|
|
import { NEW_USER_HTTP_RELAY_URL } from '@/lib/new-user-template' |
|
|
|
|
|
|
|
import { normalizeAnyRelayUrl } from '@/lib/url' |
|
|
|
import client from '@/services/client.service' |
|
|
|
import client from '@/services/client.service' |
|
|
|
import indexedDb from '@/services/indexed-db.service' |
|
|
|
import indexedDb from '@/services/indexed-db.service' |
|
|
|
import type { TRelayList } from '@/types' |
|
|
|
import type { TRelayList } from '@/types' |
|
|
|
@ -11,21 +13,80 @@ import { Event, kinds } from 'nostr-tools' |
|
|
|
|
|
|
|
|
|
|
|
const BROADCAST_PENDING_KEY = 'imwaldNewUserTemplateBroadcastPending' |
|
|
|
const BROADCAST_PENDING_KEY = 'imwaldNewUserTemplateBroadcastPending' |
|
|
|
|
|
|
|
|
|
|
|
export const NEW_USER_TEMPLATE_BROADCAST_INTERVAL_MS = 5000 |
|
|
|
/** Space between replaceable template events — keeps relay publish rate limits from tripping. */ |
|
|
|
|
|
|
|
export const NEW_USER_TEMPLATE_BROADCAST_INTERVAL_MS = 20_000 |
|
|
|
|
|
|
|
|
|
|
|
/** Replaceable kinds created during one-click signup, in publish order. */ |
|
|
|
/** Replaceable kinds created during one-click signup, in publish order. */ |
|
|
|
export const NEW_USER_TEMPLATE_BROADCAST_KINDS = [ |
|
|
|
export const NEW_USER_TEMPLATE_BROADCAST_KINDS = [ |
|
|
|
kinds.RelayList, |
|
|
|
kinds.RelayList, |
|
|
|
ExtendedKind.HTTP_RELAY_LIST, |
|
|
|
ExtendedKind.HTTP_RELAY_LIST, |
|
|
|
ExtendedKind.FAVORITE_RELAYS, |
|
|
|
ExtendedKind.FAVORITE_RELAYS, |
|
|
|
|
|
|
|
ExtendedKind.BLOCKED_RELAYS, |
|
|
|
kinds.Metadata, |
|
|
|
kinds.Metadata, |
|
|
|
10015, |
|
|
|
10015, |
|
|
|
kinds.Contacts, |
|
|
|
kinds.Contacts, |
|
|
|
kinds.Mutelist |
|
|
|
kinds.Mutelist |
|
|
|
] as const |
|
|
|
] as const |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Relays that reject bursts or return HTTP 429 on connect during signup publish. */ |
|
|
|
|
|
|
|
const NEW_USER_TEMPLATE_PUBLISH_EXCLUDED = [ |
|
|
|
|
|
|
|
'wss://relay.layer.systems', |
|
|
|
|
|
|
|
'wss://profiles.nostrver.se/' |
|
|
|
|
|
|
|
] as const |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Profile mirrors that only mirror kind 10002 (not kind 0 or other lists). */ |
|
|
|
|
|
|
|
const RELAY_LIST_ONLY_PROFILE_MIRRORS = ['wss://indexer.coracle.social/'] as const |
|
|
|
|
|
|
|
|
|
|
|
const broadcastScheduledOrRunning = new Set<string>() |
|
|
|
const broadcastScheduledOrRunning = new Set<string>() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function templateRelayKey(url: string): string { |
|
|
|
|
|
|
|
return (normalizeAnyRelayUrl(url) || url).toLowerCase() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isExcludedFromTemplateBroadcast(url: string): boolean { |
|
|
|
|
|
|
|
const key = templateRelayKey(url) |
|
|
|
|
|
|
|
return NEW_USER_TEMPLATE_PUBLISH_EXCLUDED.some((u) => templateRelayKey(u) === key) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function relayAllowsTemplateKind(url: string, kind: number): boolean { |
|
|
|
|
|
|
|
if (kind === kinds.RelayList) return true |
|
|
|
|
|
|
|
const key = templateRelayKey(url) |
|
|
|
|
|
|
|
return !RELAY_LIST_ONLY_PROFILE_MIRRORS.some((u) => templateRelayKey(u) === key) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function maxTemplatePublishRelays(kind: number): number { |
|
|
|
|
|
|
|
return kind === kinds.Metadata || kind === kinds.RelayList ? 4 : 3 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Prefer mercury + stable write relays; profile index when kind allows. */ |
|
|
|
|
|
|
|
function prioritizeNewUserTemplateRelays(urls: string[]): string[] { |
|
|
|
|
|
|
|
const preferredOrder = [ |
|
|
|
|
|
|
|
NEW_USER_HTTP_RELAY_URL, |
|
|
|
|
|
|
|
'wss://profiles.nostr1.com', |
|
|
|
|
|
|
|
'wss://nos.lol', |
|
|
|
|
|
|
|
'wss://relay.primal.net', |
|
|
|
|
|
|
|
'wss://relay.damus.io', |
|
|
|
|
|
|
|
'wss://thecitadel.nostr1.com' |
|
|
|
|
|
|
|
] |
|
|
|
|
|
|
|
const byKey = new Map(urls.map((u) => [templateRelayKey(u), u])) |
|
|
|
|
|
|
|
const ordered: string[] = [] |
|
|
|
|
|
|
|
for (const pref of preferredOrder) { |
|
|
|
|
|
|
|
const u = byKey.get(templateRelayKey(pref)) |
|
|
|
|
|
|
|
if (u) { |
|
|
|
|
|
|
|
ordered.push(u) |
|
|
|
|
|
|
|
byKey.delete(templateRelayKey(u)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
for (const u of urls) { |
|
|
|
|
|
|
|
const k = templateRelayKey(u) |
|
|
|
|
|
|
|
if (byKey.has(k)) { |
|
|
|
|
|
|
|
ordered.push(u) |
|
|
|
|
|
|
|
byKey.delete(k) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return ordered |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function markNewUserTemplateBroadcastPending(pubkey: string): void { |
|
|
|
export function markNewUserTemplateBroadcastPending(pubkey: string): void { |
|
|
|
if (typeof sessionStorage === 'undefined') return |
|
|
|
if (typeof sessionStorage === 'undefined') return |
|
|
|
sessionStorage.setItem(BROADCAST_PENDING_KEY, pubkey) |
|
|
|
sessionStorage.setItem(BROADCAST_PENDING_KEY, pubkey) |
|
|
|
@ -45,7 +106,10 @@ export function newUserTemplatePublishRelays(kind: number, relayList: TRelayList |
|
|
|
kind === kinds.Metadata || kind === kinds.RelayList |
|
|
|
kind === kinds.Metadata || kind === kinds.RelayList |
|
|
|
? dedupeNormalizeRelayUrlsOrdered([...write, ...PROFILE_RELAY_URLS]) |
|
|
|
? dedupeNormalizeRelayUrlsOrdered([...write, ...PROFILE_RELAY_URLS]) |
|
|
|
: write |
|
|
|
: write |
|
|
|
return filterRelaysForEventPublish(merged, kind) |
|
|
|
const filtered = filterRelaysForEventPublish(merged, kind) |
|
|
|
|
|
|
|
.filter((u) => !isExcludedFromTemplateBroadcast(u)) |
|
|
|
|
|
|
|
.filter((u) => relayAllowsTemplateKind(u, kind)) |
|
|
|
|
|
|
|
return prioritizeNewUserTemplateRelays(filtered).slice(0, maxTemplatePublishRelays(kind)) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async function loadRelayListForPublish(pubkey: string): Promise<TRelayList> { |
|
|
|
async function loadRelayListForPublish(pubkey: string): Promise<TRelayList> { |
|
|
|
@ -101,7 +165,7 @@ async function broadcastNewUserTemplateFromStorage(pubkey: string): Promise<void |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* After the user dismisses the backup banner or leaves cache settings, broadcast locally stored |
|
|
|
* After the user dismisses the backup banner or leaves cache settings, broadcast locally stored |
|
|
|
* template events to their write outboxes and profile relays (5s between events). |
|
|
|
* template events to their write outboxes and profile relays (spaced to avoid relay rate limits). |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
export function requestNewUserTemplateBroadcast(pubkey: string): void { |
|
|
|
export function requestNewUserTemplateBroadcast(pubkey: string): void { |
|
|
|
if (!pubkey || broadcastScheduledOrRunning.has(pubkey)) return |
|
|
|
if (!pubkey || broadcastScheduledOrRunning.has(pubkey)) return |
|
|
|
|