You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

410 lines
15 KiB

import { kinds } from 'nostr-tools'
/** API base URL; override with VITE_JUMBLE_API_BASE_URL for forks (e.g. https://api.jumble.imwald.eu). */
export const JUMBLE_API_BASE_URL =
(import.meta.env.VITE_JUMBLE_API_BASE_URL as string | undefined) ?? 'https://api.jumble.imwald.eu'
/** HiveTalk (WebRTC video call) base URL; override with VITE_HIVETALK_BASE_URL for self-hosted instances. */
export const HIVETALK_BASE_URL =
(import.meta.env.VITE_HIVETALK_BASE_URL as string | undefined) ?? 'https://vanilla.hivetalk.org'
/**
* Default URL for the sidebar “Download desktop app” entry (e.g. GitHub Releases with AppImage/deb).
* Override per deploy with `DESKTOP_DOWNLOAD_URL` in `/config.json` (empty string hides the entry).
*/
export const DESKTOP_APP_DOWNLOAD_URL_DEFAULT =
'https://github.com/Silberengel/jumble/releases'
export const DEFAULT_FAVORITE_RELAYS = [
'wss://theforest.nostr1.com',
'wss://orly-relay.imwald.eu',
'wss://nostr.land'
]
/**
* Max concurrent relay connection + REQ setups (ensureRelay + subscribe) app-wide.
* Limits parallel WebSocket handshakes when many relays or timeline shards open at once.
*/
export const MAX_CONCURRENT_RELAY_CONNECTIONS = 10
/** Max relays to publish each event to (outboxes first, then targets' inboxes, then extras). */
export const MAX_PUBLISH_RELAYS = MAX_CONCURRENT_RELAY_CONNECTIONS
/** Max merged URLs per REQ / timeline relay list (see `relay-url-priority`). */
export const MAX_REQ_RELAY_URLS = MAX_CONCURRENT_RELAY_CONNECTIONS
/** Multi-relay queries and timeline initial REQ: after the first event, wait this long then close (query) or finalize EOSE (live feed) while keeping the subscription open for new events. */
export const FIRST_RELAY_RESULT_GRACE_MS = 5000
/** Legacy name: was used to cap spell NoteList skeleton time; loading now ends on EOSE / first events / safety timeouts. Kept for forks. */
export const SPELL_FEED_LOADING_MAX_MS = 1000
/** @deprecated Alias of {@link SPELL_FEED_LOADING_MAX_MS}. */
export const SPELL_FEED_FIRST_RELAY_GRACE_MS = SPELL_FEED_LOADING_MAX_MS
/**
* Implicit query feed grace ({@link FIRST_RELAY_RESULT_GRACE_MS}) applies only when the largest `limit` among
* filters is at least this value. Omitting `limit` counts as 0 (no implicit grace).
*/
export const FEED_FIRST_RELAY_RESULT_GRACE_MIN_LIMIT = 200
/**
* Minimum time between full account network hydrates (NostrProvider: relay + replaceable fetch from relays).
* IndexedDB cache still applies on every load; this only skips redundant network merges after a recent run.
*/
export const ACCOUNT_SESSION_NETWORK_HYDRATE_MIN_INTERVAL_MS = 24 * 60 * 60 * 1000
/**
* Batched kind-0 queries (ReplaceableEventService) over many relays (inbox, favorites, cache, defaults).
* Too low causes empty profiles and NIP-05 gaps when relays are slow or many URLs are queried.
*/
export const METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS = 16000
export const METADATA_BATCH_QUERY_EOSE_TIMEOUT_MS = 500
/**
* useFetchProfile: outer Promise.race on fetchProfileEvent and wait-for-shared-promise timeouts.
* Must be greater than {@link METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS} so the batch can finish first.
*/
export const PROFILE_FETCH_PROMISE_TIMEOUT_MS = 20000
export const RECOMMENDED_RELAYS = DEFAULT_FAVORITE_RELAYS.concat([])
export const RECOMMENDED_BLOSSOM_SERVERS = [
'https://blossom.band',
'https://blossom.primal.net',
'https://nostr.media'
]
export const StorageKey = {
VERSION: 'version',
THEME_SETTING: 'themeSetting',
/** Resolved theme (light/dark) written by ThemeProvider; stored in IndexedDB. */
THEME: 'theme',
FONT_SIZE: 'fontSize',
RELAY_SETS: 'relaySets',
ACCOUNTS: 'accounts',
CURRENT_ACCOUNT: 'currentAccount',
ADD_CLIENT_TAG: 'addClientTag',
NOTE_LIST_MODE: 'noteListMode',
NOTIFICATION_TYPE: 'notificationType',
DEFAULT_ZAP_SATS: 'defaultZapSats',
DEFAULT_ZAP_COMMENT: 'defaultZapComment',
QUICK_ZAP: 'quickZap',
ZAP_REPLY_THRESHOLD: 'zapReplyThreshold',
ACCOUNT_FEED_INFO_MAP: 'accountFeedInfoMap',
/** Per-pubkey ms timestamps: last full network hydrate (see ACCOUNT_SESSION_NETWORK_HYDRATE_MIN_INTERVAL_MS). */
ACCOUNT_NETWORK_HYDRATE_AT_MAP: 'accountNetworkHydrateAtMap',
AUTOPLAY: 'autoplay',
HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions',
HIDE_UNTRUSTED_NOTIFICATIONS: 'hideUntrustedNotifications',
MEDIA_UPLOAD_SERVICE_CONFIG_MAP: 'mediaUploadServiceConfigMap',
HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes',
DEFAULT_SHOW_NSFW: 'defaultShowNsfw',
DISMISSED_TOO_MANY_RELAYS_ALERT: 'dismissedTooManyRelaysAlert',
SHOW_KINDS: 'showKinds',
SHOW_KINDS_VERSION: 'showKindsVersion',
SHOW_KIND_1_OPs: 'showKind1OPs',
SHOW_KIND_1_REPLIES: 'showKind1Replies',
SHOW_KIND_1111: 'showKind1111',
/** @deprecated use SHOW_KIND_1_REPLIES + SHOW_KIND_1111 */
SHOW_REPLIES_AND_COMMENTS: 'showRepliesAndComments',
HIDE_CONTENT_MENTIONING_MUTED_USERS: 'hideContentMentioningMutedUsers',
NOTIFICATION_LIST_STYLE: 'notificationListStyle',
MEDIA_AUTO_LOAD_POLICY: 'mediaAutoLoadPolicy',
SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS: 'shownCreateWalletGuideToastPubkeys',
SHOW_RECOMMENDED_RELAYS_PANEL: 'showRecommendedRelaysPanel',
DEFAULT_EXPIRATION_ENABLED: 'defaultExpirationEnabled',
DEFAULT_EXPIRATION_MONTHS: 'defaultExpirationMonths',
DEFAULT_QUIET_ENABLED: 'defaultQuietEnabled',
DEFAULT_QUIET_DAYS: 'defaultQuietDays',
RESPECT_QUIET_TAGS: 'respectQuietTags',
GLOBAL_QUIET_MODE: 'globalQuietMode',
SHOW_RSS_FEED: 'showRssFeed',
PANE_MODE: 'paneMode',
ADD_RANDOM_RELAYS_TO_PUBLISH: 'addRandomRelaysToPublish',
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
ACCOUNT_FOLLOW_LIST_EVENT_MAP: 'accountFollowListEventMap', // deprecated
ACCOUNT_MUTE_LIST_EVENT_MAP: 'accountMuteListEventMap', // deprecated
ACCOUNT_MUTE_DECRYPTED_TAGS_MAP: 'accountMuteDecryptedTagsMap', // deprecated
ACCOUNT_PROFILE_EVENT_MAP: 'accountProfileEventMap', // deprecated
ACTIVE_RELAY_SET_ID: 'activeRelaySetId', // deprecated
FEED_TYPE: 'feedType' // deprecated
}
export const FONT_SIZE = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large'
} as const
/**
* Random public relays (from NIP-66 lively list; write-tested monitors preferred) merged into the
* publish relay picker. More candidates improve odds some accept open writes.
*/
export const RANDOM_PUBLISH_RELAY_COUNT = 5
/** Relays to query for NIP-66 relay monitoring events (30166), in addition to FAST_READ_RELAY_URLS. */
export const NIP66_DISCOVERY_RELAY_URLS = [
'wss://thecitadel.nostr1.com',
'wss://relay.nostr.watch',
'wss://relaypag.es'
]
// Relay with bookstr composite index support
export const BOOKSTR_RELAY_URLS = [
'wss://orly-relay.imwald.eu'
]
/** Relays that must never be used for publishing (read-only aggregators, etc.). */
export const READ_ONLY_RELAY_URLS = ['wss://aggr.nostr.land']
/** Relays that block kind 1 (microblogging); skip for kind 1 read and write. */
export const KIND_1_BLOCKED_RELAY_URLS = [
'wss://thecitadel.nostr1.com',
'wss://hist.nostr.land',
'wss://profiles.nostr1.com',
'wss://purplepag.es',
'wss://wikifreedia.xyz'
]
/** Relays that reject #e (and similar) tag filters; skip for reply/quote/stats fetches. */
export const E_TAG_FILTER_BLOCKED_RELAY_URLS = [
'wss://nostr.v0l.io',
'wss://nostr.sovbit.host'
]
// Optimized relay list for read operations (includes aggregator)
export const FAST_READ_RELAY_URLS = [
'wss://theforest.nostr1.com',
'wss://orly-relay.imwald.eu',
'wss://nostr.wine',
'wss://nostr.land',
'wss://nostr21.com',
'wss://thecitadel.nostr1.com',
'wss://aggr.nostr.land',
]
// Optimized relay list for write operations (no aggregator since it's read-only)
export const FAST_WRITE_RELAY_URLS = [
'wss://relay.damus.io',
'wss://relay.primal.net',
'wss://thecitadel.nostr1.com',
'wss://nos.lol'
]
/** Relays used for NIP-94 file metadata (kind 1063) / GIF discovery and publish.
* Publish to all of these so GIFs are discoverable across clients; some may be temporarily down. */
export const GIF_RELAY_URLS = [
'wss://relay.gifbuddy.lol',
'wss://relay.damus.io',
'wss://relay.primal.net',
'wss://thecitadel.nostr1.com',
'wss://nos.lol',
]
export const SEARCHABLE_RELAY_URLS = [
'wss://freelay.sovbit.host',
'wss://search.nos.today',
'wss://nostr.wine',
'wss://orly-relay.imwald.eu',
'wss://aggr.nostr.land',
'wss://thecitadel.nostr1.com',
'wss://relay.primal.net',
'wss://relay.damus.io',
'wss://relay.snort.social',
'wss://nos.lol',
'wss://nostr.mom',
'wss://relay.noswhere.com',
'wss://relay.wikifreedia.xyz',
'wss://nostr.einundzwanzig.space',
'wss://nostrelites.org',
'wss://relay.nsec.app',
'wss://bucket.coracle.social',
'wss://spatia-arcana.com',
'wss://nostr-pub.wellorder.net',
'wss://pyramid.fiatjaf.com/',
'wss://nostr.lopp.social/',
'wss://relay.dergigi.com/'
]
export const PROFILE_RELAY_URLS = [
'wss://nos.lol',
'wss://relay.damus.io',
'wss://profiles.nostr1.com',
'wss://purplepag.es'
]
// Combined relay URLs for profile fetching - includes both FAST_READ_RELAY_URLS and SEARCHABLE_RELAY_URLS
export const PROFILE_FETCH_RELAY_URLS = [...SEARCHABLE_RELAY_URLS, ...FAST_READ_RELAY_URLS, ...PROFILE_RELAY_URLS]
export const GROUP_METADATA_EVENT_KIND = 39000
export const ExtendedKind = {
PICTURE: 20,
VIDEO: 21,
SHORT_VIDEO: 22,
POLL: 1068,
POLL_RESPONSE: 1018,
COMMENT: 1111,
VOICE: 1222,
VOICE_COMMENT: 1244,
PUBLIC_MESSAGE: 24,
DISCUSSION: 11,
FAVORITE_RELAYS: 10012,
BLOCKED_RELAYS: 10006,
BLOSSOM_SERVER_LIST: 10063,
CACHE_RELAYS: 10432,
RELAY_REVIEW: 31987,
GROUP_METADATA: 39000,
GROUP_LIST: 10009, // NIP-51 Group List
ZAP_REQUEST: 9734,
ZAP_RECEIPT: 9735,
PUBLICATION: 30040,
WIKI_ARTICLE: 30818,
WIKI_ARTICLE_MARKDOWN: 30817,
PUBLICATION_CONTENT: 30041,
CITATION_INTERNAL: 30,
CITATION_EXTERNAL: 31,
CITATION_HARDCOPY: 32,
CITATION_PROMPT: 33,
RSS_FEED_LIST: 10895,
/** Client-only synthetic "parent" for RSS article threads; never published to relays */
RSS_THREAD_ROOT: 99999,
// NIP-89 Application Handlers
APPLICATION_HANDLER_RECOMMENDATION: 31989,
APPLICATION_HANDLER_INFO: 31990,
PAYMENT_INFO: 10133,
FOLLOW_PACK: 39089,
/** NIP-94 File Metadata (e.g. GIFs) */
FILE_METADATA: 1063,
/** NIP-66 Relay discovery (relay characteristics from NIP-11 or probing) */
RELAY_DISCOVERY: 30166,
/** NIP-66 Relay monitor announcement (intent to publish 30166 at a frequency) */
RELAY_MONITOR_ANNOUNCEMENT: 10166,
/** NIP-52 Date-based calendar event (all-day / multi-day) */
CALENDAR_EVENT_DATE: 31922,
/** NIP-52 Time-based calendar event */
CALENDAR_EVENT_TIME: 31923,
/** NIP-52 Calendar event RSVP */
CALENDAR_EVENT_RSVP: 31925,
/** NIP-A7 Spells: portable relay query filters (kind 777) */
SPELL: 777
}
/** NIP-52 calendar event kinds (addressable by d-tag); use in isReplaceableEvent. */
export const CALENDAR_EVENT_KINDS = [
ExtendedKind.CALENDAR_EVENT_DATE,
ExtendedKind.CALENDAR_EVENT_TIME
]
/** Maximum invitees for calendar event group invites (one kind 24 with all as p-tags). */
export const MAX_CALENDAR_INVITEES = 10
export const SUPPORTED_KINDS = [
kinds.ShortTextNote,
kinds.Repost,
ExtendedKind.PICTURE,
ExtendedKind.VIDEO,
ExtendedKind.SHORT_VIDEO,
ExtendedKind.POLL,
ExtendedKind.COMMENT,
ExtendedKind.VOICE,
ExtendedKind.VOICE_COMMENT,
// ExtendedKind.PUBLIC_MESSAGE, // Excluded - public messages should only appear in notifications
kinds.Highlights,
kinds.LongFormArticle,
ExtendedKind.RELAY_REVIEW,
ExtendedKind.DISCUSSION,
ExtendedKind.ZAP_RECEIPT,
ExtendedKind.CALENDAR_EVENT_DATE,
ExtendedKind.CALENDAR_EVENT_TIME,
ExtendedKind.PUBLICATION,
ExtendedKind.WIKI_ARTICLE,
ExtendedKind.WIKI_ARTICLE_MARKDOWN,
// ExtendedKind.PUBLICATION_CONTENT, // Excluded - publication content should only be embedded in publications
// NIP-89 Application Handlers
ExtendedKind.APPLICATION_HANDLER_RECOMMENDATION,
ExtendedKind.APPLICATION_HANDLER_INFO
]
/** Kinds for profile feed and favorites-style feeds: supported kinds except boosts (kind 6), publications, publication content, NIP-89 handlers. */
export const PROFILE_FEED_KINDS = SUPPORTED_KINDS.filter(
(k) =>
k !== kinds.Repost &&
k !== ExtendedKind.PUBLICATION &&
k !== ExtendedKind.PUBLICATION_CONTENT &&
k !== ExtendedKind.APPLICATION_HANDLER_RECOMMENDATION &&
k !== ExtendedKind.APPLICATION_HANDLER_INFO
)
/** Order for faux-spells in the feed / spell picker. */
export const FAUX_SPELL_ORDER = [
'notifications',
'discussions',
'following',
'followPacks',
'media',
'interests',
'bookmarks',
'calendar'
] as const
export const URL_REGEX =
/https?:\/\/[\w\p{L}\p{N}\p{M}&.\-/?=#@%+_:!~*]+(?:,[^\s.][\w\p{L}\p{N}\p{M}&.\-/?=#@%+_:!~*,]*)*[^\s.,;:'")\]}!?"'](?=\.|,\s|$|[^\w\p{L}\p{N}\p{M}&.\-/?=#@%+_:!~*,])/giu
export const WS_URL_REGEX =
/wss?:\/\/[\w\p{L}\p{N}\p{M}&.\-/?=#@%+_:!~*]+[^\s.,;:'")\]}!?"']/giu
export const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
/** @see {@link '@/lib/content-patterns'} — single source for emoji + nostr regexes */
export {
EMOJI_SHORT_CODE_MAX_INNER_LENGTH,
EMOJI_SHORT_CODE_REGEX,
EMBEDDED_EVENT_REGEX,
EMBEDDED_MENTION_REGEX
} from '@/lib/content-patterns'
export const HASHTAG_REGEX = /#[a-zA-Z0-9_\-\u00C0-\u017F\u0100-\u017F\u0180-\u024F\u1E00-\u1EFF]+/g
export const LN_INVOICE_REGEX = /(ln(?:bc|tb|bcrt))([0-9]+[munp]?)?1([02-9ac-hj-np-z]+)/g
export const EMOJI_REGEX =
/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F900}-\u{1F9FF}]|[\u{1FA70}-\u{1FAFF}]|[\u{1F004}]|[\u{1F0CF}]|[\u{1F18E}]|[\u{3030}]|[\u{2B50}]|[\u{2B55}]|[\u{2934}-\u{2935}]|[\u{2B05}-\u{2B07}]|[\u{2B1B}-\u{2B1C}]|[\u{3297}]|[\u{3299}]|[\u{303D}]|[\u{00A9}]|[\u{00AE}]|[\u{2122}]|[\u{23E9}-\u{23EF}]|[\u{23F0}]|[\u{23F3}]|[\u{FE00}-\u{FE0F}]|[\u{200D}]/gu
export const YOUTUBE_URL_REGEX =
/https?:\/\/(?:(?:www|m)\.)?(?:youtube\.com\/(?:watch\?[^#\s]*|embed\/[\w-]+|shorts\/[\w-]+|live\/[\w-]+)|youtu\.be\/[\w-]+)(?:\?[^#\s]*)?(?:#[^\s]*)?/gi
export const JUMBLE_PUBKEY = 'f4eb8e62add1340b9cadcd9861e669b2e907cea534e0f7f3ac974c11c758a51a'
export const CODY_PUBKEY = '8125b911ed0e94dbe3008a0be48cfe5cd0c0b05923cfff917ae7e87da8400883'
export const SILBERENGEL_PUBKEY = 'fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1'
export const NIP_96_SERVICE = [
'https://mockingyou.com',
'https://nostpic.com',
'https://nostr.build', // default
'https://nostrcheck.me',
'https://nostrmedia.com',
'https://files.sovbit.host'
]
export const DEFAULT_NIP_96_SERVICE = 'https://nostr.build'
export const DEFAULT_NOSTRCONNECT_RELAY = [
'wss://relay.nsec.app/',
'wss://bucket.coracle.social/',
'wss://relay.primal.net/',
'wss://thecitadel.nostr1.com/'
]
export const POLL_TYPE = {
MULTIPLE_CHOICE: 'multiplechoice',
SINGLE_CHOICE: 'singlechoice'
} as const
export const NOTIFICATION_LIST_STYLE = {
COMPACT: 'compact',
DETAILED: 'detailed'
} as const
export const MEDIA_AUTO_LOAD_POLICY = {
ALWAYS: 'always',
WIFI_ONLY: 'wifi-only',
NEVER: 'never'
} as const
export const DEFAULT_RSS_FEEDS = ['https://divineoffice.org/feed/']