Showing preview only (5,320K chars total). Download the full file or copy to clipboard to get everything.
Repository: v0l/snort
Branch: main
Commit: c29b4914a523
Files: 743
Total size: 4.8 MB
Directory structure:
gitextract_dgfwre9y/
├── .dockerignore
├── .drone.yml
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ ├── docker.yml
│ ├── nsite.yml
│ └── release.yml
├── .gitignore
├── .vscode/
│ ├── extensions.json
│ └── settings.json
├── AGENTS.md
├── Dockerfile
├── Dockerfile.prebuilt
├── LICENSE
├── README.md
├── biome.json
├── crowdin.yml
├── docker/
│ └── nginx.conf
├── functions/
│ ├── _middleware.ts
│ └── tsconfig.json
├── maintainers.yaml
├── package.json
├── packages/
│ ├── app/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── babel.config.json
│ │ ├── bun-env.d.ts
│ │ ├── bunfig.toml
│ │ ├── config/
│ │ │ ├── README.md
│ │ │ ├── default.json
│ │ │ ├── iris.json
│ │ │ ├── meku.json
│ │ │ ├── nostr.json
│ │ │ ├── phoenix.json
│ │ │ └── soloco.json
│ │ ├── custom.d.ts
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── public/
│ │ │ ├── iris/
│ │ │ │ ├── .well-known/
│ │ │ │ │ └── assetlinks.json
│ │ │ │ ├── _headers
│ │ │ │ ├── manifest.json
│ │ │ │ └── robots.txt
│ │ │ ├── nostr/
│ │ │ │ └── _headers
│ │ │ ├── phoenix/
│ │ │ │ ├── .well-known/
│ │ │ │ │ ├── apple-app-site-association
│ │ │ │ │ └── assetlinks.json
│ │ │ │ ├── _headers
│ │ │ │ ├── manifest.json
│ │ │ │ └── robots.txt
│ │ │ └── snort/
│ │ │ ├── .well-known/
│ │ │ │ ├── apple-app-site-association
│ │ │ │ └── assetlinks.json
│ │ │ ├── _headers
│ │ │ ├── manifest.json
│ │ │ └── robots.txt
│ │ ├── src/
│ │ │ ├── Agent/
│ │ │ │ └── system-prompt.ts
│ │ │ ├── Cache/
│ │ │ │ ├── CommunityLeadersStore.tsx
│ │ │ │ ├── GiftWrapCache.ts
│ │ │ │ ├── ProfileWorkerCache.ts
│ │ │ │ ├── RefreshFeedCache.ts
│ │ │ │ ├── RelaysWorkerCache.ts
│ │ │ │ ├── UserFollowsWorker.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── worker-cached.ts
│ │ │ ├── Components/
│ │ │ │ ├── AskSnort/
│ │ │ │ │ └── AskSnortInput.tsx
│ │ │ │ ├── Button/
│ │ │ │ │ ├── AsyncButton.tsx
│ │ │ │ │ ├── AsyncIcon.tsx
│ │ │ │ │ ├── BackButton.tsx
│ │ │ │ │ ├── CloseButton.tsx
│ │ │ │ │ ├── IconButton.tsx
│ │ │ │ │ ├── LogoutButton.tsx
│ │ │ │ │ └── NavLink.tsx
│ │ │ │ ├── Collapsed.tsx
│ │ │ │ ├── CommunityLeaders/
│ │ │ │ │ ├── Award.tsx
│ │ │ │ │ └── LeaderBadge.tsx
│ │ │ │ ├── Copy/
│ │ │ │ │ └── Copy.tsx
│ │ │ │ ├── DvmSelector.tsx
│ │ │ │ ├── Embed/
│ │ │ │ │ ├── AppleMusicEmbed.tsx
│ │ │ │ │ ├── BlossomBlob.tsx
│ │ │ │ │ ├── CashuNuts.tsx
│ │ │ │ │ ├── GenericPlayer.tsx
│ │ │ │ │ ├── Hashtag.tsx
│ │ │ │ │ ├── HyperText.tsx
│ │ │ │ │ ├── Invoice.tsx
│ │ │ │ │ ├── LinkPreview.tsx
│ │ │ │ │ ├── MagnetLink.tsx
│ │ │ │ │ ├── MediaElement.tsx
│ │ │ │ │ ├── Mention.tsx
│ │ │ │ │ ├── MixCloudEmbed.tsx
│ │ │ │ │ ├── NostrLink.tsx
│ │ │ │ │ ├── NostrNestsEmbed.tsx
│ │ │ │ │ ├── PubkeyList.tsx
│ │ │ │ │ ├── SoundCloudEmded.tsx
│ │ │ │ │ ├── SpotifyEmbed.tsx
│ │ │ │ │ ├── TidalEmbed.tsx
│ │ │ │ │ ├── TwitchEmbed.tsx
│ │ │ │ │ ├── UrlStatusCheck.tsx
│ │ │ │ │ ├── WavlakeEmbed.tsx
│ │ │ │ │ ├── YoutubeEmbed.tsx
│ │ │ │ │ ├── ZapstrEmbed.css
│ │ │ │ │ └── ZapstrEmbed.tsx
│ │ │ │ ├── ErrorBoundary.tsx
│ │ │ │ ├── ErrorOrOffline.tsx
│ │ │ │ ├── Event/
│ │ │ │ │ ├── Application.tsx
│ │ │ │ │ ├── Create/
│ │ │ │ │ │ ├── NoteCreator.tsx
│ │ │ │ │ │ ├── NoteCreatorButton.tsx
│ │ │ │ │ │ ├── OkResponseRow.tsx
│ │ │ │ │ │ └── util.ts
│ │ │ │ │ ├── DVMJobFeedback.tsx
│ │ │ │ │ ├── EventComponent.tsx
│ │ │ │ │ ├── FileUpload.tsx
│ │ │ │ │ ├── HiddenNote.tsx
│ │ │ │ │ ├── LoadMore.tsx
│ │ │ │ │ ├── LongFormText.tsx
│ │ │ │ │ ├── Markdown.tsx
│ │ │ │ │ ├── NostrFileHeader.tsx
│ │ │ │ │ ├── Note/
│ │ │ │ │ │ ├── ClientFingerprinting.tsx
│ │ │ │ │ │ ├── ClientTag.tsx
│ │ │ │ │ │ ├── Note.tsx
│ │ │ │ │ │ ├── NoteAppHandler.tsx
│ │ │ │ │ │ ├── NoteContent.tsx
│ │ │ │ │ │ ├── NoteContext.tsx
│ │ │ │ │ │ ├── NoteContextMenu.tsx
│ │ │ │ │ │ ├── NoteFooter/
│ │ │ │ │ │ │ ├── AsyncFooterIcon.tsx
│ │ │ │ │ │ │ ├── FooterZapButton.tsx
│ │ │ │ │ │ │ ├── LikeButton.tsx
│ │ │ │ │ │ │ ├── NoteFooter.tsx
│ │ │ │ │ │ │ ├── PowIcon.tsx
│ │ │ │ │ │ │ ├── ReplyButton.tsx
│ │ │ │ │ │ │ ├── RepostButton.tsx
│ │ │ │ │ │ │ └── ZapperQueue.tsx
│ │ │ │ │ │ ├── NoteHeader.tsx
│ │ │ │ │ │ ├── NoteQuote.tsx
│ │ │ │ │ │ ├── NoteText.tsx
│ │ │ │ │ │ ├── NoteTime.tsx
│ │ │ │ │ │ ├── ReactionsModal.tsx
│ │ │ │ │ │ ├── ReplyTag.tsx
│ │ │ │ │ │ ├── TranslationInfo.tsx
│ │ │ │ │ │ └── types.tsx
│ │ │ │ │ ├── NoteReaction.tsx
│ │ │ │ │ ├── Poll.tsx
│ │ │ │ │ ├── Reveal.tsx
│ │ │ │ │ ├── RevealMedia.tsx
│ │ │ │ │ ├── Thread/
│ │ │ │ │ │ ├── Subthread.tsx
│ │ │ │ │ │ ├── Thread.tsx
│ │ │ │ │ │ ├── ThreadRoute.tsx
│ │ │ │ │ │ └── util.ts
│ │ │ │ │ ├── Zap.tsx
│ │ │ │ │ ├── ZapButton.tsx
│ │ │ │ │ ├── ZapGoal.tsx
│ │ │ │ │ └── ZapsSummary.tsx
│ │ │ │ ├── Feed/
│ │ │ │ │ ├── ImageGridItem.tsx
│ │ │ │ │ ├── LoadMore.tsx
│ │ │ │ │ ├── RootTabItems.tsx
│ │ │ │ │ ├── RootTabs.tsx
│ │ │ │ │ ├── Timeline.tsx
│ │ │ │ │ ├── TimelineChunk.tsx
│ │ │ │ │ ├── TimelineFollows.tsx
│ │ │ │ │ ├── TimelineFragment.tsx
│ │ │ │ │ ├── TimelineRenderer.tsx
│ │ │ │ │ └── UsersFeed.tsx
│ │ │ │ ├── Icons/
│ │ │ │ │ ├── Alby.tsx
│ │ │ │ │ ├── BlueWallet.tsx
│ │ │ │ │ ├── Cashu.tsx
│ │ │ │ │ ├── ECash.tsx
│ │ │ │ │ ├── Icon.tsx
│ │ │ │ │ ├── NWC.tsx
│ │ │ │ │ ├── Nostrich.tsx
│ │ │ │ │ ├── Spinner.tsx
│ │ │ │ │ └── Toggle.tsx
│ │ │ │ ├── IntlProvider/
│ │ │ │ │ ├── IntlProvider.tsx
│ │ │ │ │ ├── IntlProviderUtils.tsx
│ │ │ │ │ ├── langStore.tsx
│ │ │ │ │ └── useLocale.tsx
│ │ │ │ ├── Invite.tsx
│ │ │ │ ├── LiveStream/
│ │ │ │ │ ├── LiveEvent.tsx
│ │ │ │ │ ├── LiveStreams.tsx
│ │ │ │ │ ├── VU.tsx
│ │ │ │ │ ├── livekit.tsx
│ │ │ │ │ └── nests-participants.tsx
│ │ │ │ ├── Modal/
│ │ │ │ │ └── Modal.tsx
│ │ │ │ ├── Nip5Service.tsx
│ │ │ │ ├── Offline.tsx
│ │ │ │ ├── PageSpinner.tsx
│ │ │ │ ├── PinPrompt/
│ │ │ │ │ └── PinPrompt.tsx
│ │ │ │ ├── Progress/
│ │ │ │ │ └── Progress.tsx
│ │ │ │ ├── ProxyImg.tsx
│ │ │ │ ├── QrCode.tsx
│ │ │ │ ├── ReBroadcaster.tsx
│ │ │ │ ├── Relay/
│ │ │ │ │ ├── Relay.tsx
│ │ │ │ │ ├── RelaysMetadata.tsx
│ │ │ │ │ ├── name.tsx
│ │ │ │ │ ├── paid.tsx
│ │ │ │ │ ├── permissions.tsx
│ │ │ │ │ ├── software.tsx
│ │ │ │ │ ├── status-label.tsx
│ │ │ │ │ ├── uptime-label.tsx
│ │ │ │ │ └── uptime.tsx
│ │ │ │ ├── Review.tsx
│ │ │ │ ├── RightWidgets/
│ │ │ │ │ ├── articles.tsx
│ │ │ │ │ ├── base.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── invite-friends.tsx
│ │ │ │ │ └── mini-stream.tsx
│ │ │ │ ├── ScrollToTop.tsx
│ │ │ │ ├── SearchBox/
│ │ │ │ │ └── SearchBox.tsx
│ │ │ │ ├── Spotlight/
│ │ │ │ │ ├── SpotlightMedia.tsx
│ │ │ │ │ ├── SpotlightThreadModal.tsx
│ │ │ │ │ └── context.tsx
│ │ │ │ ├── SuggestedProfiles.tsx
│ │ │ │ ├── TabSelectors/
│ │ │ │ │ └── TabSelectors.tsx
│ │ │ │ ├── Tasks/
│ │ │ │ │ ├── BackupKey.tsx
│ │ │ │ │ ├── DonateTask.tsx
│ │ │ │ │ ├── FollowMorePeople.tsx
│ │ │ │ │ ├── Nip5Task.tsx
│ │ │ │ │ ├── NoticeZapPool.tsx
│ │ │ │ │ ├── PendingChangesTask.tsx
│ │ │ │ │ ├── RenewSubscription.tsx
│ │ │ │ │ ├── TaskList.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Text/
│ │ │ │ │ ├── DisableMedia.tsx
│ │ │ │ │ ├── HighlightedText.tsx
│ │ │ │ │ ├── Text.tsx
│ │ │ │ │ └── const.ts
│ │ │ │ ├── Textarea/
│ │ │ │ │ ├── Textarea.css
│ │ │ │ │ └── Textarea.tsx
│ │ │ │ ├── Toaster/
│ │ │ │ │ └── Toaster.tsx
│ │ │ │ ├── Trending/
│ │ │ │ │ ├── ShortNote.tsx
│ │ │ │ │ ├── TrendingHashtags.tsx
│ │ │ │ │ ├── TrendingPosts.tsx
│ │ │ │ │ └── TrendingUsers.tsx
│ │ │ │ ├── Upload/
│ │ │ │ │ └── file-picker.tsx
│ │ │ │ ├── User/
│ │ │ │ │ ├── AnimalName.ts
│ │ │ │ │ ├── Avatar.tsx
│ │ │ │ │ ├── AvatarEditor.tsx
│ │ │ │ │ ├── AvatarGroup.tsx
│ │ │ │ │ ├── BadgeList.tsx
│ │ │ │ │ ├── Bookmarks.tsx
│ │ │ │ │ ├── Debug.tsx
│ │ │ │ │ ├── DisplayName.tsx
│ │ │ │ │ ├── FollowButton.tsx
│ │ │ │ │ ├── FollowDistanceIndicator.tsx
│ │ │ │ │ ├── FollowListBase.tsx
│ │ │ │ │ ├── FollowedBy.tsx
│ │ │ │ │ ├── Following.tsx
│ │ │ │ │ ├── FollowsYou.tsx
│ │ │ │ │ ├── MuteButton.tsx
│ │ │ │ │ ├── MutedList.tsx
│ │ │ │ │ ├── Nip05.tsx
│ │ │ │ │ ├── NoteToSelf.tsx
│ │ │ │ │ ├── ProfileCard.tsx
│ │ │ │ │ ├── ProfileCardWrapper.tsx
│ │ │ │ │ ├── ProfileImage.tsx
│ │ │ │ │ ├── ProfileLink.tsx
│ │ │ │ │ ├── ProfilePreview.tsx
│ │ │ │ │ ├── UserWebsiteLink.tsx
│ │ │ │ │ └── Username.tsx
│ │ │ │ ├── WarningNotice/
│ │ │ │ │ └── WarningNotice.tsx
│ │ │ │ ├── ZapModal/
│ │ │ │ │ ├── SuccessAction.tsx
│ │ │ │ │ ├── ZapModal.tsx
│ │ │ │ │ ├── ZapModalInput.tsx
│ │ │ │ │ ├── ZapModalInvoice.tsx
│ │ │ │ │ ├── ZapModalTitle.tsx
│ │ │ │ │ ├── ZapType.tsx
│ │ │ │ │ └── ZapTypeSelector.tsx
│ │ │ │ ├── flyout.tsx
│ │ │ │ ├── json.tsx
│ │ │ │ ├── kind-name.tsx
│ │ │ │ ├── messages.ts
│ │ │ │ ├── nip.tsx
│ │ │ │ └── zap-amount.tsx
│ │ │ ├── Db/
│ │ │ │ └── FuzzySearch.ts
│ │ │ ├── External/
│ │ │ │ ├── NostrBand.ts
│ │ │ │ ├── NostrServices.ts
│ │ │ │ ├── SnortApi.ts
│ │ │ │ ├── base.ts
│ │ │ │ └── index.ts
│ │ │ ├── Feed/
│ │ │ │ ├── ArticlesFeed.ts
│ │ │ │ ├── BadgesFeed.ts
│ │ │ │ ├── FollowersFeed.ts
│ │ │ │ ├── FollowsFeed.ts
│ │ │ │ ├── HashtagsFeed.ts
│ │ │ │ ├── LoginFeed.ts
│ │ │ │ ├── RelayState.ts
│ │ │ │ ├── RelaysFeed.tsx
│ │ │ │ ├── StatusFeed.ts
│ │ │ │ ├── TimelineFeed.ts
│ │ │ │ ├── WorkerRelayView.ts
│ │ │ │ └── ZapsFeed.ts
│ │ │ ├── Hooks/
│ │ │ │ ├── useAiAgent.ts
│ │ │ │ ├── useAppHandler.ts
│ │ │ │ ├── useBlindSpot.ts
│ │ │ │ ├── useBlossomServers.ts
│ │ │ │ ├── useCloseRelays.ts
│ │ │ │ ├── useCommunityLeaders.tsx
│ │ │ │ ├── useContentDiscovery.ts
│ │ │ │ ├── useCopy.ts
│ │ │ │ ├── useDiscoverMediaServers.ts
│ │ │ │ ├── useDvmLinks.ts
│ │ │ │ ├── useEventPublisher.tsx
│ │ │ │ ├── useFollowControls.ts
│ │ │ │ ├── useHistoryState.tsx
│ │ │ │ ├── useHorizontalScroll.tsx
│ │ │ │ ├── useHovering.ts
│ │ │ │ ├── useImgProxy.ts
│ │ │ │ ├── useKeyboardShortcut.ts
│ │ │ │ ├── useLists.tsx
│ │ │ │ ├── useLiveStreams.ts
│ │ │ │ ├── useLoading.tsx
│ │ │ │ ├── useLogin.tsx
│ │ │ │ ├── useLoginHandler.tsx
│ │ │ │ ├── useLoginRelays.tsx
│ │ │ │ ├── useMediaServerList.ts
│ │ │ │ ├── useModeration.tsx
│ │ │ │ ├── usePageDimensions.tsx
│ │ │ │ ├── usePreferences.ts
│ │ │ │ ├── useProfileLink.ts
│ │ │ │ ├── useProfileSearch.tsx
│ │ │ │ ├── useRates.tsx
│ │ │ │ ├── useRelays.tsx
│ │ │ │ ├── useTextTransformCache.tsx
│ │ │ │ ├── useTheme.tsx
│ │ │ │ ├── useTimelineChunks.ts
│ │ │ │ ├── useTimelineWindow.tsx
│ │ │ │ ├── useTraceTimeline.tsx
│ │ │ │ ├── useWindowSize.ts
│ │ │ │ └── useWoT.ts
│ │ │ ├── Pages/
│ │ │ │ ├── About.tsx
│ │ │ │ ├── Agent/
│ │ │ │ │ └── AgentPage.tsx
│ │ │ │ ├── CacheDebug.tsx
│ │ │ │ ├── ComponentDebug.tsx
│ │ │ │ ├── Deck/
│ │ │ │ │ ├── Articles.tsx
│ │ │ │ │ ├── Columns.tsx
│ │ │ │ │ └── DeckLayout.tsx
│ │ │ │ ├── Discover.tsx
│ │ │ │ ├── Donate/
│ │ │ │ │ ├── DonatePage.tsx
│ │ │ │ │ ├── ZapPoolDonateSection.tsx
│ │ │ │ │ └── const.ts
│ │ │ │ ├── ErrorPage.tsx
│ │ │ │ ├── FixedPage.tsx
│ │ │ │ ├── FreeNostrAddressPage.tsx
│ │ │ │ ├── HashTagsPage.tsx
│ │ │ │ ├── HelpPage.tsx
│ │ │ │ ├── Layout/
│ │ │ │ │ ├── Footer.tsx
│ │ │ │ │ ├── HasNotificationsMarker.tsx
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── LogoHeader.tsx
│ │ │ │ │ ├── NavSidebar.tsx
│ │ │ │ │ ├── NotificationsHeader.tsx
│ │ │ │ │ ├── ProfileMenu.tsx
│ │ │ │ │ ├── RightColumn.tsx
│ │ │ │ │ ├── WalletBalance.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── ListFeedPage.tsx
│ │ │ │ ├── Messages/
│ │ │ │ │ ├── ChatParticipant.tsx
│ │ │ │ │ ├── DM.tsx
│ │ │ │ │ ├── DmWindow.tsx
│ │ │ │ │ ├── MessagesPage.tsx
│ │ │ │ │ ├── NewChatWindow.tsx
│ │ │ │ │ ├── UnreadCount.tsx
│ │ │ │ │ └── WriteMessage.tsx
│ │ │ │ ├── NostrAddressPage.tsx
│ │ │ │ ├── NostrLinkHandler.tsx
│ │ │ │ ├── Notifications/
│ │ │ │ │ ├── NotificationGroup.tsx
│ │ │ │ │ ├── Notifications.tsx
│ │ │ │ │ ├── getNotificationContext.tsx
│ │ │ │ │ └── notificationContext.tsx
│ │ │ │ ├── Profile/
│ │ │ │ │ ├── AvatarSection.tsx
│ │ │ │ │ ├── MusicStatus.tsx
│ │ │ │ │ ├── ProfileDetails.tsx
│ │ │ │ │ ├── ProfilePage.tsx
│ │ │ │ │ ├── ProfileTabComponents.tsx
│ │ │ │ │ ├── ProfileTabSelectors.tsx
│ │ │ │ │ └── ProfileTabType.tsx
│ │ │ │ ├── Root/
│ │ │ │ │ ├── BlindSpots.tsx
│ │ │ │ │ ├── ConversationsTab.tsx
│ │ │ │ │ ├── DefaultTab.tsx
│ │ │ │ │ ├── FollowSets.tsx
│ │ │ │ │ ├── FollowedByFriendsTab.tsx
│ │ │ │ │ ├── ForYouTab.tsx
│ │ │ │ │ ├── Media.tsx
│ │ │ │ │ ├── NotesTab.tsx
│ │ │ │ │ ├── RelayFeedPage.tsx
│ │ │ │ │ ├── RootRoutes.tsx
│ │ │ │ │ ├── RootTabRoutes.tsx
│ │ │ │ │ └── TagsTab.tsx
│ │ │ │ ├── SearchPage.tsx
│ │ │ │ ├── TopicsPage.tsx
│ │ │ │ ├── ZapPool/
│ │ │ │ │ ├── ZapPool.css
│ │ │ │ │ ├── ZapPool.tsx
│ │ │ │ │ ├── ZapPoolPageInner.tsx
│ │ │ │ │ └── ZapPoolTarget.tsx
│ │ │ │ ├── messages.ts
│ │ │ │ ├── onboarding/
│ │ │ │ │ ├── discover.tsx
│ │ │ │ │ ├── fixedModeration.tsx
│ │ │ │ │ ├── fixedTopics.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── moderation.tsx
│ │ │ │ │ ├── profile.tsx
│ │ │ │ │ ├── routes.ts
│ │ │ │ │ ├── sign-in.tsx
│ │ │ │ │ ├── sign-up.tsx
│ │ │ │ │ └── topics.tsx
│ │ │ │ ├── settings/
│ │ │ │ │ ├── Accounts.tsx
│ │ │ │ │ ├── Cache.tsx
│ │ │ │ │ ├── Keys.css
│ │ │ │ │ ├── Keys.tsx
│ │ │ │ │ ├── Menu/
│ │ │ │ │ │ ├── Menu.tsx
│ │ │ │ │ │ └── SettingsMenuComponent.tsx
│ │ │ │ │ ├── Moderation.tsx
│ │ │ │ │ ├── Notifications.tsx
│ │ │ │ │ ├── Preferences.tsx
│ │ │ │ │ ├── Profile.tsx
│ │ │ │ │ ├── Referrals.tsx
│ │ │ │ │ ├── RelayInfo.tsx
│ │ │ │ │ ├── Relays.tsx
│ │ │ │ │ ├── Routes.tsx
│ │ │ │ │ ├── SnortNostrAddressService.tsx
│ │ │ │ │ ├── WalletSettings.tsx
│ │ │ │ │ ├── handle/
│ │ │ │ │ │ ├── LNAddress.tsx
│ │ │ │ │ │ ├── ListHandles.tsx
│ │ │ │ │ │ ├── Manage.tsx
│ │ │ │ │ │ ├── TransferHandle.tsx
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── routes.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── media-settings.tsx
│ │ │ │ │ ├── messages.ts
│ │ │ │ │ ├── relays/
│ │ │ │ │ │ └── discover.tsx
│ │ │ │ │ ├── saveRelays.tsx
│ │ │ │ │ ├── tools/
│ │ │ │ │ │ ├── follows-relay-health.tsx
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ ├── prune-follows.tsx
│ │ │ │ │ │ ├── routes.tsx
│ │ │ │ │ │ └── sync-account.tsx
│ │ │ │ │ └── wallet/
│ │ │ │ │ ├── Alby.tsx
│ │ │ │ │ ├── LNDHub.tsx
│ │ │ │ │ ├── NWC.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── routes.tsx
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── subscribe/
│ │ │ │ │ ├── ManageSubscription.tsx
│ │ │ │ │ ├── RenewSub.tsx
│ │ │ │ │ ├── SubscriptionCard.tsx
│ │ │ │ │ ├── index.css
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── utils.tsx
│ │ │ │ └── wallet/
│ │ │ │ ├── index.tsx
│ │ │ │ ├── price-chart.tsx
│ │ │ │ ├── receive.tsx
│ │ │ │ └── send.tsx
│ │ │ ├── State/
│ │ │ │ └── NoteCreator.ts
│ │ │ ├── Utils/
│ │ │ │ ├── Const.ts
│ │ │ │ ├── Login/
│ │ │ │ │ ├── Functions.ts
│ │ │ │ │ ├── LoginSession.ts
│ │ │ │ │ ├── MultiAccountStore.ts
│ │ │ │ │ ├── Nip7OsSigner.ts
│ │ │ │ │ ├── Preferences.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Nip05/
│ │ │ │ │ ├── ServiceProvider.ts
│ │ │ │ │ └── SnortServiceProvider.ts
│ │ │ │ ├── Notifications.ts
│ │ │ │ ├── Number.ts
│ │ │ │ ├── Subscription/
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Thread/
│ │ │ │ │ ├── ThreadContextWrapper.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Upload/
│ │ │ │ │ ├── blossom.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── ZapPoolController.ts
│ │ │ │ ├── emoji-search.ts
│ │ │ │ ├── getEventMedia.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── nip6.ts
│ │ │ │ ├── stream.ts
│ │ │ │ └── wasm.ts
│ │ │ ├── Wallet/
│ │ │ │ └── index.ts
│ │ │ ├── assets/
│ │ │ │ └── fonts/
│ │ │ │ └── inter.css
│ │ │ ├── bench.html
│ │ │ ├── benchmarks.ts
│ │ │ ├── chat/
│ │ │ │ ├── index.ts
│ │ │ │ └── nip17.ts
│ │ │ ├── hug.json
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── lang.json
│ │ │ ├── service-worker.ts
│ │ │ ├── setupTests.ts
│ │ │ ├── system.ts
│ │ │ ├── translations/
│ │ │ │ ├── af_ZA.json
│ │ │ │ ├── ar_SA.json
│ │ │ │ ├── az_AZ.json
│ │ │ │ ├── ca_ES.json
│ │ │ │ ├── cs_CZ.json
│ │ │ │ ├── da_DK.json
│ │ │ │ ├── de_DE.json
│ │ │ │ ├── el_GR.json
│ │ │ │ ├── en.json
│ │ │ │ ├── es_ES.json
│ │ │ │ ├── fa_IR.json
│ │ │ │ ├── fi_FI.json
│ │ │ │ ├── fr_FR.json
│ │ │ │ ├── he_IL.json
│ │ │ │ ├── hr_HR.json
│ │ │ │ ├── hu_HU.json
│ │ │ │ ├── id_ID.json
│ │ │ │ ├── it_IT.json
│ │ │ │ ├── ja_JP.json
│ │ │ │ ├── ko_KR.json
│ │ │ │ ├── ms_MY.json
│ │ │ │ ├── nl_NL.json
│ │ │ │ ├── no_NO.json
│ │ │ │ ├── pa_IN.json
│ │ │ │ ├── pl_PL.json
│ │ │ │ ├── pt_BR.json
│ │ │ │ ├── pt_PT.json
│ │ │ │ ├── ro_RO.json
│ │ │ │ ├── ru_RU.json
│ │ │ │ ├── sr_SP.json
│ │ │ │ ├── sv_SE.json
│ │ │ │ ├── sw_KE.json
│ │ │ │ ├── ta_IN.json
│ │ │ │ ├── th_TH.json
│ │ │ │ ├── tr_TR.json
│ │ │ │ ├── uk_UA.json
│ │ │ │ ├── vi_VN.json
│ │ │ │ ├── zh_CN.json
│ │ │ │ └── zh_TW.json
│ │ │ └── tz.json
│ │ ├── tests/
│ │ │ ├── Utils.test.ts
│ │ │ └── worker-cached.test.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── bot/
│ │ ├── README.md
│ │ ├── example/
│ │ │ └── simple.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ ├── shared/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── SortedMap/
│ │ │ │ ├── SortedMap.test.ts
│ │ │ │ └── SortedMap.ts
│ │ │ ├── cache-store.ts
│ │ │ ├── const.ts
│ │ │ ├── custom.d.ts
│ │ │ ├── external-store.ts
│ │ │ ├── feed-cache.ts
│ │ │ ├── imgproxy.ts
│ │ │ ├── index.ts
│ │ │ ├── invoices.ts
│ │ │ ├── lnurl.ts
│ │ │ ├── tlv.ts
│ │ │ ├── utils.ts
│ │ │ └── work-queue.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ ├── system/
│ │ ├── .npmignore
│ │ ├── AUDIT.md
│ │ ├── README.md
│ │ ├── examples/
│ │ │ └── simple.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── background-loader.ts
│ │ │ ├── cache/
│ │ │ │ ├── index.ts
│ │ │ │ ├── user-follows-lists.ts
│ │ │ │ ├── user-metadata.ts
│ │ │ │ └── user-relays.ts
│ │ │ ├── cache-relay.ts
│ │ │ ├── connection-cache-relay.ts
│ │ │ ├── connection-pool.ts
│ │ │ ├── connection-stats.ts
│ │ │ ├── connection.ts
│ │ │ ├── const.ts
│ │ │ ├── encryption/
│ │ │ │ ├── index.ts
│ │ │ │ ├── nip44.ts
│ │ │ │ └── pin-encrypted.ts
│ │ │ ├── event-builder.ts
│ │ │ ├── event-ext.ts
│ │ │ ├── event-kind.ts
│ │ │ ├── event-publisher.ts
│ │ │ ├── filter-cache-layer.ts
│ │ │ ├── impl/
│ │ │ │ ├── nip10.ts
│ │ │ │ ├── nip11.ts
│ │ │ │ ├── nip18.ts
│ │ │ │ ├── nip22.ts
│ │ │ │ ├── nip25.ts
│ │ │ │ ├── nip4.ts
│ │ │ │ ├── nip44.ts
│ │ │ │ ├── nip46.ts
│ │ │ │ ├── nip55.ts
│ │ │ │ ├── nip57.ts
│ │ │ │ ├── nip7.ts
│ │ │ │ ├── nip90.ts
│ │ │ │ ├── nip92.ts
│ │ │ │ └── nip94.ts
│ │ │ ├── index.ts
│ │ │ ├── negentropy/
│ │ │ │ ├── accumulator.ts
│ │ │ │ ├── negentropy-flow.ts
│ │ │ │ ├── negentropy.ts
│ │ │ │ ├── utils.ts
│ │ │ │ ├── vector-storage.ts
│ │ │ │ └── wrapped-buffer.ts
│ │ │ ├── nips.ts
│ │ │ ├── nostr-link.ts
│ │ │ ├── nostr-system.ts
│ │ │ ├── nostr.ts
│ │ │ ├── note-collection.ts
│ │ │ ├── outbox/
│ │ │ │ ├── index.ts
│ │ │ │ ├── outbox-model.ts
│ │ │ │ └── relay-loader.ts
│ │ │ ├── pow-util.ts
│ │ │ ├── pow-worker.ts
│ │ │ ├── pow.ts
│ │ │ ├── profile-cache.ts
│ │ │ ├── query-manager.ts
│ │ │ ├── query-optimizer/
│ │ │ │ ├── index.ts
│ │ │ │ ├── request-expander.ts
│ │ │ │ ├── request-merger.ts
│ │ │ │ └── request-splitter.ts
│ │ │ ├── query.ts
│ │ │ ├── relays.ts
│ │ │ ├── request-builder.ts
│ │ │ ├── request-matcher.ts
│ │ │ ├── request-router.ts
│ │ │ ├── request-trim.ts
│ │ │ ├── signer.ts
│ │ │ ├── sync/
│ │ │ │ ├── diff-sync.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── json-in-event-sync.ts
│ │ │ │ ├── range-sync.ts
│ │ │ │ └── safe-sync.ts
│ │ │ ├── system-base.ts
│ │ │ ├── system.ts
│ │ │ ├── text.ts
│ │ │ ├── trace-timeline.ts
│ │ │ ├── user-state.ts
│ │ │ └── utils.ts
│ │ ├── tests/
│ │ │ ├── background-loader.test.ts
│ │ │ ├── event-ext.test.ts
│ │ │ ├── feed-cache-subscribe.test.ts
│ │ │ ├── negentropy.test.ts
│ │ │ ├── nip10.test.ts
│ │ │ ├── nip18.test.ts
│ │ │ ├── node.ts
│ │ │ ├── nostr-link.test.ts
│ │ │ ├── note-collection-comprehensive.test.ts
│ │ │ ├── note-collection.test.ts
│ │ │ ├── pin-encrypted.test.ts
│ │ │ ├── query-comprehensive.test.ts
│ │ │ ├── query-manager-comprehensive.test.ts
│ │ │ ├── query-manager-race.test.ts
│ │ │ ├── query-system-edge-cases.test.ts
│ │ │ ├── request-builder.test.ts
│ │ │ ├── request-expander.test.ts
│ │ │ ├── request-matcher.test.ts
│ │ │ ├── request-merger.test.ts
│ │ │ ├── request-splitter.test.ts
│ │ │ ├── setupTests.ts
│ │ │ ├── text.test.ts
│ │ │ └── utils.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ ├── system-react/
│ │ ├── README.md
│ │ ├── example/
│ │ │ └── example.tsx
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── TraceTimeline/
│ │ │ │ ├── TraceStatsView.tsx
│ │ │ │ ├── TraceTimeline.css
│ │ │ │ ├── TraceTimelineDetailPopup.tsx
│ │ │ │ ├── TraceTimelineOverlay.tsx
│ │ │ │ └── TraceTimelineView.tsx
│ │ │ ├── context.tsx
│ │ │ ├── index.ts
│ │ │ ├── useCached.ts
│ │ │ ├── useEventFeed.ts
│ │ │ ├── useEventReactions.tsx
│ │ │ ├── useReactions.ts
│ │ │ ├── useRequestBuilder.tsx
│ │ │ ├── useSystemState.tsx
│ │ │ ├── useUserProfile.ts
│ │ │ └── useUserSearch.tsx
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ ├── system-svelte/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── request-builder.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ ├── system-wasm/
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ ├── benches/
│ │ │ └── basic.rs
│ │ ├── package.json
│ │ ├── pkg/
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ ├── system_wasm.d.ts
│ │ │ ├── system_wasm.js
│ │ │ ├── system_wasm_bg.js
│ │ │ ├── system_wasm_bg.wasm
│ │ │ └── system_wasm_bg.wasm.d.ts
│ │ ├── src/
│ │ │ ├── diff.rs
│ │ │ ├── filter.rs
│ │ │ ├── lib.rs
│ │ │ ├── merge.rs
│ │ │ ├── pow.rs
│ │ │ └── verify.rs
│ │ ├── system-query.iml
│ │ └── typedoc.json
│ ├── wallet/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── AlbyWallet.ts
│ │ │ ├── LNDHub.ts
│ │ │ ├── NostrWalletConnect.ts
│ │ │ ├── WebLN.ts
│ │ │ ├── custom.d.ts
│ │ │ ├── index.ts
│ │ │ └── zapper.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.json
│ └── worker-relay/
│ ├── README.md
│ ├── example/
│ │ └── basic.ts
│ ├── package.json
│ ├── src/
│ │ ├── custom.d.ts
│ │ ├── debug.ts
│ │ ├── forYouFeed.ts
│ │ ├── index.ts
│ │ ├── interface.ts
│ │ ├── memory-relay.ts
│ │ ├── queue.ts
│ │ ├── sqlite/
│ │ │ ├── fixers.ts
│ │ │ ├── migrations.ts
│ │ │ └── sqlite-relay.ts
│ │ ├── types.ts
│ │ └── worker.ts
│ ├── tsconfig.json
│ └── typedoc.json
├── src-tauri/
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── build.rs
│ ├── capabilities/
│ │ └── migrated.json
│ ├── gen/
│ │ └── schemas/
│ │ ├── acl-manifests.json
│ │ ├── capabilities.json
│ │ ├── desktop-schema.json
│ │ └── linux-schema.json
│ ├── src/
│ │ └── main.rs
│ └── tauri.conf.json
└── zapstore.yaml
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
**/node_modules
**/.idea
**/target
================================================
FILE: .drone.yml
================================================
---
kind: pipeline
type: kubernetes
name: docker
concurrency:
limit: 1
trigger:
branch:
- main
event:
- push
metadata:
namespace: git
steps:
- name: Fetch tags
image: alpine/git
commands:
- git fetch --tags
- name: Build site
image: node:current
volumes:
- name: cache
path: /cache
environment:
NODE_CONFIG_ENV: default
commands:
- apt update && apt install -y git curl unzip
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install
- bun run build
- name: build docker image
image: docker
privileged: true
volumes:
- name: cache
path: /cache
environment:
TOKEN:
from_secret: docker_hub
commands:
- dockerd &
- docker login -u voidic -p $TOKEN
- docker buildx create --platform linux/amd64,linux/arm64 --bootstrap --use
- docker buildx build -t voidic/snort:latest --platform linux/amd64,linux/arm64 --push -f Dockerfile.prebuilt .
- kill $(cat /var/run/docker.pid)
volumes:
- name: cache
claim:
name: docker-cache
---
kind: pipeline
type: kubernetes
name: test-lint
concurrency:
limit: 1
metadata:
namespace: git
steps:
- name: Test/Lint
image: node:current
volumes:
- name: cache
path: /cache
environment:
NODE_CONFIG_ENV: default
commands:
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install
- bun run build
- bun test
- bunx --bun biome lint
volumes:
- name: cache
claim:
name: docker-cache
---
kind: pipeline
type: kubernetes
name: crowdin
concurrency:
limit: 1
trigger:
branch:
- main
event:
- push
metadata:
namespace: git
steps:
- name: Push/Pull translations
image: node:current
volumes:
- name: cache
path: /cache
environment:
NODE_CONFIG_ENV: default
TOKEN:
from_secret: gitea
CTOKEN:
from_secret: crowdin
commands:
- git config --global user.email drone@v0l.io
- git config --global user.name "Drone CI"
- git remote set-url origin https://drone:$TOKEN@git.v0l.io/Kieran/snort.git
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install
- bunx @crowdin/cli upload sources -b main -T $CTOKEN
- bunx @crowdin/cli pull -b main -T $CTOKEN
- bunx --bun biome lint --write
- git add .
- >
if output=$(git status --porcelain) && [ -n "$output" ]; then
git commit -a -m "chore: Update translations"
git push -u origin main
fi
volumes:
- name: cache
claim:
name: docker-cache
---
kind: pipeline
type: kubernetes
name: docker-release
concurrency:
limit: 1
trigger:
event:
- tag
metadata:
namespace: git
steps:
- name: Fetch tags
image: alpine/git
commands:
- git fetch --tags
- name: Build site
image: node:current
volumes:
- name: cache
path: /cache
environment:
NODE_CONFIG_ENV: default
commands:
- apt update && apt install -y git curl unzip
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install
- bun run build
- name: build docker image
image: docker
privileged: true
volumes:
- name: cache
path: /cache
environment:
TOKEN:
from_secret: docker_hub
commands:
- dockerd &
- docker login -u voidic -p $TOKEN
- docker buildx create --platform linux/amd64,linux/arm64 --bootstrap --use
- docker buildx build -t voidic/snort:$DRONE_TAG --platform linux/amd64,linux/arm64 --push -f Dockerfile.prebuilt .
- kill $(cat /var/run/docker.pid)
volumes:
- name: cache
claim:
name: docker-cache
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: ""
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser: [e.g. stock browser, safari]
- Version: [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ""
labels: ""
assignees: ""
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/docker.yml
================================================
name: Docker
on:
push:
branches:
- main
tags:
- v[0-9]+.[0-9]+.[0-9]+
- v[0-9]+.[0-9]+.[0-9]+-*
jobs:
docker-latest:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install
- name: Build site
run: bun run build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: voidic
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.prebuilt
platforms: linux/amd64,linux/arm64
push: true
tags: voidic/snort:latest
docker-release:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install
- name: Build site
run: bun run build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: voidic
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.prebuilt
platforms: linux/amd64,linux/arm64
push: true
tags: voidic/snort:${{ github.ref_name }}
================================================
FILE: .github/workflows/nsite.yml
================================================
name: Deploy nsite
on:
workflow_dispatch: # temporarily disabled - nsite-cli upload hangs indefinitely
# push:
# branches:
# - main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install Dependencies
run: bun install
- name: Build
run: bun run build
- name: Redirect 404 to Index for SPA
run: cp packages/app/build/index.html packages/app/build/404.html
- name: Deploy nsite
run: bunx nsite-cli upload packages/app/build --verbose --purge --privatekey ${{ secrets.NSITE_KEY }} --servers 'https://nostr.download,https://blossom.band'
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
tags:
- v[0-9]+.[0-9]+.[0-9]+
- v[0-9]+.[0-9]+.[0-9]+-*
env:
DOCKER_CLI_EXPERIMENTAL: enabled
TAG_FMT: "^refs/tags/(((.?[0-9]+){3,4}))$"
jobs:
app:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install frontend dependencies
run: bun install
- name: Build Site
run: bun run build
- name: Copy files
run: |-
git clone --depth 1 --branch ${{ github.ref_name }} https://github.com/v0l/snort_android.git
mkdir -p snort_android/app/src/main/assets/
cp -r packages/app/build/* snort_android/app/src/main/assets/
- name: Build AAB
working-directory: snort_android
run: ./gradlew clean bundleRelease --stacktrace
- name: Build APK
working-directory: snort_android
run: ./gradlew assembleRelease --stacktrace
- name: Sign AAB
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: snort_android/app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "35.0.0"
- name: Sign APK
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: snort_android/app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "35.0.0"
- name: Rename files
run: |-
mkdir -p snort_android/app/release
mv snort_android/app/build/outputs/bundle/release/app-release.aab snort_android/app/release/snort-${{ github.ref_name }}.aab
mv snort_android/app/build/outputs/apk/release/app-universal-release-unsigned-signed.apk snort_android/app/release/snort-universal-${{ github.ref_name }}.apk
mv snort_android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned-signed.apk snort_android/app/release/snort-arm64-v8a-${{ github.ref_name }}.apk
mv snort_android/app/build/outputs/apk/release/app-x86_64-release-unsigned-signed.apk snort_android/app/release/snort-x86_64-${{ github.ref_name }}.apk
mv snort_android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned-signed.apk snort_android/app/release/snort-armeabi-v7a-${{ github.ref_name }}.apk
- name: Upload assets
uses: softprops/action-gh-release@v1
with:
files: |
snort_android/app/release/snort-${{ github.ref_name }}.aab
snort_android/app/release/snort-universal-${{ github.ref_name }}.apk
snort_android/app/release/snort-arm64-v8a-${{ github.ref_name }}.apk
snort_android/app/release/snort-x86_64-${{ github.ref_name }}.apk
snort_android/app/release/snort-armeabi-v7a-${{ github.ref_name }}.apk
================================================
FILE: .gitignore
================================================
node_modules/
.idea
.pnp.*
dist/
*.tgz
*.log
.DS_Store
.pnp*
docs/
.wrangler/
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": [
"arcanis.vscode-zipfs",
"biomejs.biome"
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/node_modules": true
},
"search.exclude": {},
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
================================================
FILE: AGENTS.md
================================================
# AGENTS.md - Snort Codebase Guidelines
This document provides guidelines for AI coding agents working in the Snort codebase.
## Project Overview
Snort is a **Nostr UI client** built with:
- **Language**: TypeScript (strict mode)
- **Framework**: React 19 (main app)
- **Build Tool**: Vite
- **Package Manager**: Bun (required - do not use npm/yarn/pnpm)
- **Monorepo**: Bun workspaces
### Package Structure
```
packages/
app/ # Main React web application (@snort/app)
system/ # Core Nostr system library (@snort/system)
shared/ # Shared utilities (@snort/shared)
wallet/ # Wallet integration (@snort/wallet)
worker-relay/ # Service worker relay (@snort/worker-relay)
system-react/ # React hooks for system (@snort/system-react)
```
## Build Commands
```bash
# Install dependencies
bun install
# Build all packages (order matters - shared -> system -> wallet -> worker-relay -> app)
bun run build
# Start dev server
bun run start
# Build specific package
bun --cwd=packages/app run build
bun --cwd=packages/system run build
```
## Testing
**Framework**: Bun's built-in test runner (`bun:test`)
```bash
# Run all tests
bun test
# Run tests in a specific package
cd packages/system && bun test
# Run a single test file
bun test packages/system/tests/nip10.test.ts
# Run tests matching a pattern
bun test --test-name-pattern="parseThread"
# Run test files matching a name
bun test nip10
```
**Test file locations**:
- `packages/system/tests/*.test.ts` (most tests)
- `packages/app/tests/*.test.ts`
- `packages/shared/src/**/*.test.ts`
## Linting & Formatting
**Tool**: Biome (not ESLint/Prettier)
```bash
# Lint and fix
bunx --bun biome lint --write
# Pre-commit (extract translations + lint)
bun run pre:commit
```
## Code Style Guidelines
### Formatting (Biome)
- **Indentation**: 2 spaces
- **Line width**: 120 characters
- **Semicolons**: as needed (omit when optional)
- **Quotes**: single quotes for JS/TS, double quotes for JSX attributes
- **Trailing commas**: all
- **Arrow parentheses**: as needed
- **Line endings**: LF
### TypeScript
- **Strict mode** is enabled
- **Target**: ESNext
- **Module resolution**: Bundler
- Use `type` imports for types: `import type { Foo } from './bar'`
- Path alias in app: `@/*` maps to `./src/*`
### Imports
- Biome auto-organizes imports
- Group order: external packages, then internal modules
- Use workspace packages: `@snort/shared`, `@snort/system`, etc.
### Naming Conventions
- **Files**: PascalCase for React components (`Note.tsx`, `EventBuilder.ts`)
- **Files**: kebab-case or camelCase for utilities (`event-builder.ts`, `nostr-link.ts`)
- **Components**: PascalCase (`function Note()`, `function EventComponent()`)
- **Hooks**: camelCase with `use` prefix (`useLogin`, `useModeration`)
- **Types/Interfaces**: PascalCase (`TaggedNostrEvent`, `NoteProps`)
- **Constants**: UPPER_SNAKE_CASE or PascalCase
- **Private class fields**: Use `#` prefix (`#kind`, `#content`)
### React Patterns
- Functional components only
- Use hooks for state management
- Custom hooks in `src/Hooks/` directory
- Components in `src/Components/` with subdirectories by feature
- Pages in `src/Pages/`
### Error Handling
- Use try/catch for async operations
- Prefer optional chaining (`?.`) and nullish coalescing (`??`)
- Return `undefined` for not-found cases rather than throwing
### Nostr-Specific Patterns
- Event kinds defined in `packages/system/src/event-kind.ts`
- NIP implementations in `packages/system/src/impl/nip*.ts`
- Use `EventBuilder` class for constructing events
- Use `NostrLink` for referencing events/profiles
- Tags are arrays: `["e", "eventId", "relay", "marker"]`
## Project-Specific Notes
### Bun Requirements
- Always use `bun` instead of `node`, `npm`, `yarn`, or `pnpm`
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun install` instead of `npm install`
- Use `bun run <script>` instead of `npm run <script>`
- Bun automatically loads `.env` files - do not use dotenv
### Internationalization
- Uses react-intl for i18n
- Translations in `packages/app/src/translations/`
- Extract strings: `bun --cwd=packages/app run intl-extract`
- Compile translations: `bun --cwd=packages/app run intl-compile`
### Desktop App
- Tauri support in `src-tauri/` (Rust backend)
### Common Imports
```typescript
// From system package
import { EventKind, NostrLink, type TaggedNostrEvent } from "@snort/system"
// From shared package
import { NostrPrefix, unixNow, getPublicKey } from "@snort/shared"
// App internal imports (using path alias)
import { Relay } from "@/Cache"
import useModeration from "@/Hooks/useModeration"
```
### Test Patterns
```typescript
import { describe, expect, test } from "bun:test"
describe("FeatureName", () => {
test("should do something", () => {
expect(result).toBe(expected)
})
})
```
## Quick Reference
| Task | Command |
|------|---------|
| Install deps | `bun install` |
| Build all | `bun run build` |
| Dev server | `bun run start` |
| Run all tests | `bun test` |
| Run single test | `bun test path/to/file.test.ts` |
| Lint & fix | `bunx --bun biome lint --write` |
| Pre-commit | `bun run pre:commit` |
================================================
FILE: Dockerfile
================================================
FROM oven/bun:latest AS build
WORKDIR /src
RUN apt update \
&& apt install -y --no-install-recommends git ca-certificates \
&& git clone --single-branch -b main https://github.com/v0l/snort \
&& cd snort \
&& bun install \
&& bun run build
FROM nginxinc/nginx-unprivileged:mainline-alpine
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /src/snort/packages/app/build /usr/share/nginx/html
================================================
FILE: Dockerfile.prebuilt
================================================
FROM nginxinc/nginx-unprivileged:mainline-alpine
COPY packages/app/build /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 Kieran (v0l)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
## Snort
Snort is a nostr UI built with React aiming for speed and efficiency.
Snort supports the following NIP's:
- [x] NIP-01: Basic protocol flow description
- [x] NIP-02: Contact List and Petnames (No petname support)
- [ ] NIP-03: OpenTimestamps Attestations for Events
- [x] NIP-04: Encrypted Direct Message
- [x] NIP-05: Mapping Nostr keys to DNS-based internet identifiers
- [x] NIP-06: Basic key derivation from mnemonic seed phrase
- [x] NIP-07: `window.nostr` capability for web browsers
- [x] NIP-08: Handling Mentions
- [x] NIP-09: Event Deletion
- [x] NIP-10: Conventions for clients' use of `e` and `p` tags in text events
- [x] NIP-11: Relay Information Document
- [x] NIP-13: Proof of Work
- [ ] NIP-14: Subject tag in text events
- [x] NIP-18: Reposts
- [x] NIP-19: bech32-encoded entities
- [x] NIP-21: `nostr:` Protocol handler (`web+nostr`)
- [x] NIP-23: Long form content
- [x] NIP-25: Reactions
- [x] NIP-26: Delegated Event Signing (Display delegated signings only)
- [x] NIP-27: Text note references
- [x] NIP-28: Public Chat
- [x] NIP-30: Custom Emoji
- [x] NIP-31: Alt tag for unknown events
- [x] NIP-36: Sensitive Content
- [x] NIP-38: User Statuses
- [ ] NIP-39: External Identities
- [ ] NIP-40: Expiration Timestamp
- [x] NIP-42: Authentication of clients to relays
- [x] NIP-44: Versioned encryption
- [x] NIP-46: Nostr connect (+bunker)
- [x] NIP-47: Nostr wallet connect
- [x] NIP-50: Search
- [x] NIP-51: Lists
- [x] NIP-53: Live Events
- [x] NIP-55: Android signer application
- [x] NIP-57: Zaps
- [x] NIP-58: Badges
- [x] NIP-59: Gift Wrap
- [x] NIP-65: Relay List Metadata
- [x] NIP-75: Zap Goals
- [x] NIP-78: App specific data
- [x] NIP-89: App handlers
- [x] NIP-90: Data Vending Machines
- [x] NIP-94: File Metadata
- [x] NIP-96: HTTP File Storage Integration (Draft)
- [x] NIP-98: HTTP Auth
### Translations
[](https://crowdin.com/project/snort)
Translations are managed on [Crowdin](https://crowdin.com/project/snort)
To extract translations run:
```bash
bun run pre:commit
```
This will create the source file `packages/app/src/translations/en.json`
================================================
FILE: biome.json
================================================
{
"$schema": "https://biomejs.dev/schemas/2.3.9/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 120,
"attributePosition": "auto",
"bracketSameLine": false,
"bracketSpacing": true,
"expand": "auto",
"useEditorconfig": true,
"includes": [
"**",
"!**/.yarn/",
"!**/build/",
"!**/.vscode/",
"!**/.github/",
"!**/transifex.yml",
"!**/dist/",
"!**/src-tauri/",
"!**/target/"
]
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"useExhaustiveDependencies": "off"
}
}
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "asNeeded",
"arrowParentheses": "asNeeded",
"bracketSameLine": false,
"quoteStyle": "double",
"attributePosition": "auto",
"bracketSpacing": true
}
},
"html": {
"formatter": {
"indentScriptAndStyle": false,
"selfCloseVoidElements": "always"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
================================================
FILE: crowdin.yml
================================================
project_id: 568149
preserve_hierarchy: true
files:
- source: packages/app/src/translations/en.json
translation: packages/app/src/translations/%locale_with_underscore%.json
================================================
FILE: docker/nginx.conf
================================================
server {
listen 8080 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html;
add_header Content-Security-Policy "default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com";
add_header Cross-Origin-Opener-Policy same-origin;
location / {
try_files $uri $uri/ /index.html =404;
}
}
================================================
FILE: functions/_middleware.ts
================================================
type Env = {}
const HOST = "snort.social";
export const onRequest: PagesFunction<Env> = async context => {
const u = new URL(context.request.url);
const prefixes = ["npub1", "nprofile1", "naddr1", "nevent1", "note1"];
const isEntityPath = prefixes.some(
a => u.pathname.startsWith(`/${a}`) || u.pathname.startsWith(`/e/${a}`) || u.pathname.startsWith(`/p/${a}`),
);
const nostrAddress = u.pathname.match(/^\/([a-zA-Z0-9_]+)$/i);
const next = await context.next();
if (u.pathname != "/" && (isEntityPath || nostrAddress)) {
//console.log("Handeling path: ", u.pathname, isEntityPath, nostrAddress[1]);
try {
let id = u.pathname.split("/").at(-1);
if (!isEntityPath && nostrAddress) {
id = `${id}@${HOST}`;
}
const fetchApi = `https://nostr-rs-api.v0l.io/opengraph/${id}?canonical=${encodeURIComponent(
`https://${HOST}/%s`,
)}`;
console.log("Fetching tags from: ", fetchApi);
const rsp = await fetch(fetchApi, {
method: "POST",
body: await next.arrayBuffer(),
headers: {
"user-agent": `SnortFunctions/1.0 (https://${HOST})`,
"content-type": "text/html",
accept: "text/html",
},
});
if (rsp.ok) {
const body = await rsp.text();
if (body.length > 0) {
return new Response(body, {
headers: {
...Object.fromEntries(rsp.headers.entries()),
"cache-control": "no-cache",
},
});
}
}
} catch (e) {
console.error(e);
}
}
return next;
};
================================================
FILE: functions/tsconfig.json
================================================
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"lib": ["esnext"],
"types": ["@cloudflare/workers-types"]
}
}
================================================
FILE: maintainers.yaml
================================================
maintainers:
- npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
relays:
- wss://relay.snort.social/
- wss://pyramid.fiatjaf.com/
- wss://nos.lol/
- ws://skzzn6cimfdv5e2phjc4yr5v7ikbxtn5f7dkwn5c7v47tduzlbosqmqd.onion/
================================================
FILE: package.json
================================================
{
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"start": "bun run build && bun --cwd=packages/app run start",
"build": "bun --cwd=packages/shared run build && bun --cwd=packages/system run build && bun --cwd=packages/wallet run build && bun --cwd=packages/worker-relay run build && bun --cwd=packages/app run build",
"push-prod": "git switch snort-prod && git merge --ff-only main && git push && git checkout main",
"docs": "typedoc --entryPointStrategy packages ./packages/* --exclude ./packages/app --exclude ./packages/webrtc-server --name snort.social",
"pre:commit": "bun --cwd=packages/app run intl-extract && bun --cwd=packages/app run intl-compile && bunx --bun biome lint --write"
},
"dependencies": {
"@cloudflare/workers-types": "^4.20251011.0",
"jiti": "^2.6.1",
"typescript": "^5.9.3"
},
"devDependencies": {
"@biomejs/biome": "2.3.9",
"@noble/curves": "^2.0.1",
"@tauri-apps/cli": "^2.8.4",
"bun": "^1.3.1",
"typedoc": "^0.28.14"
}
}
================================================
FILE: packages/app/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
dist/
dev-dist/
.wrangler/
================================================
FILE: packages/app/CHANGELOG.md
================================================
# v0.5.0
`+196,739,-43,235`
## Highlights
- **Security Hardening**: Comprehensive audit fixes including Schnorr signature verification, NIP-46 relay message forgery protection, PIN encryption improvements, and NIP-26 delegation trust removal
- **Performance**: Batched Schnorr verification in WASM, lazy-loaded routes, pre-compiled translations, worker-relay batch optimizations, and eliminated double verification per event
- **Stability**: Fixed multiple React render loops, hook dependency arrays, stale closures, and lifecycle/memory leaks across the app
## Added
- Comprehensive request processing path tests
- Priority profile loader with batch loading, priority tiers, and chunking
- DVM kind:7000 payment-required invoice display (#618)
- Default relays fallback when pool is empty
- Zapstore manifest migration (nap.yaml → zapstore.yaml)
## Changed
- Batch event verification in system-wasm for improved throughput
- Profile loader rewritten with priority tiers, chunking, and O(1) notifications
- NoteCollection clear() emission and ghost-timer fixes
- Trace-timeline terminal state runtime tracking improvements
- Replaced uuid with crypto.randomUUID()
- All useSyncExternalStore calls now include getServerSnapshot for SSR compatibility (#615)
- Switched quote style to double quotes (Biome formatting)
## Fixed
- **Security**: Real Schnorr signature verification in isValid (#607)
- **Security**: PIN encryption security issues (#606)
- **Security**: NIP-46 hardened against forged relay messages and secret leakage (#609)
- **Security**: Removed unverified NIP-26 delegation trust in getRootPubKey (#608)
- **Security**: Wrapped all dangerous JSON.parse calls in try/catch (#610)
- **Security**: Added timeouts to all hanging promises (#611)
- **Security**: Added lifecycle management and fixed timer/memory leaks (#612)
- Render loop in NoteReaction from unstable inline ref callback
- Render loop on profile page pinned notes
- Notifications render loop from unstable login object dependency
- NoteContext render loop from unstable ev/link/translate deps
- React hook dependency arrays and stale closures across components
- NoteStore lazy snapshot and OutboxModel input mutation (#614)
- Queue traces for connecting relays with query system edge-case tests
- Batch verify in WASM with error logging
- Build-tools version updated to 35.0.0
- APK artifact matching patterns
## PRs
* Fix #617: Display DVM kind:7000 payment-required invoices to users (#618)
* Add getServerSnapshot to all useSyncExternalStore calls for SSR compatibility (#615)
* Lazy snapshot in NoteStore, type safety improvements, fix OutboxModel input mutation (#614)
* Replace uuid with crypto.randomUUID(), remove uuid dependency (#613)
* Add lifecycle management and fix timer/memory leaks (#612)
* Add timeouts to all hanging promises (#611)
* Wrap all dangerous JSON.parse calls in try/catch (#610)
* Harden NIP-46 against forged relay messages and secret leakage (#609)
* Remove unverified NIP-26 delegation trust in getRootPubKey (#608)
* Add real Schnorr signature verification to isValid (#607)
* Fix PIN encryption security issues (#606)
**Full Changelog**: https://github.com/v0l/snort/compare/v0.4.0...v0.5.0
---
# v0.4.0
`+21,991,-31,914`
## Highlights
- **Discover Page**: New discover page with DVM-powered trending posts and content suggestions
- **Media Posts**: Support for kind 20, 21, 22 media notes with dedicated media tab
- **Follow Sets**: Browse and filter follow sets from other users
- **Blossom Storage**: Replaced NIP-96 with Blossom
- **Bun Migration**: Moved entire build system from Yarn/Node to Bun
- **Nests Integration**: Audio rooms with chat and speak functionality
## Added
- Discover page with selectable content discovery DVMs
- Media tab on root page for browsing media content
- Support for kind 20, 21, 22 (media notes) and kind 1111 replies
- Follow sets page with filtering options
- Nests audio rooms integration (linked from live streams)
- Blossom blob support with fallback image loading
- Client tags for identifying applications
- NIP-119 compatibility
- Auto-translate enabled by default
- Show NIP-05 handles and USD price in wallet
- VertexLabs suggested follows
- Table rendering in markdown
## Changed
- Replaced NIP-96 uploads with Blossom
- Upgraded to React 19 `use` hook (replacing `useContext`)
- Upgraded to Vite 7 with ESM imports
- Improved messages with WoT filter and read status
- Refactored cache systems, query system, and profile loader
- Moved imgproxy logic to `@snort/shared`
- **Removed**: LNC and Cashu wallet integrations
- **Removed**: NIP-28 public chat support
## Fixed
- Query system race conditions and DVM request handling
- Profile sync and loading issues
- Note creator auto-complete and tagging
- Link previews and quote reposts
- Negentropy sync missing events
- Support for `ws://` relays (#600)
- Search box and various build issues
---
# v0.3.0
`+35,347,-17,720`
## Highlights
- **For You Feed**: Personalized content algorithm based on your social graph
- **Web of Trust**: Spam filtering based on your network of trust
- **NIP-46 OAuth**: New login and sign up flow with remote signers
- **Relay Overhaul**: Detailed relay pages with uptime, stats, and recommendations
- **NIP-17 Migration**: Dropped NIP-4 in favor of NIP-17 encrypted DMs
## Added
- For You feed with personalized content scoring
- Web of Trust (WoT) spam filter (enabled for all users)
- NIP-46 OAuth login and sign up
- NIP-55 Android signer support
- NIP-44 v2 encryption
- NIP-89 application handlers
- NIP-96 server list with custom server selection
- Relay management: info pages, uptime tracking, reliable indicators
- Revamped note creator
- LiveKit integration for audio/video
- `@snort/bot` package for building bots
- `nostr-social-graph` integration
- Modular right sidebar with articles widget
- Profile top zappers tab
- Reply count in feed
- Quote notes load from multiple relays
- Npub/nprofile QR selector
- Embeds from link previews
- Korean translations
## Changed
- **Breaking**: Dropped NIP-4 DMs, now using NIP-17 only
- Integrated `nostr-social-graph` for social features
- Moved zapper to `@snort/wallet` package
- Refactored outbox model into RequestRouter
- Improved thread loading and timeline rendering
- Preload follows list for faster startup
- **Removed**: Tauri desktop build
- **Removed**: Dislike reactions
- **Removed**: Notification summary chart
## Fixed
- NIP-46 now uses NIP-44 encryption
- NIP-6 mnemonic and nprofile login
- Note creator, mute buttons, and muted words
- Connection race conditions and reconnection spam
- Service worker and startup migration issues
- Feed glitches and timeline reloads
- Hashtag parser and topic buttons
---
# v0.2.0
`+16,990,-9,649`
## Added
- Check notification settings page
- New settings page layout - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Community Leaders / Invite system - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Settings->Tools pages (Check follows relay health etc) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- New wallet pages design - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Alby OAuth wallet connection - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Cashu wallet support (WIP) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Followed by friends feed page - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Fuzzysearch profiles everywhere - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Worker Relay package `@snort/worker-relay` - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Replaces all previous caching objects, all caches are handled inside `@snort/system` via worker relay
- "View as user" button - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Play live streams directly in feed with embed iframe - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Negentropy v1 support - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
## Changed
- Hidden note styles & preferences - nostr:npub1cz2ve34nk0ukn0ph4yq2qx3ud8rfy5e0ak4epx42dn8gha0sdgpsgra9kv
- Keybinds for grid modal navigation - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Cache trending sections in browser - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Cache images / nostr.json in service worker - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Add dimensions to `imeta` tag for void.cat uploads - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Check event sigs in `@snort/system-wasm` - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Primary color scheme - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Note creator styles (removed hashtags input) - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Cache link preview results in memory - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Show only 1 task at a time in task list - nostr:
- Render media in reply to note creator - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Show top zappers inline with footer icons on notes - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Add more search relays - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Moved link previews and opengraph tagging to https://nostr.api.v0l.io - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
## Fixed
- Iris account error mesage - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Light theme color fixes - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Notifications page overflow - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
# v0.1.24
`+11,573,-3,010`
## Added
- 3 Column layout - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Fuzzy cache search - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Followed by on profile pages - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Show more on long notes - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Better error message page - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Media grid feed - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Mobile fixed footer - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Follow button on profile search results - nostr:npub17q5n2z8naw0xl6vu9lvt560lg33pdpe29k0k09umlfxm3vc4tqrq466f2y
- Invite codes (WIP Community Program) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- `imeta` tag insertion for images - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Wallet settings page improvements - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Nostr Wallet Connect upgrade (balance + history) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Schnorr sig check in WASM binary - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Autoplay videos in feed (muted) - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Followed by friends feed (a feed of your 2nd degree follows posts) - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- imgproxy image integrity check (sha256 from `imeta` passed to imgproxy) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
## Changed
- Removed Twitter embed - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Removed attachment button on DM's - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Note broadcaster dialog changed to toast notification - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Removed npub link from profile (use QR button) - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Render image size from `imeta` tags - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Style fixes - nostr:npub1cz2ve34nk0ukn0ph4yq2qx3ud8rfy5e0ak4epx42dn8gha0sdgpsgra9kv
- Zap pool slider tweak - nostr:npub1ltx67888tz7lqnxlrg06x234vjnq349tcfyp52r0lstclp548mcqnuz40t
- New Malay translations - nostr:npub1cjtt3nywuflj65ftld4v7zzpg0qh3ergycjcym0956vf9eftv7esekxpmn
- Updated Persian translations - nostr:npub1cpazafytvafazxkjn43zjfwtfzatfz508r54f6z6a3rf2ws8223qc3xxpk
- Updated Finnish translations - nostr:npub1ust7u0v3qffejwhqee45r49zgcyewrcn99vdwkednd356c9resyqtnn3mj
- Updated French translations - nostr:npub1x8dzy9xegwmdk2vy30l8u08caspcqq2yzncxehdsa6kvnte9pr3qnt8pg4 & nostr:npub13w02l37gkjwv90lnklfet5653jj0p5ueu976v3dpda5afvxgw3uslcqdnv
- Updated German translations - nostr:npub19a6x8frkkn2660fw0flz74a7qg8c2jxk5v9p2rsh7tv5e6ftsq3sav63vp
- Updated Hungarian translations - nostr:npub1ww8kjxz2akn82qptdpl7glywnchhkx3x04hez3d3rye397turrhssenvtp
- Updated Swedish translations - nostr:npub19jk45jz45gczwfm22y9z69xhaex3nwg47dz84zw096xl6z62amkqj99rv7
- Updated Japanese translations - nostr:npub1wh69w45awqnlsxw7jt5tkymets87h6t4phplkx6ug2ht2qkssswswntjk0
## Fixed
- Longform note overlfow-x - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Trim zap content - nostr:npub1u8lnhlw5usp3t9vmpz60ejpyt649z33hu82wc2hpv6m5xdqmuxhs46turz
---
# v0.1.23
## Added
- DeepL translate api (Automatic for PRO subscribers)
- Add nostr:nprofile1qqsydl97xpj74udw0qg5vkfyujyjxd3l706jd0t0w0turp93d0vvungfgfewr to contributors
- Proxy LN address type enabled on Nostr Address settings pages
- Infinite scrol on notifications page
- Default 0.5% ZapPool rate for Snort donation address
- Collect relay metrics in `@snort/system` for better relay selection algo in Outbox Model (NIP-65)
- New sign up / login flow!
- Topics / Mute words on sign up for easier onboarding
- Drag & Drop for uploads on note creator - nostr:nprofile1qqs8tchhwf5smv3r2g0vkswz58c837uu456x59m3dh380gtrhqzydeqz4wlka
- Mixin topics (hashtags) into timeline feed
- Language specific trending posts
- Show following info for hashtags
- Sync preferences to network (`NIP-78` support)
- Trending hashtags page
- Note creator hashtag input
- Top trending hashtags on note creator
- Social Graph - nostr:nprofile1qqsy2ga7trfetvd3j65m3jptqw9k39wtq2mg85xz2w542p5dhg06e5qpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uh8am0r
- New users relay list based off "close" relays
- `NIP-96` support for nostr native image/file uploaders
- Write replies/reactions to `p` tagged users read relays (Outbox model)
- Sync joined public chats (`NIP-28`) using `PublicChatList` kind `10_005`
## Changed
- Read/Write relays only on kind `10_002` (NIP-65)
- Removed `nostr.watch` code for adding new users to random relays
- Render kind `10_002` on profile relays tab
- `@snort/system` using eventemitter3 for triggering events
- Use latest `NIP-51` spec (Bookmarks/Interests/`NIP-28` PublicChatList)
- `nreq` support (Demo)
- Write profile/relays to blasters
- `@snort/system` automated outbox model (automatic fetching of relay metadata)
## Fixes
- Upgrade ephermal connection to non-ephemeral
- Remove relay tag from zaps (Some zap services dont support it)
- Fix zap parsing for goals
- Remove extra chars from quoted events to fix loading (`'s` etc)
- CSS Fixes for profile card on light theme
- Zap counting on replacable events
- `NIP-28` chats loading
- Overflowing modal UI
- Live stream widget layout with long titles
- Notifications marker has returned from its long slumber
---
# v0.1.22
## Fixes
- Note creator too wide on mobile
- Sending notes dialog duplicated when replying
---
# v0.1.21
## Added
- Add gradients to iris.to domain
- Render referenced kind-3 (ContactList) as pubkey list
- List feed page renders the posts of a given list `/list-feed/{naddr-of-nip51-list-or-nevent-of-kind3}`
- Respond to AUTH when expected (Requesting DM's/GiftWrap)
- Show reply counts on threads
- Quote Repost
- Signature checks can be enabled in preferences
- NIP-98 auth for void.cat / nostr.build file uploaders
- Add `E` tag for direct replies
- File upload progress bar (void.cat only)
- Long form modal for deck layout (WIP still)
- Video thumbnails using ImgProxy
- Renew subscriptions for X months
- Tailwind CSS migration @mmalmi
- Seasonal features
- Profile cards on hover for mentions
- Dropdown search results on search bar @mmalmi
- Renew subscription task on task list
## Changed
- Disable highligher.js code blocks (for now)
- Removed "Popular Accounts" from new user flow, replaced with "Snort Devs" only
- Moved "Show Preview" on note creator to preview toggle switch
- Premium subscription renamed to PRO
- Limit images in posts to 800px high
- Nostrplebs colors removed
## Fixed
- Use correct hostname when submitting analytics
- Disable WASM when not supported on device
- Typo on "Nostr Address" in account settings
- Hide expired user status on profiles
- Hide muted dm chats
- Hide blocked replies
---
# v0.1.20
## Added
- Highlight text in search results - @fernandoporazzi
- Iris/Snort build configuration - @mmalmi
- Iris free NIP-05 on Profile page - @mmalmi
- Image galleries on posts - @fernandoporazzi
- Close modal with ESC - @mmalmi
- Navigate image spotlight with LR direction keys - @mmalmi
- Spotlight preview profile/banner on click - @mmalmi
- Fetch profiles from HTTP cache (Iris) - @mmalmi
- Animal names for empty profile accounts (Iris) - @mmalmi
- Redirect to NIP-05 short links for iris/snort accounts - @mmalmi
- Code block highlighting - @fernandoporazzi
- Notification summary graph - @Kieran
- Profile hover cards - @Kieran
- Keyboard shortcuts for new post/focus/search - @fernandoporazzi
- Markdown rendering for long form content - @Kieran
- Show relay response when publishing - @Kieran
## Fixed
- Copy buttons on insecure context - @Kieran
---
# v0.1.19
## Added
- Highlight search results
## Fixes
- Copy to clipboard on insecure context (Umbrel)
---
# v0.1.16
## Fixes
- Login bugs
---
# v0.1.15
## Added
- User status on profile pages (Music only [NIP-38])
- Following mark on avatars, if you follow the pubkey you will see a green tick on their avatar
- Pin encryption, encrypted private key storage for nsec login
- Pubkey (readonly) logins hide buttons which cannot be used (reactions, reply, save profiles, dms etc)
- Muted words feature (phase 1)
- NIP-28 public chats
## Changed
- Styles changes for Content warnings
- Live stream embed styles
- Cashu token embed styles
- Snort Deck thread navigation in modal from timeline
- PoW miner moved to WASM module for faster hashing
## Fixed
- Profile link to dms
- Long form content loading and replies
- Search function restored
---
# v0.1.14
## Added
- Timeline cache: faster page loads and much lower data usage
- WASM module: Some code moved to Rust WASM module for faster execution
- Zap Splits: NIP-57.G
- New Languages:
- Finnish
- Dutch
- Portuguese Brazilian
## Changed
- Count polls by pubkey
---
# v0.1.13
# Added
- Snort V2 Design
- NIP-24 Encrypted secret chats (nsec login only)
- NIP-13 Proof of Work (POW)
- NIP-31 Alt tag spec for unknown event kinds
- Render mentioned zap goals (Kind 9041)
- Embed fonts in src (No more google fonts requests)
- Native key storage for Android app (`Nip7os` interface)
- Swahili translations
- Thai translations
# Changed
- PWA pre-cache setup (Faster PWA loading)
- Show note creator button on profile pages
# Fixed
- Umlauts in urls
- Reject events which don't match request filter
---
# v0.1.12
# Added
- nsecBunker support (connection string `bunker://<pubkey>?relay=wss://realy.com[#token]`)
# Changed
- New snort logo by Bitko
- Infinite scroll changed to manual action (temperarily to fix performance issues)
# Fixed
- Note to self containing all DMS
- Media spotlight disabled for poll options containing images
- Badge image sizes oversize when bypassing imgproxy due to loading error
---
# v0.1.11
## Added
- `@snort/system` package
- `@snort/system-react` package
- Live streaming page (NIP-102)
- Chat system refactor (adding new chat systems much easier now, NIP-29 first candidate)
- NIP-29 simple group chat support
## Fixed
- Profile links with incorrect hrp fixed in some places
- `naddr` event loading fixed
- Relay specific requests fixed (Global tab / Search page)
- NWC connection responds to AUTH requests now
https://git.v0l.io/Kieran/snort/compare/v0.1.10...v0.1.11
---
# v0.1.10
## Added
- Gossip model, query follows write relays for events
- @snort/system NPM package containing Snort core nostr code
- NIP-44 Encryption scheme support
- NIP-59 Gift Wrap support
## Fixed
- Unmarked thread events replies out of order
https://git.v0l.io/Kieran/snort/compare/v0.1.9...v0.1.10
---
# v0.1.9
## Added
- Discover tab, shows trending users/posts from nostr.build
- New DM styles
- Mentioned Zapstr tracks are previewed on Snort with player
- Custom emoji rendering in posts (NIP-30)
- Lanaguage selector on new user flow
- ZapPool, support nostr ecosystem by donating a percentage of your zaps
- Alby NWC link added to NWC connect page
- SemisolDev follow recommendations on Discover tab
- Pubkey lists (NIP-51) render inline when mentioned in notes
- Persian language
- OpenGraph Image/Video media rendered inside link preview box
- Option to zap everybody on mentioned pubkey list
- L402 support for inline media (paywall content)
## Changed
- Error page shows actual error message now, also a button to reset app cache
- Massivly improved profile loading
- Improved JS bundle size by ejecting CRA and using dynamic modules
- Switched to `@void-cat/api` package for void.cat uploads
---
# v0.1.8
## Added
- Tamil Language support
- Quoted notes are rendered embedded
- Multi-account support for subscribers
- Zapper key loading processing in background to speed up profile loading
- Export keys page added to settings
- NIP-94 support for rendering quoted file metadata events
- Interactions cache (zaps/likes/reports) for better UX
- Full screen image/video previews in modal
- Re-broadcast own events dialog
- Nostr wallet connect support
- Cashu token parsing preview with redeem link
- Trending notes/people tabs added to search page
## Changed
- Profile page loads only 200 latest notes, improving profile load times for accounts with less activity
- New user flow has been tweaked to be shorter with NIP5 & Twitter import steps removed
## Fixed
- Thread navigation without page reload
- NIP-42 functionality restored
- `a` tagged kind 1 replies render properly under root event
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.7...v0.1.8
---
# v0.1.7
## Added
- Per event zap targets by @v0l
- Content warning (NIP-36) support by @v0l
- Polls (NIP-69) by @v0l
- Snort subscriptions by @v0l
- NIP-94 File header support by @v0l
- Link previews by @ghobs91 & @v0l
- Cmd+Enter to post note by @v0l
- `nostr:` links (NIP-27) by @v0l
- Tending users on Search page by @ghobs91 & @v0l
## Changed
- Paste image upload by @vivganes
- Note creator note preview by @v0l
- Login private key input masking by @vivganes
## Fixed
- Fix note creator closing on thread when new replies load by @SamSamskies
- Follow hashtag tab highlighting by @SamSamskies
- Language dropdown defaults to Arabic by @vivganes
- Bookmarks showing reactions by @vivganes
- Single zapper on note only shows name by @vivganes
- Broken link previews show empty box by @vivganes
- Render jfif images by @v0l
## PR List
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/476
- `nostr` package: implement NIP-05 by @sistemd in https://github.com/v0l/snort/pull/474
- `nostr` package: NIP-09 event deletion by @sistemd in https://github.com/v0l/snort/pull/478
- fix #484 by @vivganes in https://github.com/v0l/snort/pull/486
- fix #485 by @vivganes in https://github.com/v0l/snort/pull/487
- Per event zap targets by @v0l in https://github.com/v0l/snort/pull/466
- feat: nip-36 by @v0l in https://github.com/v0l/snort/pull/497
- fix #496 by @vivganes in https://github.com/v0l/snort/pull/498
- use redux for NoteCreator state management by @SamSamskies in https://github.com/v0l/snort/pull/494
- fix #495 by @vivganes in https://github.com/v0l/snort/pull/499
- Polls (NIP-69) by @v0l in https://github.com/v0l/snort/pull/489
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/483
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/508
- add ability to paste image from clipboard by @vivganes in https://github.com/v0l/snort/pull/510
- Subscriptions by @v0l in https://github.com/v0l/snort/pull/506
- feat: multi-account system by @v0l in https://github.com/v0l/snort/pull/514
- fix followed tag active tab highlighting by @SamSamskies in https://github.com/v0l/snort/pull/516
- NIP-94 file headers by @v0l in https://github.com/v0l/snort/pull/488
- fix #517 by @vivganes in https://github.com/v0l/snort/pull/518
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/511
- `nostr` package: get tests passing in the browser by @sistemd in https://github.com/v0l/snort/pull/490
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/519
- Subscription handle by @v0l in https://github.com/v0l/snort/pull/522
## New Contributors
- @vivganes made their first contribution in https://github.com/v0l/snort/pull/486
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.6...v0.1.7
---
# v0.1.6
## 🏷️ Summary
- Snort NIP5 management page (for transfers to new pubkeys)
- Short links for Snort NIP5 owners (ie. https://snort.social/kieran)
## Other Changes
- Update Wavlake embed to support .com links by @blastshielddown in https://github.com/v0l/snort/pull/469
- Bug fixes for save profile & relay connection on clean browser
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.5...v0.1.6
---
# v0.1.5
## 🏷️ Short Summary
- Completely rebuilt "core" subscription management system
- Option to rewrite Twitter links to Nitter links
- Tarui app setup, Mac/Windows/Linux desktop apps (coming soon)
- OpenGraph tagging for profiles and events (Only for https://snort.social)
- NIP-27 `nostr:` link parsing
- Global tab full relay names
## What's Changed
- `nostr` package: add direct messages by @sistemd in https://github.com/v0l/snort/pull/399
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/445
- Display search property alongside host in relay name by @h3y6e in https://github.com/v0l/snort/pull/452
- Shorten long relay name by @h3y6e in https://github.com/v0l/snort/pull/455
- Nostr links by @v0l in https://github.com/v0l/snort/pull/461
- `nostr` package: vastly simplify the API by @sistemd in https://github.com/v0l/snort/pull/412
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/453
- Fix: invisible <option> text in dark theme by @jiftechnify in https://github.com/v0l/snort/pull/454
- add setting for rewriting twitter links to nitter by @w3irdrobot in https://github.com/v0l/snort/pull/459
- RequestBuilder / Core Refactor by @v0l in https://github.com/v0l/snort/pull/326
- Tauri setup by @v0l in https://github.com/v0l/snort/pull/462
- Prevents adding ws relay when over https by @ivanacostarubio in https://github.com/v0l/snort/pull/463
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/465
- OpenGraph tag injection by @v0l in https://github.com/v0l/snort/pull/470
## New Contributors
- @jiftechnify made their first contribution in https://github.com/v0l/snort/pull/454
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.4...v0.1.5
---
# v0.1.4
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.3...v0.1.4
---
# v0.1.3
## What's Changed
- only replace note ID when note ID starts with `@` character by @SamSamskies in https://github.com/v0l/snort/pull/441
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.2...v0.1.3
---
# v0.1.2
## What's Changed
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/309
- UI bugs by @verbiricha in https://github.com/v0l/snort/pull/301
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/311
- Add build command to readme by @joshr4 in https://github.com/v0l/snort/pull/300
- fix(fotter-actions): add highlighting and min-width by @fernandolguevara in https://github.com/v0l/snort/pull/312
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/313
- `nostr` package part 1 by @fcked in https://github.com/v0l/snort/pull/315
- Reduce space between the texts for selecting relays by @h3y6e in https://github.com/v0l/snort/pull/316
- fix(profile): convert page id to npub bech32 by @fernandolguevara in https://github.com/v0l/snort/pull/322
- Improve overflow menu button by @joshr4 in https://github.com/v0l/snort/pull/304
- German translations for snort by @gandlafbtc in https://github.com/v0l/snort/pull/323
- fix: send all relays when zapping by @verbiricha in https://github.com/v0l/snort/pull/324
- Add default page selector by @jacany in https://github.com/v0l/snort/pull/321
- UI fixes by @verbiricha in https://github.com/v0l/snort/pull/318
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/319
- feat: render kind 1 reposts by @kphrx in https://github.com/v0l/snort/pull/314
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/330
- Use inner note content as comment by @Semisol in https://github.com/v0l/snort/pull/333
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/331
- Fix stale relays by @SamSamskies in https://github.com/v0l/snort/pull/337
- Feed cache rework by @v0l in https://github.com/v0l/snort/pull/339
- fix long zap comment text overflow by @SamSamskies in https://github.com/v0l/snort/pull/344
- fix links in parentheses by @SamSamskies in https://github.com/v0l/snort/pull/347
- Revert "Merge pull request #347 from v0l/fix-links-in-parentheses" by @SamSamskies in https://github.com/v0l/snort/pull/350
- Update thread detection to not include mentions by @w3irdrobot in https://github.com/v0l/snort/pull/351
- Small settings page stuff by @w3irdrobot in https://github.com/v0l/snort/pull/353
- Change message unread color to purple by @w3irdrobot in https://github.com/v0l/snort/pull/354
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/356
- Remove unread message dot when messages all read by @w3irdrobot in https://github.com/v0l/snort/pull/355
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/359
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/362
- `nostr` package part 2 by @fcked in https://github.com/v0l/snort/pull/346
- feat: add search page field autofocus by @lujakob in https://github.com/v0l/snort/pull/363
- fix URL parsing edge cases by @SamSamskies in https://github.com/v0l/snort/pull/360
- Fast Zaps ⚡ by @v0l in https://github.com/v0l/snort/pull/370
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/364
- Feat/add spinner to button by @lujakob in https://github.com/v0l/snort/pull/368
- Update mark all read dm button to be disabled when no unreads by @w3irdrobot in https://github.com/v0l/snort/pull/373
- `nostr` package part 3 by @fcked in https://github.com/v0l/snort/pull/365
- LNDHub/LNC wallet by @v0l in https://github.com/v0l/snort/pull/219
- Proposal: Remove SVGs from JSX by @enjikaka in https://github.com/v0l/snort/pull/382
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/374
- add Nostr Nests embed by @SamSamskies in https://github.com/v0l/snort/pull/377
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/387
- fix icons by @h3y6e in https://github.com/v0l/snort/pull/392
- Fix broken note links by @SamSamskies in https://github.com/v0l/snort/pull/380
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/391
- fix(BackButton): vertical align styles by @lujakob in https://github.com/v0l/snort/pull/397
- feat(note): open note in new tab on cmd press by @lujakob in https://github.com/v0l/snort/pull/395
- fix(skeleton): dark theme styles by @lujakob in https://github.com/v0l/snort/pull/393
- fix HyperText matching by @mattn in https://github.com/v0l/snort/pull/405
- Makes entire note clickable by @d-r-w in https://github.com/v0l/snort/pull/371
- render webm links as inline videos by @SamSamskies in https://github.com/v0l/snort/pull/410
- render embed for youtube live links by @SamSamskies in https://github.com/v0l/snort/pull/407
- do not render reposts of badge award events in timelines by @SamSamskies in https://github.com/v0l/snort/pull/406
- `nostr` package: use `EventEmitter` by @fcked in https://github.com/v0l/snort/pull/384
- `nostr` pacakge: implement basic NIP-20 `OK` functionality by @fcked in https://github.com/v0l/snort/pull/385
- feat: read nip-58 badges by @verbiricha in https://github.com/v0l/snort/pull/394
- Add Wavlake embed by @blastshielddown in https://github.com/v0l/snort/pull/416
- display search results on page load if query in url by @SamSamskies in https://github.com/v0l/snort/pull/415
- Fix event mention bug by @SamSamskies in https://github.com/v0l/snort/pull/421
- fix NaN when parsing empty string by @SamSamskies in https://github.com/v0l/snort/pull/422
- NIP06 support by @w3irdrobot in https://github.com/v0l/snort/pull/425
- Added key attr to TabSelectors to remove React warning by @w3irdrobot in https://github.com/v0l/snort/pull/424
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/426
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/436
- Update Wavlake embed URL, add support for album & artist links by @blastshielddown in https://github.com/v0l/snort/pull/439
- build(deps): bump webpack from 5.75.0 to 5.76.1 by @dependabot in https://github.com/v0l/snort/pull/442
## New Contributors
- @joshr4 made their first contribution in https://github.com/v0l/snort/pull/300
- @gandlafbtc made their first contribution in https://github.com/v0l/snort/pull/323
- @jacany made their first contribution in https://github.com/v0l/snort/pull/321
- @kphrx made their first contribution in https://github.com/v0l/snort/pull/314
- @lujakob made their first contribution in https://github.com/v0l/snort/pull/363
- @mattn made their first contribution in https://github.com/v0l/snort/pull/405
- @d-r-w made their first contribution in https://github.com/v0l/snort/pull/371
- @blastshielddown made their first contribution in https://github.com/v0l/snort/pull/416
- @dependabot made their first contribution in https://github.com/v0l/snort/pull/442
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.1...v0.1.2
---
# v0.1.1
## What's Changed
- React Map Optimization, [missing map keys ] by @ahmedrowaihi in https://github.com/v0l/snort/pull/283
- Translate '/src/lang.json' in 'ar' by @transifex-integration in https://github.com/v0l/snort/pull/287
- HTML auto direction for specific textual content by @verbiricha in https://github.com/v0l/snort/pull/286
- Translate '/src/lang.json' in 'hu' by @transifex-integration in https://github.com/v0l/snort/pull/288
- feat: twitch embed by @v0l in https://github.com/v0l/snort/pull/289
- fix: don't show 0 if there is no description by @verbiricha in https://github.com/v0l/snort/pull/290
- feat: pinned notes and bookmarks by @verbiricha in https://github.com/v0l/snort/pull/255
- Translate '/src/lang.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/293
- fix: set auto to whole text content instead of individual paragraphs by @verbiricha in https://github.com/v0l/snort/pull/292
- protocol handler by @v0l in https://github.com/v0l/snort/pull/164
- feat: read global from specific (paid) relays by @v0l in https://github.com/v0l/snort/pull/249
- SUPPORT RTL/LTR ON LOGIN PAGE by @ahmedrowaihi in https://github.com/v0l/snort/pull/291
- Add Apple Music embed by @SamSamskies in https://github.com/v0l/snort/pull/294
- Workspace with decoupled `nostr` package by @ennmichael in https://github.com/v0l/snort/pull/274
- Translate '/src/lang.json' in 'ar' by @transifex-integration in https://github.com/v0l/snort/pull/296
- UI fixes + counts on profile page tabs by @verbiricha in https://github.com/v0l/snort/pull/282
- Fix blackout when selecting global tab by @h3y6e in https://github.com/v0l/snort/pull/297
- Prevent profile text from overflowing flex container when it is too long by @h3y6e in https://github.com/v0l/snort/pull/298
## New Contributors
- @ahmedrowaihi made their first contribution in https://github.com/v0l/snort/pull/283
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.0...v0.1.1
---
# v0.1.0
## What's Changed
- Add global tab to Root by @p2pseed in https://github.com/v0l/snort/pull/3
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/4
- fix: dedupe thread pubkeys by @verbiricha in https://github.com/v0l/snort/pull/7
- Note creator improvement by @verbiricha in https://github.com/v0l/snort/pull/6
- fix: correctly follow user mention links by @verbiricha in https://github.com/v0l/snort/pull/5
- fix: force timeline rerender on tab change by @verbiricha in https://github.com/v0l/snort/pull/8
- feat: add mov to video files by @verbiricha in https://github.com/v0l/snort/pull/9
- feat: copy npub on profile by @verbiricha in https://github.com/v0l/snort/pull/10
- fix: display full lightning address, is trimmed if too long by @verbiricha in https://github.com/v0l/snort/pull/12
- feat: embed youtube videos by @verbiricha in https://github.com/v0l/snort/pull/13
- feat: add support for positive and negative reactions by @verbiricha in https://github.com/v0l/snort/pull/11
- feat: nip05 on profile page by @verbiricha in https://github.com/v0l/snort/pull/21
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/24
- Home tabs by @v0l in https://github.com/v0l/snort/pull/25
- fix: support m.youtube.com subdomain links by @verbiricha in https://github.com/v0l/snort/pull/27
- fix: use all available width for note creator text area by @verbiricha in https://github.com/v0l/snort/pull/28
- highlight hashtags by @verbiricha in https://github.com/v0l/snort/pull/29
- UI tweaks by @verbiricha in https://github.com/v0l/snort/pull/30
- Improve regexes by @verbiricha in https://github.com/v0l/snort/pull/32
- Shows QR code first by @ivanacostarubio in https://github.com/v0l/snort/pull/23
- feat: embed tweets by @verbiricha in https://github.com/v0l/snort/pull/33
- Nip5 shop by @v0l in https://github.com/v0l/snort/pull/50
- Activate snort.social NIP-5 service by @v0l in https://github.com/v0l/snort/pull/51
- feat: add avatar borders with color gradients to partner nip05 providers by @verbiricha in https://github.com/v0l/snort/pull/52
- DM's by @v0l in https://github.com/v0l/snort/pull/54
- feat: display banner in profile by @verbiricha in https://github.com/v0l/snort/pull/53
- add max width to details by @verbiricha in https://github.com/v0l/snort/pull/59
- Markdown by @verbiricha in https://github.com/v0l/snort/pull/55
- feat: mentions by @verbiricha in https://github.com/v0l/snort/pull/56
- Minor UI fixes by @verbiricha in https://github.com/v0l/snort/pull/63
- add user DB and cache nip-05 verifications by @verbiricha in https://github.com/v0l/snort/pull/65
- fix: adjust nip05 size by @verbiricha in https://github.com/v0l/snort/pull/66
- fix: dont display display_name as nip user when username is default by @verbiricha in https://github.com/v0l/snort/pull/67
- fix: don't retry errored verifications by @verbiricha in https://github.com/v0l/snort/pull/71
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/70
- refactor: TS by @v0l in https://github.com/v0l/snort/pull/69
- More TSX by @v0l in https://github.com/v0l/snort/pull/74
- Moar UI fixes by @verbiricha in https://github.com/v0l/snort/pull/73
- fix: autocomplete colors by @verbiricha in https://github.com/v0l/snort/pull/75
- feat: query for autocompletion using local db by @verbiricha in https://github.com/v0l/snort/pull/76
- fix: rerender user timeline on pubkey change by @verbiricha in https://github.com/v0l/snort/pull/77
- feat: follows you on profile page by @ivanacostarubio in https://github.com/v0l/snort/pull/64
- autocomplete improvements by @verbiricha in https://github.com/v0l/snort/pull/83
- filter for self dms by @LiranCohen in https://github.com/v0l/snort/pull/86
- Notifications by @v0l in https://github.com/v0l/snort/pull/88
- Theme by @verbiricha in https://github.com/v0l/snort/pull/87
- Modified self-dm to be a "Note to Self" by @LiranCohen in https://github.com/v0l/snort/pull/89
- note footer ordering by @verbiricha in https://github.com/v0l/snort/pull/91
- Hashtags by @v0l in https://github.com/v0l/snort/pull/92
- Make logo cursor a pointer by @w3irdrobot in https://github.com/v0l/snort/pull/99
- fix: active note colors by @verbiricha in https://github.com/v0l/snort/pull/102
- Tidal embeds by @v0l in https://github.com/v0l/snort/pull/95
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/103
- User preferences by @v0l in https://github.com/v0l/snort/pull/104
- Add note context menu by @v0l in https://github.com/v0l/snort/pull/105
- feat: soundcloud embed by @ivanacostarubio in https://github.com/v0l/snort/pull/112
- feat: Show latest by @v0l in https://github.com/v0l/snort/pull/113
- light theme fixes by @verbiricha in https://github.com/v0l/snort/pull/116
- add Karnage to contributors by @verbiricha in https://github.com/v0l/snort/pull/117
- feat: note mentions by @verbiricha in https://github.com/v0l/snort/pull/125
- Preferences & Profile changes by @FlannelDipole in https://github.com/v0l/snort/pull/126
- sort bug in the event that your pubkey is the 2nd item in the list by @LiranCohen in https://github.com/v0l/snort/pull/137
- UI updates by @verbiricha in https://github.com/v0l/snort/pull/135
- fix: hide note creator on send by @verbiricha in https://github.com/v0l/snort/pull/139
- fix: add bottom margin to thread by @verbiricha in https://github.com/v0l/snort/pull/140
- adds mixcloud by @ivanacostarubio in https://github.com/v0l/snort/pull/136
- feat: audio player by @verbiricha in https://github.com/v0l/snort/pull/146
- feat: in-memory fallback for storing user profiles by @verbiricha in https://github.com/v0l/snort/pull/110
- Make Markdown more interoperable by @fiatjaf in https://github.com/v0l/snort/pull/153
- fix: default to in-memory db only on db read fail by @verbiricha in https://github.com/v0l/snort/pull/155
- bug: logout reply by @ivanacostarubio in https://github.com/v0l/snort/pull/154
- Search by @v0l in https://github.com/v0l/snort/pull/143
- Nip42 (AUTH) by @v0l in https://github.com/v0l/snort/pull/144
- Muted list via NIP-51 by @verbiricha in https://github.com/v0l/snort/pull/151
- Add more relays (high performance) by @Semisol in https://github.com/v0l/snort/pull/149
- Show absolute time on hover by @wanacode in https://github.com/v0l/snort/pull/166
- nostr.build file uploads by @v0l in https://github.com/v0l/snort/pull/162
- New UI by @v0l in https://github.com/v0l/snort/pull/161
- fix: send d tags as list by @verbiricha in https://github.com/v0l/snort/pull/169
- Image proxy service by @v0l in https://github.com/v0l/snort/pull/174
- Translate notes by @v0l in https://github.com/v0l/snort/pull/179
- Use standard imgproxy by @v0l in https://github.com/v0l/snort/pull/180
- Fix races where Socket is closed before Websocket is created by @brugeman in https://github.com/v0l/snort/pull/186
- feat: nostrimg.com by @v0l in https://github.com/v0l/snort/pull/181
- Add Spotify embed by @SamSamskies in https://github.com/v0l/snort/pull/188
- Ln invoice styling by @verbiricha in https://github.com/v0l/snort/pull/187
- feed cache by @v0l in https://github.com/v0l/snort/pull/184
- bug: prepends https when missing from website by @ivanacostarubio in https://github.com/v0l/snort/pull/194
- Use the current embed player via TIDALs OEmbed API. by @enjikaka in https://github.com/v0l/snort/pull/191
- feat: zaps by @verbiricha in https://github.com/v0l/snort/pull/78
- display note zaps succintly by @verbiricha in https://github.com/v0l/snort/pull/196
- nostr-pub.semisol.dev is now atlas.nostr.land by @Semisol in https://github.com/v0l/snort/pull/198
- Zaps fixes by @verbiricha in https://github.com/v0l/snort/pull/199
- Note creator improvements by @verbiricha in https://github.com/v0l/snort/pull/193
- Settings page and UI tweaks by @verbiricha in https://github.com/v0l/snort/pull/200
- fix avatars by @verbiricha in https://github.com/v0l/snort/pull/203
- Skeleton component on timeline loading for better user experience by @leotuna in https://github.com/v0l/snort/pull/190
- Threads by @verbiricha in https://github.com/v0l/snort/pull/170
- fix: don't stream global feed in notifications tab by @verbiricha in https://github.com/v0l/snort/pull/207
- Zap modal by @verbiricha in https://github.com/v0l/snort/pull/209
- Add prettier formatting by @ennmichael in https://github.com/v0l/snort/pull/214
- react-intl spike by @SamSamskies in https://github.com/v0l/snort/pull/216
- Add support for zh and ja locales by @SamSamskies in https://github.com/v0l/snort/pull/218
- feat: reactions modal by @verbiricha in https://github.com/v0l/snort/pull/215
- Translate '/src/translations/en.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/224
- Translate '/src/translations/en.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/227
- fix: allow zap comments by @verbiricha in https://github.com/v0l/snort/pull/229
- Eslint by @v0l in https://github.com/v0l/snort/pull/223
- Translate '/src/translations/en.json' in 'fr' by @transifex-integration in https://github.com/v0l/snort/pull/230
- add ability to use babel plugins without ejecting by @SamSamskies in https://github.com/v0l/snort/pull/225
- add prettier pre-commit hook by @SamSamskies in https://github.com/v0l/snort/pull/234
- oversight of intl by @h3y6e in https://github.com/v0l/snort/pull/231
- feat: new login page by @v0l in https://github.com/v0l/snort/pull/235
- feat: onboarding by @verbiricha in https://github.com/v0l/snort/pull/233
- Translate '/src/translations/en.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/243
- Translate '/src/translations/en.json' in 'fr' by @transifex-integration in https://github.com/v0l/snort/pull/242
- Translate '/src/translations/en.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/241
- feat: break lang by @v0l in https://github.com/v0l/snort/pull/244
- fix(missing-event): avoid redirect by @fernandolguevara in https://github.com/v0l/snort/pull/246
- fix(content): render media content for current pubkey by @fernandolguevara in https://github.com/v0l/snort/pull/240
- remove follow button from reactions modal by @SamSamskies in https://github.com/v0l/snort/pull/247
- NIP-65: Relay list metada by @verbiricha in https://github.com/v0l/snort/pull/238
- Fix DM page UI by @SamSamskies in https://github.com/v0l/snort/pull/250
- Translate '/src/lang.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/252
- Translate '/src/lang.json' in 'es' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/258
- Translate '/src/lang.json' in 'fr' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/259
- Translate '/src/lang.json' in 'hu' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/260
- Translate '/src/lang.json' in 'ja' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/261
- Translate '/src/lang.json' in 'zh' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/262
- Translate '/src/lang.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/275
- Translate '/src/lang.json' in 'id' by @transifex-integration in https://github.com/v0l/snort/pull/277
- Translate '/src/lang.json' in 'zh' by @transifex-integration in https://github.com/v0l/snort/pull/278
- Translate '/src/lang.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/279
- Translate '/src/lang.json' in 'hu' by @transifex-integration in https://github.com/v0l/snort/pull/280
- Translate '/src/lang.json' in 'fr' by @transifex-integration in https://github.com/v0l/snort/pull/281
## New Contributors
- @p2pseed made their first contribution in https://github.com/v0l/snort/pull/3
- @v0l made their first contribution in https://github.com/v0l/snort/pull/25
- @ivanacostarubio made their first contribution in https://github.com/v0l/snort/pull/23
- @w3irdrobot made their first contribution in https://github.com/v0l/snort/pull/99
- @FlannelDipole made their first contribution in https://github.com/v0l/snort/pull/126
- @fiatjaf made their first contribution in https://github.com/v0l/snort/pull/153
- @Semisol made their first contribution in https://github.com/v0l/snort/pull/149
- @wanacode made their first contribution in https://github.com/v0l/snort/pull/166
- @SamSamskies made their first contribution in https://github.com/v0l/snort/pull/188
- @enjikaka made their first contribution in https://github.com/v0l/snort/pull/191
- @leotuna made their first contribution in https://github.com/v0l/snort/pull/190
- @transifex-integration made their first contribution in https://github.com/v0l/snort/pull/224
- @h3y6e made their first contribution in https://github.com/v0l/snort/pull/231
- @fernandolguevara made their first contribution in https://github.com/v0l/snort/pull/246
**Full Changelog**: https://github.com/v0l/snort/commits/v0.1.0
================================================
FILE: packages/app/README.md
================================================
# bun-react-template
To install dependencies:
```bash
bun install
```
To start a development server:
```bash
bun dev
```
To run for production:
```bash
bun start
```
This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
================================================
FILE: packages/app/babel.config.json
================================================
{
"plugins": [
[
"formatjs",
{
"idInterpolationPattern": "[sha512:contenthash:base64:6]",
"ast": true
}
]
]
}
================================================
FILE: packages/app/bun-env.d.ts
================================================
// Generated by `bun init`
declare module "*.svg" {
/**
* A path to the SVG file
*/
const path: `${string}.svg`
export = path
}
declare module "*.module.css" {
/**
* A record of class names to their corresponding CSS module classes
*/
const classes: { readonly [key: string]: string }
export = classes
}
================================================
FILE: packages/app/bunfig.toml
================================================
[serve.static]
env = "BUN_PUBLIC_*"
================================================
FILE: packages/app/config/README.md
================================================
Choose config with NODE_CONFIG_ENV: `NODE_CONFIG_ENV=iris bun start`
================================================
FILE: packages/app/config/default.json
================================================
{
"appName": "Snort",
"appNameCapitalized": "Snort",
"appTitle": "Snort - Nostr",
"hostname": "snort.social",
"nip05Domain": "snort.social",
"icon": "/nostrich_512.png",
"navLogo": null,
"publicDir": "public/snort",
"httpCache": "",
"animalNamePlaceholders": false,
"defaultZapPoolFee": 1,
"features": {
"analytics": true,
"subscriptions": false,
"deck": false,
"zapPool": false,
"communityLeaders": false,
"nostrAddress": true,
"pushNotifications": true
},
"signUp": {
"quickStart": false,
"defaultFollows": ["npub1sn0rtcjcf543gj4wsg7fa59s700d5ztys5ctj0g69g2x6802npjqhjjtws"]
},
"defaultPreferences": {
"hideMutedNotes": false,
"defaultRootTab": "following"
},
"media": {
"bypassImgProxyError": false,
"preferLargeMedia": true
},
"communityLeaders": {
"list": "naddr1qq4xc6tnw3ez6vp58y6rywpjxckngdtyxukngwr9vckkze33vcknzcnrxcenje35xqmn2cczyp3lucccm3v9s087z6qslpkap8schltk427zfgqgrn3g2menq5zw6qcyqqq82vqprpmhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv7rajfl"
},
"noteCreatorToast": false,
"hideFromNavbar": [],
"deckSubKind": 1,
"showPowIcon": true,
"showNip05": true,
"eventLinkPrefix": "nevent",
"profileLinkPrefix": "nprofile",
"defaultRelays": {
"wss://relay.snort.social/": {
"read": true,
"write": true
},
"wss://nostr.wine/": {
"read": true,
"write": false
},
"wss://relay.damus.io/": {
"read": true,
"write": true
},
"wss://nos.lol/": {
"read": true,
"write": true
}
},
"alby": {
"clientId": "pohiJjPhQR",
"clientSecret": "GAl1YKLA3FveK1gLBYok"
},
"chatChannels": []
}
================================================
FILE: packages/app/config/iris.json
================================================
{
"appName": "iris",
"appNameCapitalized": "Iris",
"appTitle": "iris",
"hostname": "iris.to",
"nip05Domain": "iris.to",
"icon": "/img/icon128.png",
"navLogo": "/img/icon128.png",
"publicDir": "public/iris",
"httpCache": "",
"animalNamePlaceholders": true,
"defaultZapPoolFee": 0.5,
"features": {
"analytics": true,
"subscriptions": true,
"deck": true,
"zapPool": true,
"communityLeaders": true
},
"defaultPreferences": {
"hideMutedNotes": true,
"defaultRootTab": "for-you"
},
"signUp": {
"quickStart": true,
"defaultFollows": ["npub1wnwwcv0a8wx0m9stck34ajlwhzuua68ts8mw3kjvspn42dcfyjxs4n95l8"]
},
"media": {
"bypassImgProxyError": true,
"preferLargeMedia": true
},
"communityLeaders": {
"list": "naddr1qq4xc6tnw3ez6vp58y6rywpjxckngdtyxukngwr9vckkze33vcknzcnrxcenje35xqmn2cczyp3lucccm3v9s087z6qslpkap8schltk427zfgqgrn3g2menq5zw6qcyqqq82vqprpmhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv7rajfl"
},
"noteCreatorToast": false,
"hideFromNavbar": [],
"eventLinkPrefix": "note",
"profileLinkPrefix": "npub",
"showPowIcon": false,
"defaultRelays": {
"wss://relay.snort.social/": { "read": true, "write": true },
"wss://nostr.wine/": { "read": true, "write": false },
"wss://eden.nostr.land/": { "read": true, "write": false },
"wss://relay.nostr.band/": { "read": true, "write": true },
"wss://relay.damus.io/": { "read": true, "write": true }
},
"chatChannels": [{ "type": "telegram", "value": "https://t.me/irismessenger" }],
"alby": {
"clientId": "5rYcHDrlDb",
"clientSecret": "QAI3QmgiaPH3BfTMzzFd"
}
}
================================================
FILE: packages/app/config/meku.json
================================================
{
"appName": "めく",
"appNameCapitalized": "めく",
"appTitle": "めく",
"hostname": "meku.app",
"nip05Domain": "meku.app",
"icon": "/nostr.jpg",
"navLogo": null,
"publicDir": "public/nostr",
"httpCache": "",
"animalNamePlaceholders": false,
"defaultZapPoolFee": 0,
"features": {
"analytics": true,
"subscriptions": false,
"deck": false,
"zapPool": false,
"communityLeaders": false,
"nostrAddress": false,
"pushNotifications": true
},
"signUp": {
"quickStart": false,
"defaultFollows": []
},
"defaultPreferences": {
"hideMutedNotes": false,
"defaultRootTab": "following",
"language": "ja"
},
"media": {
"bypassImgProxyError": false,
"preferLargeMedia": true
},
"communityLeaders": null,
"noteCreatorToast": false,
"hideFromNavbar": [],
"deckSubKind": 1,
"showPowIcon": true,
"showNip05": true,
"eventLinkPrefix": "nevent",
"profileLinkPrefix": "nprofile",
"defaultRelays": {
"wss://relay.nostr.wirednet.jp/": { "read": true, "write": true },
"wss://yabu.me/": { "read": true, "write": true },
"wss://nos.lol/": { "read": true, "write": true }
},
"alby": null,
"chatChannels": null
}
================================================
FILE: packages/app/config/nostr.json
================================================
{
"appName": "Nostr",
"appNameCapitalized": "Nostr",
"appTitle": "Nostr",
"hostname": "nostr.com",
"nip05Domain": "nostr.com",
"icon": "/nostr.jpg",
"navLogo": null,
"publicDir": "public/nostr",
"httpCache": "",
"animalNamePlaceholders": false,
"defaultZapPoolFee": 0,
"features": {
"analytics": false,
"subscriptions": false,
"deck": false,
"zapPool": false,
"communityLeaders": false,
"nostrAddress": false,
"pushNotifications": false
},
"signUp": {
"quickStart": false,
"defaultFollows": []
},
"defaultPreferences": {
"hideMutedNotes": false,
"defaultRootTab": "following"
},
"media": {
"bypassImgProxyError": false,
"preferLargeMedia": true
},
"communityLeaders": null,
"noteCreatorToast": true,
"hideFromNavbar": [],
"deckSubKind": 1,
"showPowIcon": true,
"showNip05": true,
"eventLinkPrefix": "nevent",
"profileLinkPrefix": "nprofile",
"defaultRelays": {
"wss://relay.snort.social/": { "read": true, "write": true },
"wss://nostr.wine/": { "read": true, "write": false },
"wss://eden.nostr.land/": { "read": true, "write": false },
"wss://nos.lol/": { "read": true, "write": true }
},
"alby": null,
"chatChannels": null
}
================================================
FILE: packages/app/config/phoenix.json
================================================
{
"appName": "Phoenix",
"appNameCapitalized": "Phoenix",
"appTitle": "Phoenix - Nostr",
"hostname": "phoenix.social",
"nip05Domain": "phoenix.social",
"icon": "/logo_256.png",
"navLogo": null,
"publicDir": "public/phoenix",
"httpCache": "",
"animalNamePlaceholders": false,
"defaultZapPoolFee": 1,
"features": {
"analytics": true,
"subscriptions": false,
"deck": false,
"zapPool": false,
"communityLeaders": false,
"nostrAddress": true,
"pushNotifications": true
},
"signUp": {
"quickStart": false,
"defaultFollows": ["npub1sn0rtcjcf543gj4wsg7fa59s700d5ztys5ctj0g69g2x6802npjqhjjtws"]
},
"defaultPreferences": {
"hideMutedNotes": false,
"defaultRootTab": "following"
},
"media": {
"bypassImgProxyError": false,
"preferLargeMedia": true
},
"communityLeaders": {
"list": "naddr1qq4xc6tnw3ez6vp58y6rywpjxckngdtyxukngwr9vckkze33vcknzcnrxcenje35xqmn2cczyp3lucccm3v9s087z6qslpkap8schltk427zfgqgrn3g2menq5zw6qcyqqq82vqprpmhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv7rajfl"
},
"noteCreatorToast": false,
"hideFromNavbar": [],
"deckSubKind": 1,
"showPowIcon": true,
"showNip05": true,
"eventLinkPrefix": "nevent",
"profileLinkPrefix": "nprofile",
"defaultRelays": {
"wss://relay.phoenix.social/": {
"read": true,
"write": true
},
"wss://nostr.wine/": {
"read": true,
"write": false
},
"wss://relay.damus.io/": {
"read": true,
"write": true
},
"wss://nos.lol/": {
"read": true,
"write": true
}
},
"alby": {
"clientId": "pohiJjPhQR",
"clientSecret": "GAl1YKLA3FveK1gLBYok"
},
"chatChannels": []
}
================================================
FILE: packages/app/config/soloco.json
================================================
{
"appName": "Soloco",
"appNameCapitalized": "Soloco",
"appTitle": "Soloco",
"hostname": "soloco.nl",
"nip05Domain": "soloco.nl",
"icon": "/nostrich_512.png",
"favicon": "public/favicon.ico",
"appleTouchIconUrl": "/nostrich_512.png",
"navLogo": null,
"publicDir": "public/snort",
"httpCache": "",
"animalNamePlaceholders": false,
"defaultZapPoolFee": 0,
"features": {
"analytics": false,
"subscriptions": false,
"deck": false,
"zapPool": false,
"communityLeaders": false,
"nostrAddress": false,
"pushNotifications": true
},
"signUp": {
"quickStart": false,
"defaultFollows": []
},
"defaultPreferences": {
"hideMutedNotes": false,
"defaultRootTab": "following",
"language": "nl"
},
"media": {
"bypassImgProxyError": false,
"preferLargeMedia": true
},
"communityLeaders": null,
"noteCreatorToast": true,
"hideFromNavbar": [],
"deckSubKind": 1,
"showPowIcon": true,
"showNip05": true,
"eventLinkPrefix": "nevent",
"profileLinkPrefix": "nprofile",
"defaultRelays": {
"wss://soloco.nl/": { "read": true, "write": false }
},
"alby": null,
"chatChannels": null
}
================================================
FILE: packages/app/custom.d.ts
================================================
/// <reference types="@webbtc/webln-types" />
/// <reference types="vite/client" />
declare module "*.jpg" {
const value: unknown
export default value
}
declare module "*.svg" {
const value: unknown
export default value
}
declare module "*.webp" {
const value: string
export default value
}
declare module "*.png" {
const value: string
export default value
}
declare module "*.css" {
const stylesheet: CSSStyleSheet
export default stylesheet
}
declare module "translations/*.json" {
const value: Record<string, string>
export default value
}
declare module "*.md" {
const value: string
export default value
}
declare module "emojilib" {
const value: Record<string, string>
export default value
}
declare const CONFIG: {
appName: string
appNameCapitalized: string
appTitle: string
hostname: string
nip05Domain: string
icon: string
navLogo: string | null
httpCache: string
animalNamePlaceholders: boolean
defaultZapPoolFee: number
features: {
analytics: boolean
subscriptions: boolean
zapPool: boolean
deck: boolean
communityLeaders: boolean
nostrAddress: boolean
pushNotifications: boolean
}
defaultPreferences: {
hideMutedNotes: boolean
defaultRootTab: "following" | "for-you"
imgProxyConfig: { url: string; key: string; salt: string } | undefined
}
signUp: {
quickStart: boolean
defaultFollows: Array<string>
}
media: {
bypassImgProxyError: boolean
preferLargeMedia: boolean
}
communityLeaders?: {
list: string
}
// Filter urls from nav sidebar
hideFromNavbar: Array<string>
// Limit deck to certain subscriber tier
deckSubKind?: number
showDeck?: boolean
// Create toast notifications when publishing notes
noteCreatorToast: boolean
eventLinkPrefix: NostrPrefix
profileLinkPrefix: NostrPrefix
defaultRelays: Record<string, RelaySettings>
showPowIcon: boolean
// show nip5 beside usernames
showNip05: boolean
// Alby wallet oAuth config
alby?: {
clientId: string
clientSecret: string
}
// public chat channels for site
chatChannels?: Array<{
type: "telegram"
value: string
}>
}
/**
* Build git hash
*/
declare const __SNORT_VERSION__: string
================================================
FILE: packages/app/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes, viewport-fit=cover" />
<meta name="theme-color" content="#000000" />
<!-- Primary Meta Tags -->
<title>{{APP_TITLE}}</title>
<meta name="title" content="{{APP_TITLE}}" />
<meta
name="description"
content="{{APP_NAME}} is a feature-packed, decentralized social media client built on Nostr protocol. Fast, censorship-resistant, and open source. Connect with the decentralized web." />
<meta
name="keywords"
content="nostr, {{APP_NAME}}, fast, decentralized, social media, censorship resistant, open source, web3, decentralized social network, nostr client" />
<meta name="author" content="{{APP_NAME}}" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="{{HOSTNAME}}" />
<meta property="og:title" content="{{APP_TITLE}}" />
<meta
property="og:description"
content="{{APP_NAME}} is a feature-packed, decentralized social media client built on Nostr protocol. Fast, censorship-resistant, and open source." />
<meta property="og:image" content="{{OG_IMAGE}}" />
<meta property="og:site_name" content="{{APP_NAME}}" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="{{HOSTNAME}}" />
<meta name="twitter:title" content="{{APP_TITLE}}" />
<meta
name="twitter:description"
content="{{APP_NAME}} is a feature-packed, decentralized social media client built on Nostr protocol. Fast, censorship-resistant, and open source." />
<meta name="twitter:image" content="{{OG_IMAGE}}" />
<!-- Canonical URL -->
<meta name="robots" content="index, follow" />
<link rel="canonical" href="{{HOSTNAME}}" />
<link rel="apple-touch-icon" href="/img/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/favicon.png" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
================================================
FILE: packages/app/package.json
================================================
{
"name": "@snort/app",
"version": "0.5.2",
"type": "module",
"dependencies": {
"@cashu/cashu-ts": "^2.7.2",
"@livekit/components-react": "^2.9.15",
"@livekit/protocol": "^1.42.2",
"@noble/curves": "^2.0.1",
"@noble/hashes": "^2.0.1",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@scure/base": "^2.0.0",
"@scure/bip32": "^2.0.1",
"@scure/bip39": "^2.0.1",
"@snort/shared": "workspace:*",
"@snort/system": "workspace:*",
"@snort/system-react": "workspace:*",
"@snort/system-wasm": "workspace:*",
"@snort/wallet": "workspace:*",
"@openai/agents": "^0.8.3",
"@snort/worker-relay": "workspace:*",
"openai": "^6.34.0",
"zod": "^4.0.0",
"@uidotdev/usehooks": "^2.4.1",
"@void-cat/api": "^1.0.12",
"classnames": "^2.5.1",
"comlink": "^4.4.2",
"debug": "^4.4.3",
"emojilib": "^4.0.2",
"eventemitter3": "^5.0.1",
"fuse.js": "^7.1.0",
"latlon-geohash": "^2.0.0",
"light-bolt11-decoder": "^3.2.0",
"livekit-client": "^2.15.11",
"lottie-react": "^2.4.1",
"marked": "^16.4.1",
"marked-footnote": "^1.4.0",
"match-sorter": "^8.1.0",
"qr-code-styling": "^1.9.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-force-graph-3d": "^1.29.0",
"react-intersection-observer": "^9.16.0",
"react-intl": "^7.1.14",
"react-router-dom": "^7.9.4",
"react-tag-input-component": "^2.0.2",
"react-textarea-autosize": "^8.5.9",
"recharts": "^3.3.0",
"tslib": "^2.8.1",
"typescript-lru-cache": "^2.0.0",
"use-long-press": "^3.3.0",
"use-sync-external-store": "^1.6.0",
"uuid": "^13.0.0",
"workbox-cacheable-response": "^7.3.0",
"workbox-core": "^7.3.0",
"workbox-expiration": "^7.3.0",
"workbox-precaching": "^7.3.0",
"workbox-routing": "^7.3.0",
"workbox-strategies": "^7.3.0"
},
"scripts": {
"start": "bunx --bun vite",
"build": "bunx vite build",
"serve": "bunx --bun vite preview",
"intl-extract": "bunx --bun formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file src/lang.json --flatten true",
"intl-compile": "bunx --bun formatjs compile src/lang.json --ast --out-file src/translations/en.json",
"eslint": "bunx --bun eslint ."
},
"browserslist": {
"production": ["chrome >= 67", "edge >= 79", "firefox >= 68", "opera >= 54", "safari >= 14"],
"development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
},
"devDependencies": {
"@formatjs/cli": "^6.7.4",
"@types/config": "^3.3.5",
"@types/debug": "^4.1.12",
"@types/latlon-geohash": "^2.0.4",
"@types/node": "^24.8.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@types/three": "^0.180.0",
"@types/use-sync-external-store": "^1.5.0",
"@types/uuid": "^11.0.0",
"@types/webscopeio__react-textarea-autocomplete": "^4.7.5",
"@types/webtorrent": "^0.110.1",
"@vitejs/plugin-basic-ssl": "^2.1.0",
"@vitejs/plugin-react": "^5.0.4",
"@webbtc/webln-types": "^3.0.0",
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
"babel-plugin-formatjs": "^10.5.41",
"@tailwindcss/vite": "^4.1.18",
"config": "^4.1.1",
"prop-types": "^15.8.1",
"rollup-plugin-visualizer": "^6.0.5",
"tailwindcss": "^4.1.14",
"tinybench": "^5.0.1",
"typescript": "^5.9.3",
"vite": "^7.1.10",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-pwa": "^1.1.0",
"vite-plugin-version-mark": "^0.2.2",
"vitest": "^3.2.4"
}
}
================================================
FILE: packages/app/public/iris/.well-known/assetlinks.json
================================================
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "to.iris.twa",
"sha256_cert_fingerprints": [
"63:B5:70:E8:F1:75:7E:D6:EF:81:11:66:F4:9D:47:AB:49:3C:2E:00:B9:67:92:40:89:A5:03:0B:96:B9:40:09"
]
}
}
]
================================================
FILE: packages/app/public/iris/_headers
================================================
/*
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https://youtube.com https://www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com;
/service-worker.js
Cache-Control: max-age=604800, must-revalidate;
================================================
FILE: packages/app/public/iris/manifest.json
================================================
{
"short_name": "Iris",
"name": "Iris",
"description": "Fast nostr web ui",
"id": "/",
"icons": [
{
"src": "/img/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/img/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/img/maskable_icon.png",
"sizes": "640x640",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/img/maskable_icon_x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#000000",
"protocol_handlers": [
{
"protocol": "web+nostr",
"url": "/%s"
}
]
}
================================================
FILE: packages/app/public/iris/robots.txt
================================================
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
================================================
FILE: packages/app/public/nostr/_headers
================================================
/*
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https://youtube.com https://www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com;
================================================
FILE: packages/app/public/phoenix/.well-known/apple-app-site-association
================================================
{
"applinks": {
"details": [
{
"appIDs": [
"snort.social.app"
]
}
]
},
"webcredentials": {
"apps": [
"snort.social.app"
]
}
}
================================================
FILE: packages/app/public/phoenix/.well-known/assetlinks.json
================================================
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "social.snort.app",
"sha256_cert_fingerprints": [
"78:CE:8A:F7:C1:E2:30:12:77:55:BF:0E:86:E4:5C:BA:99:93:A0:D7:D7:42:F8:27:8B:C9:1B:AC:FC:8A:85:05",
"FC:C1:CA:02:C0:81:81:0C:1F:EC:1E:38:CA:38:61:62:6B:6E:90:88:62:DE:4A:66:FC:EC:08:33:B6:94:EE:3C"
]
}
}
]
================================================
FILE: packages/app/public/phoenix/_headers
================================================
/*
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https://youtube.com https://www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com;
/service-worker.js
Cache-Control: max-age=604800, must-revalidate;
================================================
FILE: packages/app/public/phoenix/manifest.json
================================================
{
"short_name": "Phoenix",
"name": "phoenix.social - Nostr interface",
"description": "Fast nostr web ui",
"id": "/",
"icons": [
{
"src": "phoenix_256.png",
"type": "image/png",
"sizes": "256x256"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#000000",
"protocol_handlers": [
{
"protocol": "web+nostr",
"url": "/%s"
}
],
"screenshots": [],
"display_override": ["fullscreen"],
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=social.snort.app",
"id": "social.snort.app"
}
]
}
================================================
FILE: packages/app/public/phoenix/robots.txt
================================================
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
Sitemap: https://api.snort.social/api/v1/sitemap/index.xml
================================================
FILE: packages/app/public/snort/.well-known/apple-app-site-association
================================================
{
"applinks": {
"details": [
{
"appIDs": [
"snort.social.app"
]
}
]
},
"webcredentials": {
"apps": [
"snort.social.app"
]
}
}
================================================
FILE: packages/app/public/snort/.well-known/assetlinks.json
================================================
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "social.snort.app",
"sha256_cert_fingerprints": [
"78:CE:8A:F7:C1:E2:30:12:77:55:BF:0E:86:E4:5C:BA:99:93:A0:D7:D7:42:F8:27:8B:C9:1B:AC:FC:8A:85:05",
"FC:C1:CA:02:C0:81:81:0C:1F:EC:1E:38:CA:38:61:62:6B:6E:90:88:62:DE:4A:66:FC:EC:08:33:B6:94:EE:3C"
]
}
}
]
================================================
FILE: packages/app/public/snort/_headers
================================================
/*
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https://youtube.com https://www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com;
/service-worker.js
Cache-Control: max-age=604800, must-revalidate;
================================================
FILE: packages/app/public/snort/manifest.json
================================================
{
"short_name": "Snort",
"name": "snort.social - Nostr interface",
"description": "Fast nostr web ui",
"id": "/",
"icons": [
{
"src": "nostrich_256.png",
"type": "image/png",
"sizes": "256x256"
},
{
"src": "nostrich_512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#000000",
"protocol_handlers": [
{
"protocol": "web+nostr",
"url": "/%s"
}
],
"screenshots": [],
"display_override": ["fullscreen"],
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=social.snort.app",
"id": "social.snort.app"
}
]
}
================================================
FILE: packages/app/public/snort/robots.txt
================================================
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
Sitemap: https://api.snort.social/api/v1/sitemap/index.xml
================================================
FILE: packages/app/src/Agent/system-prompt.ts
================================================
import { unixNow } from "@snort/shared"
export const SnortSystemPrompt = `You are an AI assistant integrated with Snort, a decentralized social media client built on the Nostr protocol. You can perform actions on behalf of the user by using the available tools.
## About Snort
Snort is a web-based Nostr client that allows users to:
- Post text notes and media
- Follow other users
- React to and repost content
- Send and receive Lightning Network payments (zaps)
- Manage relays and privacy settings
- Browse decentralized social content
### User Search
Use **snort_search_username** to find users by name or NIP-05 address:
- Returns matching profiles with pubkeys, display names, and NIP-05 addresses
- Use this before following or mentioning users when you only know their name
- Example: snort_search_username({ query: "jack" })
## Querying Nostr Relays with snort_query_nostr
When using snort_query_nostr, you need to construct Nostr REQ filter objects. The filters are passed as an array of filter objects.
### Filter Structure
Each filter object can contain these keys:
- **authors**: Array of 64-character hex pubkeys (NOT npub format). Example: ["8e9f..."]
- **kinds**: Array of event kind numbers. Common kinds:
- 1: Text notes
- 2: Recommended relays
- 3: Contact list (follows)
- 4: Direct messages
- 6: Reposts
- 7: Reactions
- 40-41: Channel events
- 9734-9735: Zap requests
- **ids**: Array of event IDs (64-char hex)
- **#e**: Array of event IDs referenced (for replies/mentions)
- **#p**: Array of pubkey hex that were mentioned
- **#t**: Array of hashtags (lowercase, without #)
- **#a**: Array of replace event tag references ({kind}:{author-pubkey-hex}:{d-tag})
- **search**: Free-text search string
- **since**: Unix timestamp (seconds) - only events after this time
- **until**: Unix timestamp (seconds) - only events before this time
- **limit**: Maximum number of results (default 50)
### Common Query Patterns
**Get recent posts from specific users:**
\`\`\`
[{ "authors": ["hex_pubkey_1", "hex_pubkey_2"], "kinds": [1], "limit": 50 }]
\`\`\`
**Search for hashtag:**
\`\`\`
[{ "kinds": [1], "#t": ["bitcoin"], "limit": 20 }]
\`\`\`
**Find replies to an event:**
\`\`\`
[{ "kinds": [1], "#e": ["event_id"], "limit": 50 }]
\`\`\`
**Find posts mentioning a user:**
\`\`\`
[{ "kinds": [1], "#p": ["hex_pubkey"], "limit": 20 }]
\`\`\`
**Text search:**
\`\`\`
[{ "kinds": [1], "search": "bitcoin price", "limit": 20 }]
\`\`\`
**Get user's contact list (follows):**
\`\`\`
[{ "authors": ["hex_pubkey"], "kinds": [3], "limit": 1 }]
\`\`\`
## Guidelines
- Always verify user is logged in before posting or making changes
- Use proper Nostr identifiers (hex for filters, npub for displaying to users)
- Respect privacy - don't expose private keys or sensitive info
- Be explicit when performing irreversible actions (posting, following, paying)
- Confirm content with user before publishing posts
- Handle errors gracefully
- ALWAYS prefix npub/nprofile/naddr/nevent strings with nostr: for improved rendering
- When mentioning other users or events in the content of notes, ALWAYS use nostr:npub NIP-21 entities, or use @npub for npub mentions
The current unix timestamp is ${unixNow()} or ${new Date()}
`
================================================
FILE: packages/app/src/Cache/CommunityLeadersStore.tsx
================================================
import { ExternalStore } from "@snort/shared"
class CommunityLeadersStore extends ExternalStore<Array<string>> {
#leaders: Array<string> = []
setLeaders(arr: Array<string>) {
this.#leaders = arr
this.notifyChange()
}
takeSnapshot(): string[] {
return [...this.#leaders]
}
}
export const LeadersStore = new CommunityLeadersStore()
================================================
FILE: packages/app/src/Cache/GiftWrapCache.ts
================================================
import { EventKind, type EventPublisher, type NostrEvent, type TaggedNostrEvent } from "@snort/system"
import type { CacheRelay } from "@snort/system"
import { findTag, unwrap } from "@/Utils"
import { RefreshFeedCache, type TWithCreated } from "./RefreshFeedCache"
export interface UnwrappedGift {
id: string
to: string
created_at: number
inner: NostrEvent
tags?: Array<Array<string>>
}
const DecryptedContentCache = new Map<string, string>()
export function getCachedDecryptedContent(id: string): string | undefined {
return DecryptedContentCache.get(id)
}
export function setCachedDecryptedContent(id: string, content: string) {
DecryptedContentCache.set(id, content)
}
const NIP44_MIN_PAYLOAD_LEN = 132
function isValidNip44Content(content: string): boolean {
return content.length >= NIP44_MIN_PAYLOAD_LEN
}
export class GiftWrapCache extends RefreshFeedCache<UnwrappedGift> {
#relay: CacheRelay | undefined
#persistedIds: Set<string> = new Set()
constructor() {
super("GiftWrapCache")
}
setRelay(relay: CacheRelay) {
this.#relay = relay
}
key(of: UnwrappedGift): string {
return of.id
}
buildSub(): void {}
takeSnapshot(): Array<UnwrappedGift> {
return [...this.cache.values()]
}
override async preload(): Promise<void> {
await super.preload()
}
override async onEvent(evs: Readonly<Array<TaggedNostrEvent>>, _: string, pub?: EventPublisher) {
if (!pub) return
const fresh = evs.filter(v => !this.#persistedIds.has(v.id) && !this.cache.has(v.id))
if (fresh.length === 0) return
const valid = fresh.filter(v => isValidNip44Content(v.content))
const unwrapped = (
await Promise.all(
valid.map(async v => {
try {
return {
id: v.id,
to: findTag(v, "p"),
created_at: v.created_at,
inner: await pub.unwrapGift(v),
raw: v,
} as UnwrappedGift & { raw: TaggedNostrEvent }
} catch (e) {
console.debug(e, v)
}
}),
)
)
.filter(a => a !== undefined)
.map(unwrap)
const failed = new Set<number>()
for (let i = 0; i < unwrapped.length; i++) {
const u = unwrapped[i]
if (u.inner.kind === EventKind.SealedRumor) {
try {
if (!isValidNip44Content(u.inner.content)) {
failed.add(i)
continue
}
const unsealed = await pub.unsealRumor(u.inner)
u.tags = unsealed.tags
} catch (e) {
console.debug("Failed to unseal rumor", u.id, e)
failed.add(i)
}
}
}
const good = unwrapped.filter((_, i) => !failed.has(i))
if (this.#relay) {
const toPersist = good.filter(u => !this.#persistedIds.has(u.id))
if (toPersist.length > 0) {
try {
await Promise.all(toPersist.map(u => this.#relay?.event(u.raw)))
for (const u of toPersist) {
this.#persistedIds.add(u.id)
}
} catch (e) {
console.warn("GiftWrapCache: failed to persist to worker relay", e)
}
}
}
const cleaned = good.map(({ raw, ...rest }) => rest)
await this.bulkSet(cleaned)
}
async loadPersistedAndDecrypt(pub: EventPublisher): Promise<void> {
if (!this.#relay) return
try {
const existing = await this.#relay.query(["REQ", "giftwrap-load", { kinds: [EventKind.GiftWrap] }])
for (const ev of existing) {
this.#persistedIds.add(ev.id)
}
const newEvs = existing.filter(a => !this.cache.has(a.id))
if (newEvs.length === 0) return
await this.onEvent(newEvs, "", pub)
} catch (e) {
console.warn("GiftWrapCache: failed to load persisted gift wraps", e)
}
}
override async clear(): Promise<void> {
if (this.#relay) {
try {
await this.#relay.delete(["REQ", "giftwrap-clear", { kinds: [EventKind.GiftWrap] }])
} catch (e) {
console.warn("GiftWrapCache: failed to clear worker relay", e)
}
}
this.#persistedIds.clear()
DecryptedContentCache.clear()
await super.clear()
}
search(): Promise<TWithCreated<UnwrappedGift>[]> {
throw new Error("Method not implemented.")
}
}
================================================
FILE: packages/app/src/Cache/ProfileWorkerCache.ts
================================================
import { type CachedMetadata, type CacheRelay, EventKind, mapEventToProfile, type NostrEvent } from "@snort/system"
import { WorkerBaseCache } from "./worker-cached"
export class ProfileCacheRelayWorker extends WorkerBaseCache<CachedMetadata> {
constructor(relay: CacheRelay) {
super(EventKind.SetMetadata, relay)
}
name(): string {
return "Profiles"
}
maxSize(): number {
return 5_000
}
mapper(ev: NostrEvent): CachedMetadata | undefined {
return mapEventToProfile(ev)
}
override async preload(follows?: Array<string>) {
await super.preload()
// load relay lists for follows
if (follows) {
await this.preloadTable(`${this.name()}-preload-follows`, {
kinds: [EventKind.SetMetadata],
authors: follows,
})
}
}
}
================================================
FILE: packages/app/src/Cache/RefreshFeedCache.ts
================================================
import { FeedCache } from "@snort/shared"
import type { EventPublisher, RequestBuilder, TaggedNostrEvent } from "@snort/system"
import type { LoginSession } from "@/Utils/Login"
export type TWithCreated<T> = (T | Readonly<T>) & { created_at: number }
export abstract class RefreshFeedCache<T> extends FeedCache<TWithCreated<T>> {
abstract buildSub(session: LoginSession, rb: RequestBuilder): void
abstract onEvent(evs: Readonly<Array<TaggedNostrEvent>>, pubKey: string, pub?: EventPublisher): void
/**
* Get latest event
*/
protected newest(filter?: (e: TWithCreated<T>) => boolean) {
let ret = 0
this.cache.forEach(v => {
if (!filter || filter(v)) {
ret = v.created_at > ret ? v.created_at : ret
}
})
return ret
}
override async preload(): Promise<void> {
await super.preload()
await this.buffer([...this.onTable])
}
}
================================================
FILE: packages/app/src/Cache/RelaysWorkerCache.ts
================================================
import { unixNowMs } from "@snort/shared"
import { type CacheRelay, EventKind, type NostrEvent, type UsersRelays, parseRelaysFromKind } from "@snort/system"
import { WorkerBaseCache } from "./worker-cached"
export class RelaysWorkerCache extends WorkerBaseCache<UsersRelays> {
constructor(relay: CacheRelay) {
super(EventKind.Relays, relay)
}
name(): string {
return "Relays"
}
maxSize(): number {
return 5_000
}
mapper(ev: NostrEvent): UsersRelays | undefined {
const relays = parseRelaysFromKind(ev)
if (!relays) return
return {
pubkey: ev.pubkey,
loaded: unixNowMs(),
created: ev.created_at,
relays: relays,
}
}
override async preload(follows?: Array<string>) {
await super.preload()
// load relay lists for follows
if (follows) {
await this.preloadTable(`${this.name()}-preload-follows`, {
kinds: [EventKind.Relays],
authors: follows,
})
}
}
}
================================================
FILE: packages/app/src/Cache/UserFollowsWorker.ts
================================================
import { unixNowMs } from "@snort/shared"
import { type CacheRelay, EventKind, type NostrEvent, type UsersFollows } from "@snort/system"
import { WorkerBaseCache } from "./worker-cached"
export class UserFollowsWorker extends WorkerBaseCache<UsersFollows> {
constructor(relay: CacheRelay) {
super(EventKind.ContactList, relay)
}
name(): string {
return "Follows"
}
maxSize(): number {
return 5_000
}
mapper(ev: NostrEvent): UsersFollows | undefined {
if (ev.kind !== EventKind.ContactList) return
return {
pubkey: ev.pubkey,
loaded: unixNowMs(),
created: ev.created_at,
follows: ev.tags,
}
}
override async preload(follows?: Array<string>) {
await super.preload()
// load relay lists for follows
if (follows) {
await this.preloadTable(`${this.name()}-preload-follows`, {
kinds: [EventKind.ContactList],
authors: follows,
})
}
}
}
================================================
FILE: packages/app/src/Cache/index.ts
================================================
import {
type CacheRelay,
Connection,
ConnectionCacheRelay,
UserFollowsCache,
UserProfileCache,
UserRelaysCache,
} from "@snort/system"
import { WorkerRelayInterface } from "@snort/worker-relay"
import WorkerVite from "@snort/worker-relay/src/worker?worker"
import { GiftWrapCache } from "./GiftWrapCache"
import { ProfileCacheRelayWorker } from "./ProfileWorkerCache"
import { UserFollowsWorker } from "./UserFollowsWorker"
import { RelaysWorkerCache } from "./RelaysWorkerCache"
import { hasWasm } from "@/Utils/wasm"
const cacheRelay = localStorage.getItem("cache-relay")
const workerRelay = hasWasm
? new WorkerRelayInterface(
import.meta.env.DEV ? new URL("@snort/worker-relay/dist/esm/worker.mjs", import.meta.url) : new WorkerVite(),
)
: undefined
export const Relay: CacheRelay | undefined = cacheRelay
? new ConnectionCacheRelay(new Connection(cacheRelay, { read: true, write: true }))
: workerRelay
async function tryUseCacheRelay(url: string) {
try {
const conn = new Connection(url, { read: true, write: true })
await conn.connect(true)
localStorage.setItem("cache-relay", url)
return conn
} catch (e) {
console.warn(e)
}
}
export async function tryUseLocalRelay() {
let conn = await tryUseCacheRelay("ws://localhost:4869")
if (!conn) {
conn = await tryUseCacheRelay("ws://umbrel:4848")
}
return conn
}
export async function initRelayWorker() {
try {
if (Relay instanceof ConnectionCacheRelay) {
await Relay.connection.connect(true)
return
}
} catch (e) {
localStorage.removeItem("cache-relay")
console.error(e)
if (cacheRelay) {
window.location.reload()
}
}
try {
if (workerRelay) {
await workerRelay.debug("*")
await workerRelay.init({
databasePath: "relay.db",
insertBatchSize: 100,
})
await workerRelay.configureSearchIndex({
1: [], // add index for kind 1, dont index tags
})
}
} catch (e) {
console.error(e)
}
}
export const UserRelays = Relay ? new RelaysWorkerCache(Relay) : new UserRelaysCache()
export const UserFollows = Relay ? new UserFollowsWorker(Relay) : new UserFollowsCache()
export const ProfilesCache = Relay ? new ProfileCacheRelayWorker(Relay) : new UserProfileCache()
export const GiftsCache = new GiftWrapCache()
if (Relay) {
GiftsCache.setRelay(Relay)
}
export async function preload(follows?: Array<string>) {
const preloads = [
ProfilesCache.preload(follows),
GiftsCache.preload(),
UserRelays.preload(follows),
UserFollows.preload(follows),
]
await Promise.all(preloads)
}
================================================
FILE: packages/app/src/Cache/worker-cached.ts
================================================
import { type CachedTable, type CacheEvents, removeUndefined, unixNowMs } from "@snort/shared"
import type { CachedBase, CacheRelay, NostrEvent, ReqFilter } from "@snort/system"
import debug from "debug"
import { EventEmitter } from "eventemitter3"
import { LRUCache } from "typescript-lru-cache"
/**
* Generic worker relay based cache, key by pubkey
*/
export abstract class WorkerBaseCache<T extends CachedBase>
extends EventEmitter<CacheEvents<T>>
implements CachedTable<T>
{
#relay: CacheRelay
#cache = new LRUCache<string, T>({ maxSize: this.maxSize() })
#keys = new Set<string>()
#log = debug(this.name())
/** Per-key subscribers for O(1) targeted notifications */
#keyListeners = new Map<string, Set<() => void>>()
constructor(
readonly kind: number,
relay: CacheRelay,
) {
super()
this.#relay = relay
}
async clear() {
this.#cache.clear()
this.emit("change", [])
}
key(of: T): string {
return of.pubkey
}
abstract name(): string
abstract maxSize(): number
abstract mapper(ev: NostrEvent): T | undefined
/**
* Preload only the ids from the worker relay
*/
async preload() {
await this.preloadTable(`${this.name()}-preload-ids`, { kinds: [this.kind], ids_only: true })
}
/**
* Reload the table with a request filter
*/
protected async preloadTable(id: string, f: ReqFilter) {
const start = unixNowMs()
const data = await this.#relay.query(["REQ", id, f])
if (f.ids_only === true) {
this.#keys = new Set(data as unknown as Array<string>)
} else {
const mapped = removeUndefined(data.map(a => this.mapper(a)))
for (const o of mapped) {
this.#cache.set(o.pubkey, o)
}
}
this.#log(`Loaded %d/%d in %d ms`, this.#cache.size, this.#keys.size, (unixNowMs() - start).toLocaleString())
}
async search(q: string) {
const results = await this.#relay.query([
"REQ",
`${this.name()}-search`,
{
kinds: [this.kind],
search: q,
},
])
return removeUndefined(results.map(this.mapper))
}
keysOnTable(): string[] {
return [...this.#keys]
}
getFromCache(key?: string | undefined) {
if (key) {
return this.#cache.get(key) || undefined
}
}
discover(ev: NostrEvent) {
this.#keys.add(ev.pubkey)
}
async get(key?: string | undefined): Promise<T | undefined> {
if (key) {
const res = await this.bulkGet([key])
if (res.length > 0) {
return res[0]
}
}
}
async bulkGet(keys: string[]) {
if (keys.length === 0) return []
const results = await this.#relay.query([
"REQ",
`${this.name()}-bulk`,
{
authors: keys,
kinds: [this.kind],
},
])
const mapped = removeUndefined(results.map(this.mapper))
for (const pf of mapped) {
this.#cache.set(pf.pubkey, pf)
}
this.emit(
"change",
mapped.map(a => a.pubkey),
)
return mapped
}
/**
* Because the internal type is different than T we cannot actually persist this value into the worker relay
* meaning that we can only update our internal cache, implementations must ensure that their data is externally
* persisted into the worker relay
*/
private setInternal(obj: T) {
const k = this.key(obj)
const cached = this.#cache.get(k)
if (cached?.loaded && cached?.loaded >= obj.loaded) {
return //skip if newer is in cache
}
this.#keys.add(k)
this.#cache.set(k, obj)
}
async set(obj: T) {
const k = this.key(obj)
this.setInternal(obj)
this.emit("change", [k])
this.#notifyKeyListeners(k)
}
async bulkSet(obj: T[] | readonly T[]) {
obj.map(a => this.setInternal(a))
this.emit("change", obj.map(this.key))
for (const v of obj) {
this.#notifyKeyListeners(this.key(v))
}
}
async update(obj: T): Promise<"new" | "refresh" | "updated" | "no_change"> {
const k = this.key(obj)
const existing = this.getFromCache(k) as (T & { created: number; loaded: number }) | undefined
if (existing) {
const typedObj = obj as T & { created?: number; loaded?: number }
// If we have a newer or same-age entry already cached, skip the overwrite
if (existing.created !== undefined && typedObj.created !== undefined && existing.created > typedObj.created) {
return "no_change"
}
if (existing.loaded !== undefined && typedObj.loaded !== undefined && existing.loaded >= typedObj.loaded) {
return "no_change"
}
}
await this.set(obj)
return existing ? "updated" : "new"
}
/**
* Subscribe to changes for a specific key only.
* O(1) per notification — more efficient than listening to the broad "change" event.
* Returns an unsubscribe function.
*/
subscribe(key: string, cb: () => void): () => void {
let listeners = this.#keyListeners.get(key)
if (!listeners) {
listeners = new Set()
this.#keyListeners.set(key, listeners)
}
listeners.add(cb)
return () => {
const s = this.#keyListeners.get(key)
if (s) {
s.delete(cb)
if (s.size === 0) {
this.#keyListeners.delete(key)
}
}
}
}
#notifyKeyListeners(key: string) {
const listeners = this.#keyListeners.get(key)
if (listeners) {
for (const cb of listeners) {
cb()
}
}
}
async buffer(keys: string[]): Promise<string[]> {
const missing = keys.filter(a => !this.#cache.has(a))
const res = await this.bulkGet(missing)
return missing.filter(a => !res.some(b => this.key(b) === a))
}
snapshot(): T[] {
return [...this.#cache.values()]
}
}
================================================
FILE: packages/app/src/Components/AskSnort/AskSnortInput.tsx
================================================
import { useState } from "react"
import { useNavigate } from "react-router-dom"
import { FormattedMessage, useIntl } from "react-intl"
import { AsyncIcon } from "@/Components/Button/AsyncIcon"
import Textarea from "@/Components/Textarea/Textarea"
import Icon from "@/Components/Icons/Icon"
export function AskSnortInput() {
const navigate = useNavigate()
const [input, setInput] = useState("")
const { formatMessage } = useIntl();
function handleSubmit() {
if (!input.trim()) return
navigate("/agent", { state: { initialMessage: input } })
}
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
e.stopPropagation()
handleSubmit()
}
}
return (
<div className="p-3 bg-layer-1 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<Icon name="lightbulb" className="text-primary flex-shrink-0" size={16} />
<span className="font-semibold text-sm flex-shrink-0">
<FormattedMessage defaultMessage="Ask {appName} AI" values={{ appName: CONFIG.appNameCapitalized }} />
</span>
</div>
<div className="flex items-center gap-2">
<div className="flex-1 min-w-0">
<Textarea
autoFocus={false}
placeholder={formatMessage({ defaultMessage: "Try: Summarize my timeline" })}
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={handleKeyDown}
onFocus={() => { }}
className="!border-0 !resize-none !p-2 !bg-transparent !text-sm min-h-[32px] max-h-[48px] overflow-hidden"
/>
</div>
<AsyncIcon
className="rounded-full flex items-center button shrink-0"
iconName="arrow-right"
onClick={handleSubmit}
disabled={!input.trim()}
/>
</div>
</div>
)
}
================================================
FILE: packages/app/src/Components/Button/AsyncButton.tsx
================================================
import classNames from "classnames"
import React, { type ForwardedRef } from "react"
import Spinner from "@/Components/Icons/Spinner"
import useLoading from "@/Hooks/useLoading"
export interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onClick?: (e: React.MouseEvent) => Promise<void> | void
}
const AsyncButton = React.forwardRef<HTMLButtonElement, AsyncButtonProps>((props, ref) => {
const { handle, loading } = useLoading(props.onClick, props.disabled)
return (
<button
ref={ref as ForwardedRef<HTMLButtonElement>}
type="button"
disabled={loading || props.disabled}
{...props}
className={classNames(
"light:border light:border-border light:text-neutral-400 light:shadow-sm light:hover:shadow-md",
props.className,
)}
onClick={handle}
>
<span
className="flex items-center justify-center gap-2 light:text-black"
style={{ visibility: loading ? "hidden" : "visible" }}
>
{props.children}
</span>
{loading && (
<span className="absolute inset-0">
<div className="w-full h-full flex items-center justify-center">
<Spinner />
</div>
</span>
)}
</button>
)
})
AsyncButton.displayName = "AsyncButton"
export default AsyncButton
================================================
FILE: packages/app/src/Components/Button/AsyncIcon.tsx
================================================
import Icon from "@/Components/Icons/Icon"
import Spinner from "@/Components/Icons/Spinner"
import useLoading from "@/Hooks/useLoading"
export type AsyncIconProps = React.HTMLProps<HTMLDivElement> & {
iconName: string
iconSize?: number
onClick?: (e: React.MouseEvent) => Promise<void> | void
}
export function AsyncIcon(props: AsyncIconProps) {
const { loading, handle } = useLoading(props.onClick, props.disabled)
const mergedProps = { ...props } as Record<string, unknown>
delete mergedProps.iconName
delete mergedProps.iconSize
delete mergedProps.loading
return (
<div {...mergedProps} onClick={handle} className={props.className}>
{loading ? <Spinner /> : <Icon name={props.iconName} size={props.iconSize} />}
{props.children}
</div>
)
}
================================================
FILE: packages/app/src/Components/Button/BackButton.tsx
================================================
import { FormattedMessage } from "react-intl"
import Icon from "@/Components/Icons/Icon"
import type { ReactNode } from "react"
interface BackButtonProps {
text?: ReactNode
onClick?(): void
}
export default function BackButton({ text, onClick }: BackButtonProps) {
return (
<div
className="flex gap-2 items-center cursor-pointer hover:underline"
onClick={() => {
onClick?.()
}}
>
<Icon name="arrowBack" />
<span>{text || <FormattedMessage defaultMessage="Back" />}</span>
</div>
)
}
================================================
FILE: packages/app/src/Components/Button/CloseButton.tsx
================================================
import classNames from "classnames"
import Icon from "@/Components/Icons/Icon"
export default function CloseButton({ onClick, className }: { onClick?: () => void; className?: string }) {
return (
<div
onClick={onClick}
className={classNames(
"self-center rounded-full flex flex-shrink-0 flex-grow-0 items-center justify-center hover:opacity-80 bg-dark p-2 cursor-pointer",
className,
)}
>
<Icon name="close" size={12} />
</div>
)
}
================================================
FILE: packages/app/src/Components/Button/IconButton.tsx
================================================
import classNames from "classnames"
import type { ReactNode } from "react"
import Icon, { type IconProps } from "@/Components/Icons/Icon"
interface IconButtonProps {
onClick?: () => void
icon: IconProps
className?: string
children?: ReactNode
}
const IconButton = ({ onClick, icon, children, className }: IconButtonProps) => {
return (
<button
className={classNames(
"flex items-center justify-center aspect-square w-10 h-10 !p-0 !m-0 bg-neutral-800 text-white",
className,
)}
onClick={onClick}
>
<Icon {...icon} />
{children}
</button>
)
}
export default IconButton
================================================
FILE: packages/app/src/Components/Button/LogoutButton.tsx
================================================
import { FormattedMessage } from "react-intl"
import { useNavigate } from "react-router-dom"
import useLogin from "@/Hooks/useLogin"
import { logout } from "@/Utils/Login"
import messages from "../messages"
export default function LogoutButton() {
const navigate = useNavigate()
const login = useLogin(s => ({ publicKey: s.publicKey, id: s.id }))
if (!login.publicKey) return
return (
<button
className="secondary"
type="button"
onClick={() => {
logout(login.id)
navigate("/")
}}
>
<FormattedMessage {...messages.Logout} />
</button>
)
}
================================================
FILE: packages/app/src/Components/Button/NavLink.tsx
================================================
import { NavLink as RouterNavLink, type NavLinkProps, useLocation } from "react-router-dom"
export default function NavLink(props: NavLinkProps) {
const { to, onClick, ...rest } = props
const location = useLocation()
const isActive = location.pathname === to.toString()
return (
<RouterNavLink
to={to}
onClick={e => {
onClick?.(e)
if (isActive) {
window.scrollTo({ top: 0, behavior: "instant" })
}
}}
{...rest}
/>
)
}
================================================
FILE: packages/app/src/Components/Collapsed.tsx
================================================
import classNames from "classnames"
import { type ReactNode, useState } from "react"
import Icon from "@/Components/Icons/Icon"
interface CollapsedProps {
text?: ReactNode
children: ReactNode
collapsed: boolean
setCollapsed(b: boolean): void
}
const Collapsed = ({ text, children, collapsed, setCollapsed }: CollapsedProps) => {
return collapsed ? (
<div className="text-nostr-purple px-4 pb-3 cursor-pointer hover:underline" onClick={() => setCollapsed(false)}>
{text}
</div>
) : (
<div className="uncollapsed">{children}</div>
)
}
interface CollapsedIconProps {
icon: ReactNode
collapsed: boolean
}
interface CollapsedSectionProps {
title: ReactNode
children: ReactNode
className?: string
startClosed?: boolean
}
export const CollapsedSection = ({ title, children, className, startClosed }: CollapsedSectionProps) => {
const [collapsed, setCollapsed] = useState(startClosed ?? true)
return (
<div>
<div
className={classNames(
"flex gap-4 items-center justify-between cursor-pointer layer-1 select-none",
{ "rounded-b-none": !collapsed },
className,
)}
onClick={() => setCollapsed(!collapsed)}
>
{title}
<Icon name="arrowFront" className={`transition-transform ${collapsed ? "rotate-90" : ""}`} />
</div>
{!collapsed && <div className="layer-2 rounded-t-none">{children}</div>}
</div>
)
}
export default Collapsed
================================================
FILE: packages/app/src/Components/CommunityLeaders/Award.tsx
================================================
export default function AwardIcon({ size }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 62 62" fill="none" className="award">
<defs>
<linearGradient
id="paint0_linear_2660_40043"
x1="31"
y1="3.57143"
x2="31"
y2="58.4286"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#5B2CB3" />
<stop offset="1" stopColor="#811EFF" />
</linearGradient>
<linearGradient
id="paint1_linear_2660_40043"
x1="15.5594"
y1="24.305"
x2="46.433"
y2="24.305"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#AC88FF" />
<stop offset="1" stopColor="#7234FF" />
</linearGradient>
</defs>
<g id="award-02">
<rect x="1.85713" y="1.85714" width="58.2857" height="58.2857" rx="29.1429" fill="#AC88FF" fillOpacity="0.2" />
<rect
x="1.85713"
y="1.85714"
width="58.2857"
height="58.2857"
rx="29.1429"
stroke="url(#paint0_linear_2660_40043)"
strokeWidth="3.42857"
/>
<path
id="Solid"
d="M23.2006 52.4983L22.5639 50.9066L23.2006 52.4983L30.9963 49.38L38.7919 52.4983C39.8813 52.934 41.116 52.801 42.0876 52.1432C43.0592 51.4854 43.6412 50.3885 43.6412 49.2151V38.1015C46.467 35.038 48.1957 30.9408 48.1957 26.4427C48.1957 16.9437 40.4952 9.24329 30.9963 9.24329C21.4973 9.24329 13.7968 16.9437 13.7968 26.4427C13.7968 30.9408 15.5255 35.038 18.3513 38.1015V49.2151C18.3513 50.3885 18.9333 51.4854 19.9049 52.1432C20.8765 52.801 22.1112 52.934 23.2006 52.4983ZM27.2967 43.2429L25.4234 43.9922V42.7187C26.0332 42.9275 26.6584 43.1029 27.2967 43.2429ZM34.6958 43.2429C35.3341 43.1029 35.9593 42.9275 36.5691 42.7187V43.9922L34.6958 43.2429Z"
fill="url(#paint1_linear_2660_40043)"
stroke="#251250"
strokeWidth="3.42857"
strokeLinecap="round"
/>
<path
id="Ellipse 1595"
d="M24.2557 14.6002C17.7766 18.3409 15.5567 26.6257 19.2974 33.1049L42.7604 19.5585C39.0196 13.0794 30.7348 10.8595 24.2557 14.6002Z"
fill="white"
fillOpacity="0.1"
/>
</g>
</svg>
)
}
================================================
FILE: packages/app/src/Components/CommunityLeaders/LeaderBadge.tsx
================================================
import { useState } from "react"
import { FormattedMessage } from "react-intl"
import { Link } from "react-router-dom"
import CloseButton from "../Button/CloseButton"
import Modal from "../Modal/Modal"
import AwardIcon from "./Award"
export function LeaderBadge() {
const [showModal, setShowModal] = useState(false)
return (
<>
<div
className="flex gap-1 p-1 pr-2 items-center border border-[#5B2CB3] rounded-full"
onClick={e => {
e.preventDefault()
e.stopPropagation()
setShowModal(true)
}}
>
<AwardIcon size={16} />
<div className="text-xs font-medium text-[#AC88FF]">
<FormattedMessage defaultMessage="Community Leader" />
</div>
</div>
{showModal && (
<Modal onClose={() => setShowModal(false)} id="leaders">
<div className="flex flex-col gap-4 items-center relative">
<CloseButton className="absolute right-2 top-2" onClick={() => setShowModal(false)} />
<AwardIcon size={80} />
<div className="text-3xl font-semibold">
<FormattedMessage defaultMessage="Community Leader" />
</div>
<p className="text-neutral-400">
<FormattedMessage defaultMessage="Community leaders are individuals who grow the nostr ecosystem by being active in their local communities and helping onboard new users. Anyone can become a community leader, but few hold the current honorary title." />
</p>
<Link to="/settings/invite">
<button className="primary">
<FormattedMessage defaultMessage="Become a leader" />
</button>
</Link>
</div>
</Modal>
)}
</>
)
}
================================================
FILE: packages/app/src/Components/Copy/Copy.tsx
================================================
import classNames from "classnames"
import Icon from "@/Components/Icons/Icon"
import { useCopy } from "@/Hooks/useCopy"
export interface CopyProps {
text: string
maxSize?: number
className?: string
showText?: boolean
mask?: string
}
export default function Copy({ text, maxSize = 32, className, showText, mask }: CopyProps) {
const { copy, copied } = useCopy()
const sliceLength = maxSize / 2
const displayText = mask ? mask.repeat(text.length) : text
const trimmed =
displayText.length > maxSize
? `${displayText.slice(0, sliceLength)}...${displayText.slice(-sliceLength)}`
: displayText
return (
<span
className={classNames("flex cursor-pointer gap-2 items-center", className)}
onClick={e => {
e.stopPropagation()
copy(text)
}}
>
{(showText ?? true) && <span className="text-sm text-font-color">{trimmed}</span>}
<span className="icon" style={{ color: copied ? "var(--success)" : "var(--highlight)" }}>
{copied ? <Icon name="check" size={14} /> : <Icon name="copy-solid" size={14} />}
</span>
</span>
)
}
================================================
FILE: packages/app/src/Components/DvmSelector.tsx
================================================
import { FormattedMessage } from "react-intl"
import type { EventKind } from "@snort/system"
import Modal from "@/Components/Modal/Modal"
import useAppHandler from "@/Hooks/useAppHandler"
import Avatar from "@/Components/User/Avatar"
import DisplayName from "@/Components/User/DisplayName"
import Icon from "@/Components/Icons/Icon"
interface DvmSelectorProps {
kind: number
onClose: () => void
onSelect: (pubkey: string) => void
currentProvider?: string
}
export default function DvmSelector({ kind, onClose, onSelect, currentProvider }: DvmSelectorProps) {
const apps = useAppHandler(kind as EventKind)
const handleSelect = (providerPubkey: string) => {
onSelect(providerPubkey)
onClose()
}
return (
<Modal id="dvm-selector" onClose={onClose}>
<div className="flex flex-col gap-4">
<div className="flex justify-between items-center">
<h2 className="text-xl font-semibold">
<FormattedMessage defaultMessage="Select Provider" />
</h2>
</div>
<div className="text-sm text-neutral-400">
<FormattedMessage defaultMessage="Choose a DVM (Data Vending Machine) to provide content." />
</div>
<div className="flex flex-col gap-2 max-h-96 overflow-y-auto">
{apps.length === 0 ? (
<div className="text-center py-8 text-neutral-400">
<FormattedMessage defaultMessage="No DVM providers found" />
</div>
) : (
apps
.sort((a, _) => (currentProvider === a.event.pubkey ? -1 : 0))
.map(app => {
const isSelected = currentProvider === app.event.pubkey
return (
<div
key={app.event.id}
onClick={() => handleSelect(app.event.pubkey)}
className={`flex items-start gap-3 layer-1-hover mr-1`}
>
<Avatar pubkey={app.event.pubkey} user={app.metadata} size={48} />
<div className="flex flex-col flex-1">
<div className="flex items-center gap-2">
<DisplayName pubkey={app.event.pubkey} user={app.metadata} />
{isSelected && <Icon name="check" size={16} className="text-highlight" />}
</div>
<div className="flex items-center gap-2 text-sm text-neutral-400 mt-1">
{app.reccomendations.length > 0 && (
<>
<Icon name="thumbs-up" size={14} />
{app.reccomendations.length}{" "}
{app.reccomendations.length === 1 ? "recommendation" : "recommendations"}
</>
)}
</div>
{app.metadata?.about && <div className="text-sm text-neutral-400 mt-1">{app.metadata.about}</div>}
</div>
</div>
)
})
)}
</div>
</div>
</Modal>
)
}
================================================
FILE: packages/app/src/Components/Embed/AppleMusicEmbed.tsx
================================================
const AppleMusicEmbed = ({ link }: { link: string }) => {
const convertedUrl = link.replace("music.apple.com", "embed.music.apple.com")
const isSongLink = /\?i=\d+$/.test(convertedUrl)
return (
<iframe
allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write"
frameBorder="0"
height={isSongLink ? 175 : 450}
style={{ width: "100%", maxWidth: 660, overflow: "hidden", background: "transparent" }}
sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation"
src={convertedUrl}
loading="lazy"
/>
)
}
export default AppleMusicEmbed
================================================
FILE: packages/app/src/Components/Embed/BlossomBlob.tsx
================================================
import useBlossomServers from "@/Hooks/useBlossomServers"
import { appendDedupe, dedupe, isHex, NostrPrefix, removeUndefined } from "@snort/shared"
import { type NostrLink, tryParseNostrLink } from "@snort/system"
import { randomSample } from "@/Utils"
import RevealMedia from "../Event/RevealMedia"
import Icon from "../Icons/Icon"
import { useState } from "react"
import Modal from "../Modal/Modal"
import { FormattedMessage } from "react-intl"
import ProfilePreview from "../User/ProfilePreview"
import UrlStatusCheck from "./UrlStatusCheck"
interface BlossomLink {
hash: string
extension: string
authors?: Array<NostrLink>
servers?: Array<string>
size?: number
}
function parseBlossomLink(link: string) {
const url = new URL(link)
const [hash, extension] = url.pathname.split(".")
if (!extension) {
throw new Error("Invalid blossom link, no extension set")
}
if (!isHex(hash) || hash.length !== 64) {
throw new Error("Invalid blossom link, hash is not hex or has the wrong size")
}
const q = new URLSearchParams(url.search)
const authors = removeUndefined(q.getAll("as").map(a => tryParseNostrLink(a, NostrPrefix.PublicKey)))
const servers = q.getAll("xs").map(a => (a.startsWith("http") ? a : `https://${a}`))
const size = Number(q.get("sz"))
return {
hash,
extension,
authors: authors.length > 0 ? authors : undefined,
servers: servers.length > 0 ? servers : undefined,
size: Number.isNaN(size) ? undefined : size,
} as BlossomLink
}
export default function BlossomBlob({ creator, link }: { creator: string; link: string }) {
const blob = parseBlossomLink(link)
const servers = useBlossomServers(blob.authors)
const [showModal, setShowModal] = useState(false)
// convert into media element
// random sample up to maxServers urls
const maxServers = 10
const authorUrls = dedupe(Object.values(servers).flat())
const explicitUrls = dedupe(blob.servers ?? [])
const mapBlobUrl = (a: string) => `${a}${a.endsWith("/") ? "" : "/"}${blob.hash}.${blob.extension}`
const allUrls = appendDedupe(authorUrls.map(mapBlobUrl), explicitUrls.map(mapBlobUrl))
const urls = randomSample(allUrls, maxServers)
return (
<div className="relative min-h-12 border">
<RevealMedia
creator={creator}
src={urls[0]}
meta={{
fallback: urls.slice(1),
hash: blob.hash,
size: blob.size,
}}
/>
<Icon
name="info-outline"
className="absolute top-4 left-4 cursor-pointer opacity-50 hover:opacity-100"
onClick={() => setShowModal(true)}
/>
{showModal && (
<Modal id={`blossom-modal-${blob.hash}`} onClose={() => setShowModal(false)}>
<div className="flex flex-col gap-4">
<h2 className="text-xl font-bold">
<FormattedMessage defaultMessage="Blossom Blob Debug Info" />
</h2>
<div className="flex flex-col gap-2">
<div>
<strong>
<FormattedMessage defaultMessage="Hash" />:
</strong>
<code className="ml-2 font-mono text-sm break-all">{blob.hash}</code>
</div>
<div>
<strong>
<FormattedMessage defaultMessage="Extension" />:
</strong>
<code className="ml-2 font-mono text-sm">{blob.extension}</code>
</div>
{blob.size && (
<div>
<strong>
<FormattedMessage defaultMessage="Size" />:
</strong>
<span className="ml-2">
<FormattedMessage
defaultMessage="{size} bytes ({kb} KB)"
values={{
size: blob.size.toLocaleString(),
kb: (blob.size / 1024).toFixed(2),
}}
/>
</span>
</div>
)}
</div>
{blob.authors && blob.authors.length > 0 && (
<div>
<strong>
<FormattedMessage defaultMessage="Authors ({count})" values={{ count: blob.authors.length }} />:
</strong>
<div className="flex flex-col gap-2 mt-2">
{blob.authors.map(author => (
<ProfilePreview key={author.id} pubkey={author.id} actions={<></>} className="layer-2" />
))}
</div>
</div>
)}
{explicitUrls.length > 0 && (
<div>
<strong>
<FormattedMessage
defaultMessage="Explicit Servers ({count})"
values={{ count: explicitUrls.length }}
/>
:
</strong>
<ul className="list-disc">
{explicitUrls.map(url => (
<li key={url} className="text-sm break-all">
<a href={url} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">
{url}
</a>
</li>
))}
</ul>
</div>
)}
{authorUrls.length > 0 && (
<div>
<strong>
<FormattedMessage
defaultMessage="Author-Derived Servers ({count})"
values={{ count: authorUrls.length }}
/>
:
</strong>
<ul className="list-disc max-h-48 overflow-y-auto">
{authorUrls.map(url => (
<li key={url} className="text-sm break-all">
<a href={url} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">
{url}
</a>
</li>
))}
</ul>
</div>
)}
<div>
<strong>
<FormattedMessage
defaultMessage="Active URLs ({active}/{max})"
values={{ active: urls.length, max: maxServers }}
/>
:
</strong>
<div>
{urls.map(url => (
<div key={url} className="text-sm break-all flex items-center gap-2">
<UrlStatusCheck url={url} />
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline break-all"
>
{url}
</a>
</div>
))}
</div>
</div>
</div>
</Modal>
)}
</div>
)
}
================================================
FILE: packages/app/src/Components/Embed/CashuNuts.tsx
================================================
import { useUserProfile } from "@snort/system-react"
import { useEffect, useState } from "react"
import { FormattedMessage, FormattedNumber } from "react-intl"
import AsyncButton from "@/Components/Button/AsyncButton"
import ECashIcon from "@/Components/Icons/ECash"
import Icon from "@/Components/Icons/Icon"
import { useCopy } from "@/Hooks/useCopy"
import useLogin from "@/Hooks/useLogin"
import { WarningNotice } from "../WarningNotice/WarningNotice"
import { getDecodedToken, type Token } from "@cashu/cashu-ts"
export default function CashuNuts({ token }: { token: string }) {
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }))
const profile = useUserProfile(publicKey)
const { copy } = useCopy()
async function redeemToken(token: string) {
const lnurl = profile?.lud16 ?? ""
const url = `https://redeem.cashu.me?token=${encodeURIComponent(token)}&lightning=${encodeURIComponent(
lnurl,
)}&autopay=yes`
window.open(url, "_blank")
}
const [cashu, setCashu] = useState<Token>()
useEffect(() => {
try {
if (!token.startsWith("cashuA") || token.length < 10) {
return
}
const tkn = getDecodedToken(token)
setCashu(tkn)
} catch (e) {
// ignored
console.warn(e)
}
}, [token])
if (!cashu)
return (
<WarningNotice>
<FormattedMessage defaultMessage="Invalid cashu token" />
</WarningNotice>
)
const amount = cashu.proofs.reduce((acc, v) => acc + v.amount, 0)
return (
<div
className="flex justify-between p-6 rounded-lg items-center"
style={{
backgroundImage: "linear-gradient(90deg, #40b039, #adff2a)",
}}
>
<div className="flex flex-col gap-2 min-w-0 truncate overflow-hidden text-ellipsis">
<div className="flex items-center gap-4">
<ECashIcon width={30} />
<FormattedMessage
defaultMessage="{n} eSats"
id="yAztTU"
values={{
n: (
<span className="text-3xl">
<FormattedNumber value={amount} />
</span>
),
}}
/>
</div>
</div>
<div className="flex gap-2 items-center">
<AsyncButton onClick={() => copy(token)}>
<Icon name="copy" />
</AsyncButton>
<AsyncButton onClick={() => redeemToken(token)}>
<FormattedMessage defaultMessage="Redeem" />
</AsyncButton>
</div>
</div>
)
}
================================================
FILE: packages/app/src/Components/Embed/GenericPlayer.tsx
================================================
import { useState } from "react"
import Icon from "../Icons/Icon"
import { ProxyImg } from "../ProxyImg"
export default function GenericPlayer({ url, poster }: { url: string; poster: string }) {
const [play, setPlay] = useState(false)
if (!play) {
return (
<div
className="relative aspect-video"
onClick={e => {
e.preventDefault()
e.stopPropagation()
setPlay(true)
}}
>
<ProxyImg className="absolute" src={poster} />
<div className="absolute w-full h-full opacity-0 hover:opacity-100 hover:bg-black/30 flex items-center justify-center transition">
<Icon name="play-square-outline" size={50} />
</div>
</div>
)
}
return (
<iframe
className="aspect-video w-full"
src={url}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen={true}
/>
)
}
================================================
FILE: packages/app/src/Components/Embed/Hashtag.tsx
================================================
import { Link } from "react-router-dom"
const Hashtag = ({ tag }: { tag: string }) => {
return (
<span className="text-highlight">
<Link to={`/t/${tag}`} onClick={e => e.stopPropagation()} className="hover:underline">
#{tag}
</Link>
</span>
)
}
export default Hashtag
================================================
FILE: packages/app/src/Components/Embed/HyperText.tsx
================================================
import type { ReactNode } from "react"
import AppleMusicEmbed from "@/Components/Embed/AppleMusicEmbed"
import LinkPreview from "@/Components/Embed/LinkPreview"
import MixCloudEmbed from "@/Components/Embed/MixCloudEmbed"
import SoundCloudEmbed from "@/Components/Embed/SoundCloudEmded"
import SpotifyEmbed from "@/Components/Embed/SpotifyEmbed"
import TidalEmbed from "@/Components/Embed/TidalEmbed"
import TwitchEmbed from "@/Components/Embed/TwitchEmbed"
import WavlakeEmbed from "@/Components/Embed/WavlakeEmbed"
import YoutubeEmbed from "@/Components/Embed/YoutubeEmbed"
import {
AppleMusicRegex,
MixCloudRegex,
SoundCloudRegex,
SpotifyRegex,
TidalRegex,
TwitchRegex,
WavlakeRegex,
YoutubeUrlRegex,
} from "@/Utils/Const"
interface HypeTextProps {
link: string
children?: ReactNode | Array<ReactNode> | null
showLinkPreview?: boolean
}
export default function HyperText({ link, showLinkPreview, children }: HypeTextProps) {
if (link.match(YoutubeUrlRegex)) {
return <YoutubeEmbed link={link} />
} else if (link.match(TidalRegex)) {
return <TidalEmbed link={link} />
} else if (link.match(SoundCloudRegex)) {
return <SoundCloudEmbed link={link} />
} else if (link.match(MixCloudRegex)) {
return <MixCloudEmbed link={link} />
} else if (link.match(SpotifyRegex)) {
return <SpotifyEmbed link={link} />
} else if (link.match(TwitchRegex)) {
return <TwitchEmbed link={link} />
} else if (link.match(AppleMusicRegex)) {
return <AppleMusicEmbed link={link} />
} else if (link.match(WavlakeRegex)) {
return <WavlakeEmbed link={link} />
} else if (showLinkPreview ?? true) {
return <LinkPreview url={link} />
}
return (
<a
href={link}
onClick={e => e.stopPropagation()}
target="_blank"
rel="noreferrer"
className="text-highlight no-underline hover:underline"
>
{children ?? link}
</a>
)
}
================================================
FILE: packages/app/src/Components/Embed/Invoice.tsx
================================================
import { decodeInvoice } from "@snort/shared"
import { useState } from "react"
import { useMemo } from "react"
import { FormattedMessage, useIntl } from "react-intl"
import Icon from "@/Components/Icons/Icon"
import ZapModal from "@/Components/ZapModal/ZapModal"
import { useWallet } from "@/Wallet"
import messages from "../messages"
export interface InvoiceProps {
invoice: string
}
export default function Invoice(props: InvoiceProps) {
const invoice = props.invoice
const { formatMessage } = useIntl()
const [showInvoice, setShowInvoice] = useState(false)
const walletState = useWallet()
const wallet = walletState.wallet
const info = useMemo(() => decodeInvoice(invoice), [invoice])
const [isPaid, setIsPaid] = useState(false)
const isExpired = info?.expired
const amount = info?.amount ?? 0
const description = info?.description
function header() {
return (
<>
<h4 className="m-0 p-0 font-normal text-base leading-[19px] mb-2.5">
<FormattedMessage {...messages.Invoice} />
</h4>
<Icon name="zapCircle" className="absolute top-[26px] right-5 text-font-color" />
<ZapModal
title={formatMessage(messages.PayInvoice)}
invoice={invoice}
show={showInvoice}
onClose={() => setShowInvoice(false)}
/>
</>
)
}
async function payInvoice(e: React.MouseEvent<HTMLButtonElement>) {
e.stopPropagation()
if (wallet?.isReady) {
try {
await wallet.payInvoice(invoice)
setIsPaid(true)
} catch (_error) {
setShowInvoice(true)
}
} else {
setShowInvoice(true)
}
}
return (
<div className="border rounded-lg p-6 flex-col items-start relative bg-[image:var(--invoice-gradient)]">
<div>{header()}</div>
<p className="font-normal text-[37px] leading-[45px] mb-4">
{amount > 0 && (
<>
{(amount / 1_000).toLocaleString()}{" "}
<span className="text-font-secondary-color uppercase text-[21px]">sat{amount === 1_000 ? "" : "s"}</span>
</>
)}
</p>
<div className="text-font-secondary-color w-full text-base leading-[19px]">
{description && <p className="mb-4">{description}</p>}
{isPaid ? (
<div className="w-full h-11 font-semibold text-[19px] leading-[23px] flex items-center justify-center bg-success text-white rounded-lg">
<FormattedMessage defaultMessage="Paid" />
</div>
) : (
<button
disabled={isExpired}
type="button"
onClick={payInvoice}
className="w-full h-11 font-semibold text-[19px] leading-[23px]"
>
{isExpired ? <FormattedMessage {...messages.Expired} /> : <FormattedMessage {...messages.Pay} />}
</button>
)}
</div>
</div>
)
}
================================================
FILE: packages/app/src/Components/Embed/LinkPreview.tsx
================================================
import { useEffect, useState } from "react"
import { LRUCache } from "typescript-lru-cache"
import { MediaElement } from "@/Components/Embed/MediaElement"
import Spinner from "@/Components/Icons/Spinner"
import { type LinkPreviewData, NostrServices } from "@/External"
import { ProxyImg } from "../ProxyImg"
import GenericPlayer from "./GenericPlayer"
async function fetchUrlPreviewInfo(url: string) {
const api = new NostrServices()
try {
return await api.linkPreview(url.endsWith(")") ? url.slice(0, -1) : url)
} catch (_e) {
console.warn(`Failed to load link preview`, url)
}
}
const cache = new LRUCache<string, LinkPreviewData>({
maxSize: 1000,
})
const LinkPreview = ({ url }: { url: string }) => {
const uu = new URL(url)
const [preview, setPreview] = useState<LinkPreviewData | null>(cache.get(url))
useEffect(() => {
if (cache.get(url)) return
let cancelled = false
;(async () => {
const data = await fetchUrlPreviewInfo(url)
if (cancelled) return
if (data) {
const type = data.og_tags?.find(a => a[0].toLowerCase() === "og:type")
const canPreviewType = type?.[1].startsWith("image") || type?.[1].startsWith("video") || false
if (canPreviewType || data.image) {
setPreview(data)
cache.set(url, data)
return
}
}
setPreview(null)
})()
return () => {
cancelled = true
}
}, [url])
if (preview === null)
return (
<a href={url} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="text-highlight">
{url}
</a>
)
function previewElement() {
const type = preview?.og_tags?.find(a => a[0].toLowerCase() === "og:type")?.[1]
if (type?.startsWith("video")) {
const urlTags = ["og:video:secure_url", "og:video:url", "og:video"]
const link = preview?.og_tags?.find(a => urlTags.includes(a[0].toLowerCase()))?.[1]
const videoType = preview?.og_tags?.find(a => a[0].toLowerCase() === "og:video:type")?.[1] ?? "video/mp4"
if (link && videoType.startsWith("video/")) {
return <MediaElement src={link} mime={videoType} />
}
if (link && videoType.startsWith("text/html") && preview?.image) {
return <GenericPlayer url={link} poster={preview?.image} />
}
}
if (type?.startsWith("image")) {
const urlTags = ["og:image:secure_url", "og:image:url", "og:image"]
const link = preview?.og_tags?.find(a => urlTags.includes(a[0].toLowerCase()))?.[1]
const videoType = preview?.og_tags?.find(a => a[0].toLowerCase() === "og:image:type")?.[1] ?? "image/png"
if (link) {
return <MediaElement src={link} mime={videoType} />
}
}
if (preview?.image) {
let src = preview?.image
if (!preview.image.startsWith("http")) {
src = `${uu.protocol}//${uu.hostname}/${preview.image}`
}
return <ProxyImg src={src} className="w-full object-cover aspect-video" />
}
return null
}
return (
<div className="rounded-lg bg-layer-1 overflow-hidden hover:cursor-pointer light:border light:hover:shadow-md">
{preview && (
<a href={url} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="!no-underline">
<div className="lg:min-h-[342px]">{previewElement()}</div>
<div className="px-3 pb-2 leading-[21px]">
<div className="font-bold leading-normal my-2">{preview?.title}</div>
{preview?.description && (
<small className="text-neutral-800 text-sm">{preview.description.slice(0, 160)}</small>
)}
<small className="text-xs">{uu.host}</small>
</div>
</a>
)}
{!preview && <Spinner className="items-center" />}
</div>
)
}
export default LinkPreview
================================================
FILE: packages/app/src/Components/Embed/MagnetLink.tsx
================================================
import { FormattedMessage } from "react-intl"
import type { Magnet } from "@/Utils"
import Icon from "../Icons/Icon"
interface MagnetLinkProps {
magnet: Magnet
}
const MagnetLink = ({ magnet }: MagnetLinkProps) => {
return (
<div className="border rounded-lg p-6 flex-col items-start relative bg-[image:var(--invoice-gradient)]">
<h4>
<FormattedMessage defaultMessage="Magnet Link" />
</h4>
<a href={magnet.raw} rel="noreferrer" className="flex gap-2 items-center">
<Icon name="link" size={16} />
{magnet.dn ?? magnet.infoHash}
</a>
</div>
)
}
export default MagnetLink
================================================
FILE: packages/app/src/Components/Embed/MediaElement.tsx
================================================
import type { Nip94Tags } from "@snort/system"
import classNames from "classnames"
import type React from "react"
import { type CSSProperties, useEffect, useRef, useState } from "react"
import { useInView } from "react-intersection-observer"
import { ProxyImg, type ProxyImgProps } from "@/Components/ProxyImg"
import useImgProxy from "@/Hooks/useImgProxy"
import { randomSample } from "@/Utils"
export interface MediaElementProps {
mime: string
src: string
meta?: Nip94Tags
onMediaClick?: (e: React.MouseEvent<HTMLImageElement>) => void
onFallback?: (url: string) => void
size?: number
style?: CSSProperties
}
interface AudioElementProps {
src: string
}
interface VideoElementProps {
src: string
meta?: Nip94Tags
}
export type ImageElementProps = ProxyImgProps & {
meta?: Nip94Tags
onMediaClick?: (e: React.MouseEvent<HTMLImageElement>) => void
onFallback?: (url: string) => void
}
const AudioElement = ({ src }: AudioElementProps) => {
return <audio key={src} src={src} controls />
}
const ImageElement = ({ src, meta, onMediaClick, size, onFallback, ...props }: ImageElementProps) => {
const imageRef = useRef<HTMLImageElement | null>(null)
const urlsRef = useRef<Array<string>>()
if (!urlsRef.current) {
urlsRef.current = randomSample([src, ...(meta?.fallback ?? [])], 10)
}
const [alternatives, setAlternatives] = useState<Array<string>>(urlsRef.current.slice(1))
const [currentUrl, setCurrentUrl] = useState(urlsRef.current[0])
if ("creator" in props) {
delete props.creator
}
if ("mime" in props) {
delete props.mime
}
return (
<ProxyImg
{...props}
key={currentUrl}
src={currentUrl}
sha256={meta?.hash}
onClick={onMediaClick}
className={classNames("relative max-h-[80vh] w-full h-full object-contain object-center", {
"md:max-h-[510px]": !meta && !CONFIG.media.preferLargeMedia,
})}
ref={imageRef}
onError={() => {
const next = alternatives.at(0)
if (next) {
console.warn("IMG FALLBACK", "Failed to load url, trying next: ", next)
setAlternatives(z => z.filter(y => y !== next))
setCurrentUrl(next)
onFallback?.(next)
}
}}
/>
)
}
const VideoElement = ({ src }: VideoElementProps) => {
const { proxy } = useImgProxy()
const videoRef = useRef<HTMLVideoElement | null>(null)
const { ref: videoContainerRef, inView } = useInView({ threshold: 0.33 })
const isMobile = window.innerWidth < 768
useEffect(() => {
if (isMobile || !videoRef.current) {
return
}
if (inView) {
videoRef.current.play()
} else {
videoRef.current.pause()
}
}, [inView, isMobile])
return (
<div
ref={videoContainerRef}
className={classNames("flex justify-center items-center", {
"md:h-[510px]": !CONFIG.media.preferLargeMedia,
"-mx-3": CONFIG.media.preferLargeMedia,
})}
>
<video
crossOrigin="anonymous"
ref={videoRef}
loop={true}
muted={!isMobile}
src={src}
controls
poster={proxy(src)}
className={classNames("max-h-[80vh]", { "md:max-h-[510px]": !CONFIG.media.preferLargeMedia })}
onClick={e => e.stopPropagation()}
/>
</div>
)
}
export function MediaElement(props: MediaElementProps) {
if (props.mime.startsWith("image/")) {
return <ImageElement {...props} />
} else if (props.mime.startsWith("audio/")) {
return <AudioElement {...props} />
} else if (props.mime.startsWith("video/")) {
return <VideoElement {...props} />
} else {
return (
<a
key={props.src}
href={props.src}
onClick={e => e.stopPropagation()}
target="_blank"
rel="noreferrer"
className="text-highlight no-underline hover:underline"
>
{props.src}
</a>
)
}
}
================================================
FILE: packages/app/src/Components/Embed/Mention.tsx
================================================
import { NostrPrefix } from "@snort/shared"
import type { NostrLink } from "@snort/system"
import { useUserProfile } from "@snort/system-react"
import classNames from "classnames"
import { type ReactNode, useRef } from "react"
import DisplayName from "@/Components/User/DisplayName"
import { ProfileCardWrapper } from "@/Components/User/ProfileCardWrapper"
import { ProfileLink } from "@/Components/User/ProfileLink"
export default function Mention({
link,
prefix,
className,
}: {
link: NostrLink
prefix?: ReactNode
className?: string
}) {
const ref = useRef<HTMLSpanElement>(null)
const profile = useUserProfile(link.id, ref)
if (link.type !== NostrPrefix.Profile && link.type !== NostrPrefix.PublicKey) return
return (
<ProfileCardWrapper pubkey={link.id} user={profile}>
<span ref={ref} className={classNames("text-highlight", className)}>
<ProfileLink pubkey={link.id} user={profile} onClick={e => e.stopPropagation()}>
{prefix ?? "@"}
<DisplayName user={profile} pubkey={link.id} />
</ProfileLink>
</span>
</ProfileCardWrapper>
)
}
================================================
FILE: packages/app/src/Components/Embed/MixCloudEmbed.tsx
================================================
import usePreferences from "@/Hooks/usePreferences"
import { MixCloudRegex } from "@/Utils/Const"
const MixCloudEmbed = ({ link }: { link: string }) => {
const match = link.match(MixCloudRegex)
if (!match) return
const feedPath = `${match[1]}%2F${match[2]}`
const theme = usePreferences(s => s.theme)
const lightParams = theme === "light" ? "light=1" : "light=0"
return (
<iframe
title="SoundCloud player"
width="100%"
height="120"
frameBorder="0"
src={`https://www.mixcloud.com/widget/iframe/?hide_cover=1&${lightParams}&feed=%2F${feedPath}%2F`}
loading="lazy"
/>
)
}
export default MixCloudEmbed
================================================
FILE: packages/app/src/Components/Embed/NostrLink.tsx
================================================
import { tryParseNostrLink } from "@snort/system"
import { Link } from "react-router-dom"
import Mention from "@/Components/Embed/Mention"
import NoteQuote from "@/Components/Event/Note/NoteQuote"
import { NostrPrefix } from "@snort/shared"
import classNames from "classnames"
export default function NostrLink({ link, depth, className }: { link: string; depth?: number; className?: string }) {
const nav = tryParseNostrLink(link)
if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) {
return <Mention link={nav} className={className} />
} else if (nav?.type === NostrPrefix.Note || nav?.type === NostrPrefix.Event || nav?.type === NostrPrefix.Address) {
if ((depth ?? 0) > 1) {
const evLink = nav.encode()
return (
<Link
to={`/${evLink}`}
onClick={e => e.stopPropagation()}
state={{ from: location.pathname }}
className={className}
>
#{evLink.substring(0, 12)}
</Link>
)
} else {
return <NoteQuote link={nav} depth={depth} className={className} />
}
} else {
return (
<a
href={link}
onClick={e => e.stopPropagation()}
target="_blank"
rel="noreferrer"
className={classNames("text-highlight no-underline hover:underline", className)}
>
{link}
</a>
)
}
}
================================================
FILE: packages/app/src/Components/Embed/NostrNestsEmbed.tsx
================================================
const NostrNestsEmbed = ({ link }: { link: string }) => (
<iframe src={link} allow="microphone" width="480" height="680" style={{ maxHeight: 680 }}></iframe>
)
export default NostrNestsEmbed
================================================
FILE: packages/app/src/Components/Embed/PubkeyList.tsx
================================================
import { hexToBech32, LNURL } from "@snort/shared"
import type { NostrEvent } from "@snort/system"
import { WalletInvoiceState } from "@snort/wallet"
import { FormattedMessage, FormattedNumber } from "react-intl"
import AsyncButton from "@/Components/Button/AsyncButton"
import { Toastore } from "@/Components/Toaster/Toaster"
import FollowListBase from "@/Components/User/FollowListBase"
import useEventPublisher from "@/Hooks/useEventPublisher"
import usePreferences from "@/Hooks/usePreferences"
import { dedupe, findTag, getDisplayName } from "@/Utils"
import { useWallet } from "@/Wallet"
import { ProxyImg } from "../ProxyImg"
export default function PubkeyList({ ev, className }: { ev: NostrEvent; className?: string }) {
const wallet = useWallet()
const defaultZapAmount = usePreferences(s => s.defaultZapAmount)
const { publisher, system } = useEventPublisher()
const ids = dedupe(ev.tags.filter(a => a[0] === "p").map(a => a[1]))
async function zapAll() {
for (const pk of ids) {
try {
const profile = await system.config.profiles.get(pk)
const amtSend = defaultZapAmount
const lnurl = profile?.lud16 || profile?.lud06
if (lnurl) {
const svc = new LNURL(lnurl)
await svc.load()
const relays = await system.requestRouter?.forReplyTo(pk)
const zap = await publisher?.zap(
amtSend * 1000,
pk,
relays ?? [],
undefined,
`Zap from ${hexToBech32("note", ev.id)}`,
)
const invoice = await svc.getInvoice(amtSend, undefined, zap)
if (invoice.pr) {
const rsp = await wallet.wallet?.payInvoice(invoice.pr)
if (rsp?.state === WalletInvoiceState.Paid) {
Toastore.push({
element: (
<FormattedMessage
defaultMessage="Sent {n} sats to {name}"
id="Ig9/a1"
values={{
n: amtSend,
name: getDisplayName(profile, pk),
}}
/>
),
icon: "zap",
})
}
}
}
} catch (e) {
console.debug("Failed to zap", pk, e)
}
}
}
const picture = findTag(ev, "image")
return (
<>
{picture && <ProxyImg src={picture} className="rounded-lg max-h-44 w-full object-cover mb-4" />}
<FollowListBase
pubkeys={ids}
className={className}
title={findTag(ev, "title") ?? findTag(ev, "d")}
actions={<AsyncButton className="mr5 secondary" onClick={() => zapAll()}>
<FormattedMessage
defaultMessage="Zap all {n} sats"
id="IVbtTS"
values={{
n: <FormattedNumber value={defaultZapAmount * ids.length} />,
}}
/>
</AsyncButton>}
profilePreviewProps={{
options: {
about: true,
},
}}
/>
</>
)
}
================================================
FILE: packages/app/src/Components/Embed/SoundCloudEmded.tsx
================================================
const SoundCloudEmbed = ({ link }: { link: string }) => {
return (
<iframe
width="100%"
height="166"
allow="autoplay"
src={`https://w.soundcloud.com/player/?url=${link}`}
loading="lazy"
/>
)
}
export default SoundCloudEmbed
================================================
FILE: packages/app/src/Components/Embed/SpotifyEmbed.tsx
================================================
const SpotifyEmbed = ({ link }: { link: string }) => {
const convertedUrl = link.replace(/\/(track|album|playlist|episode)\/([a-zA-Z0-9]+)/, "/embed/$1/$2")
return (
<iframe
style={{ borderRadius: 12 }}
src={convertedUrl}
width="100%"
height="352"
frameBorder="0"
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
loading="lazy"
/>
)
}
export default SpotifyEmbed
================================================
FILE: packages/app/src/Components/Embed/TidalEmbed.tsx
================================================
import { useEffect, useState } from "react"
import { TidalRegex } from "@/Utils/Const"
// Re-use dom parser across instances of TidalEmbed
const domParser = new DOMParser()
async function oembedLookup(link: string) {
// Regex + re-construct to handle both tidal.com/type/id and tidal.com/browse/type/id links.
const regexResult = TidalRegex.exec(link)
if (!regexResult) {
return Promise.reject("Not a TIDAL link.")
}
const [, productType, productId] = regexResult
const oembedApi = `https://oembed.tidal.com/?url=https://tidal.com/browse/${productType}/${productId}`
const apiResponse = await fetch(oembedApi)
const json = await apiResponse.json()
const doc = domParser.parseFromString(json.html, "text/html")
const iframe = doc.querySelector("iframe")
if (!iframe) {
return Promise.reject("No iframe delivered.")
}
return {
source: iframe.getAttribute("src"),
height: json.height,
}
}
const TidalEmbed = ({ link }: { link: string }) => {
const [source, setSource] = useState<string>()
const [height, setHeight] = useState<number>()
const extraStyles = link.includes("video") ? { aspectRatio: "16 / 9" } : { height }
useEffect(() => {
oembedLookup(link)
.then(data => {
setSource(data.source || undefined)
setHeight(data.height)
})
.catch(console.error)
}, [link])
if (!source) {
return (
<a
href={link}
target="_blank"
rel="noreferrer"
onClick={e => e.stopPropagation()}
className="text-highlight no-underline hover:underline"
>
{link}
</a>
)
}
return (
<iframe
src={source}
style={extraStyles}
width="100%"
allow="encrypted-media *; clipboard-write *; clipboard-read *"
sandbox="allow-scripts allow-popups allow-forms allow-same-origin"
title="TIDAL Embed"
frameBorder={0}
loading="lazy"
/>
)
}
export default TidalEmbed
================================================
FILE: packages/app/src/Components/Embed/TwitchEmbed.tsx
================================================
const TwitchEmbed = ({ link }: { link: string }) => {
const channel = link.split("/").slice(-1)
const args = `?channel=${channel}&parent=${window.location.hostname}&muted=true`
return (
<iframe
src={`https://player.twitch.tv/${args}`}
className="aspect-video w-full"
allowFullScreen={true}
loading="lazy"
/>
)
}
export default TwitchEmbed
================================================
FILE: packages/app/src/Components/Embed/UrlStatusCheck.tsx
================================================
import { useEffect, useState } from "react"
import Icon from "../Icons/Icon"
import Spinner from "../Icons/Spinner"
interface UrlStatusCheckProps {
url: string
}
export default function UrlStatusCheck({ url }: UrlStatusCheckProps) {
const [status, setStatus] = useState<"loading" | "success" | "error">("loading")
useEffect(() => {
const controller = new AbortController()
setStatus("loading")
fetch(url, {
method: "HEAD",
signal: controller.signal,
})
.then(response => {
if (response.ok) {
setStatus("success")
} else {
setStatus("error")
}
})
.catch(() => {
setStatus("error")
})
return () => controller.abort()
}, [url])
if (status === "loading") {
return <Spinner width={16} />
}
if (status === "success") {
return <Icon name="check" className="text-green-500" size={16} />
}
return <Icon name="close" className="text-red-500" size={16} />
}
================================================
FILE: packages/app/src/Components/Embed/WavlakeEmbed.tsx
================================================
const WavlakeEmbed = ({ link }: { link: string }) => {
const convertedUrl = link.replace(/(?:player\.|www\.)?wavlake\.com/, "embed.wavlake.com")
return (
<iframe style={{ borderRadius: 12 }} src={convertedUrl} width="100%" height="380" frameBorder="0" loading="lazy" />
)
}
export default WavlakeEmbed
================================================
FILE: packages/app/src/Components/Embed/YoutubeEmbed.tsx
================================================
import { YoutubeUrlRegex } from "@/Utils/Const"
export default function YoutubeEmbed({ link }: { link: string }) {
const m = link.match(YoutubeUrlRegex)
if (!m) return
return (
<iframe
className="aspect-video w-full"
src={`https://www.youtube.com/embed/${m[1]}${m[3] ? `?list=${m[3].slice(6)}` : ""}`}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen={true}
/>
)
}
================================================
FILE: packages/app/src/Components/Embed/ZapstrEmbed.css
================================================
.zapstr {
}
.zapstr > img {
margin: 0 10px 0 0;
}
.zapstr audio {
margin: 0;
height: 2em;
}
.zapstr .pfp .avatar {
width: 35px;
height: 35px;
}
.zapstr .pfp .subheader {
text-transform: capitalize;
}
================================================
FILE: packages/app/src/Components/Embed/ZapstrEmbed.tsx
================================================
import "./ZapstrEmbed.css"
import { type NostrEvent, NostrLink } from "@snort/system"
import { FormattedMessage } from "react-intl"
import { Link } from "react-router-dom"
import { ProxyImg } from "@/Components/ProxyImg"
import ProfileImage from "@/Components/User/ProfileImage"
export default function ZapstrEmbed({ ev }: { ev: NostrEvent }) {
const media = ev.tags.find(a => a[0] === "media")
const cover = ev.tags.find(a => a[0] === "cover")
const subject = ev.tags.find(a => a[0] === "subject")
const refPersons = ev.tags.filter(a => a[0] === "p")
const link = NostrLink.fromEvent(ev).encode(CONFIG.eventLinkPrefix)
return (
<>
<div className="flex mb-2.5 layer-1">
<ProxyImg src={cover?.[1] ?? ""} size={100} className="mr-2.5" />
<div className="flex flex-col">
<div>
<h3>{subject?.[1] ?? ""}</h3>
</div>
<audio src={media?.[1] ?? ""} controls={true} className="m-0 h-8" />
<div className="zapstr">
{refPersons.map(a => (
<ProfileImage key={a[1]} pubkey={a[1]} subHeader={a[2] ?? ""} className="pfp" defaultNip=" " />
))}
</div>
</div>
</div>
<Link to={`https://zapstr.live/?track=${link}`} target="_blank">
<button>
<FormattedMessage defaultMessage="Open on Zapstr" />
</button>
</Link>
</>
)
}
================================================
FILE: packages/app/src/Components/ErrorBoundary.tsx
================================================
import React from "react"
import { trackEvent } from "@/Utils"
interface ErrorBoundaryState {
hasError: boolean
error?: Error
}
interface ErrorBoundaryProps {
children: React.ReactNode
}
export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("Caught an error:", error, errorInfo)
trackEvent("error", { error: error.message, errorInfo: JSON.stringify(errorInfo) })
}
render() {
if (this.state.hasError) {
// Render any custom fallback UI with the error message
return (
<div className="m-4 layer-1 text-xs font-mono overflow-auto">
<h2>Something went wrong.</h2>
<b>{this.state.error?.message}</b>
<pre>{this.state.error?.stack}</pre>
</div>
)
}
return this.props.children
}
}
================================================
FILE: packages/app/src/Components/ErrorOrOffline.tsx
================================================
import { OfflineError } from "@snort/shared"
import classNames from "classnames"
import Icon from "@/Components/Icons/Icon"
import { Offline } from "./Offline"
export function ErrorOrOffline({
error,
onRetry,
className,
}: {
error: Error
onRetry?: () => void | Promise<void>
className?: string
}) {
if (error instanceof OfflineError) {
return <Offline onRetry={onRetry} className={className} />
} else {
return (
<div className={classNames("flex flex-row items-center px-4 py-3 gap-2", className)}>
<Icon name="alert-circle" size={24} />
<b>{error.message}</b>
</div>
)
}
}
================================================
FILE: packages/app/src/Components/Event/Application.tsx
================================================
import { mapEventToProfile, type TaggedNostrEvent } from "@snort/system"
import { FormattedMessage } from "react-intl"
import KindName from "../kind-name"
import Avatar from "../User/Avatar"
import DisplayName from "../User/DisplayName"
import ProfileImage from "../User/ProfileImage"
import Icon from "../Icons/Icon"
export function ApplicationHandler({ ev }: { ev: TaggedNostrEvent }) {
const profile = mapEventToProfile(ev)
const kinds = ev.tags
.filter(a => a[0] === "k")
.map(a => Number(a[1]))
.sort((a, b) => a - b)
const sourceLink = ev.tags.find(a => a[0] === "r" && a[2] === "source")?.[1]
return (
<div className="px-3 py-2 flex gap-2 flex-col">
<div className="flex items-center gap-2 text-xl">
<Avatar user={profile} pubkey={""} size={120} />
<div className="flex flex-col gap-2">
<DisplayName user={profile} pubkey={""} />
<div className="text-sm flex flex-col gap-2">
<div className="text-neutral-400">
<FormattedMessage defaultMessage="Published by" />
</div>
<ProfileImage pubkey={ev.pubkey} size={30} />
{sourceLink && (
<a href={sourceLink} className="flex items-center gap-1" target="_blank">
<Icon name="link" size={14} />
<FormattedMessage defaultMessage="Source Code" />
</a>
)}
</div>
</div>
</div>
<FormattedMessage defaultMessage="Supported Kinds:" />
<div className="flex flex-wrap gap-1">
{kinds.map(a => (
<div key={a} className="bg-layer-1 px-2 py-1 rounded-lg">
<KindName kind={a} />
</div>
))}
</div>
</div>
)
}
================================================
FILE: packages/app/src/Components/Event/Create/NoteCreator.tsx
================================================
/* eslint-disable max-lines */
import { fetchNip05Pubkey, NostrPrefix, unixNow } from "@snort/shared"
import {
type EventBuilder,
EventKind,
LinkScope,
Nip18,
type Nip94Tags,
nip94TagsToIMeta,
NostrLink,
readNip94Tags,
type TaggedNostrEvent,
tryParseNostrLink,
} from "@snort/system"
import { useUserProfile } from "@snort/system-react"
import type { ZapTarget } from "@snort/wallet"
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
import classNames from "classnames"
import { type ClipboardEventHandler, type DragEvent, useEffect } from "react"
import { FormattedMessage, useIntl } from "react-intl"
import AsyncButton from "@/Components/Button/AsyncButton"
import { AsyncIcon } from "@/Components/Button/AsyncIcon"
import CloseButton from "@/Components/Button/CloseButton"
import IconButton from "@/Components/Button/IconButton"
import { sendEventToRelays } from "@/Components/Event/Create/util"
import Note, { type NotePropsOptions } from "@/Components/Event/EventComponent"
import Flyout from "@/Components/flyout"
import Icon from "@/Components/Icons/Icon"
import { ToggleSwitch } from "@/Components/Icons/Toggle"
import Modal from "@/Components/Modal/Modal"
import Textarea from "@/Components/Textarea/Textarea"
import { Toastore } from "@/Components/Toaster/Toaster"
import { MediaServerFileList } from "@/Components/Upload/file-picker"
import Avatar from "@/Components/User/Avatar"
import useEventPublisher from "@/Hooks/useEventPublisher"
import useLogin from "@/Hooks/useLogin"
import usePreferences from "@/Hooks/usePreferences"
import useRelays from "@/Hooks/useRelays"
import { useNoteCreator } from "@/State/NoteCreator"
import { openFile, trackEvent } from "@/Utils"
import useFileUpload from "@/Utils/Upload"
import { GetPowWorker } from "@/Utils/wasm"
import { OkResponseRow } from "./OkResponseRow"
const previewNoteOptions = {
showContextMenu: false,
showFooter: false,
canClick: false,
showTime: false,
} as NotePropsOptions
const replyToNoteOptions = {
showFooter: false,
showContextMenu: false,
showProfileCard: false,
showTime: false,
canClick: false,
longFormPreview: true,
showMedia: false,
} as NotePropsOptions
export function NoteCreator() {
const { formatMessage } = useIntl()
const uploader = useFileUpload()
const publicKey = useLogin(s => s.publicKey)
const profile = useUserProfile(publicKey)
const pow = usePreferences(s => s.pow)
const relays = useRelays()
const { system, publisher: pub } = useEventPublisher()
const publisher = pow ? pub?.pow(pow, GetPowWorker()) : pub
const note = useNoteCreator()
useEffect(() => {
const draft = localStorage.getItem("msgDraft")
if (draft) {
note.update(n => (n.note = draft))
}
}, [note.update])
async function buildNote() {
try {
note.update(v => (v.error = ""))
if (note && publisher) {
let extraTags: Array<Array<string>> | undefined
if (note.zapSplits) {
const parsedSplits = [] as Array<ZapTarget>
for (const s of note.zapSplits) {
if (s.value.startsWith(NostrPrefix.PublicKey) || s.value.startsWith(NostrPrefix.Profile)) {
const link = tryParseNostrLink(s.value)
if (link) {
parsedSplits.push({ ...s, value: link.id })
} else {
throw new Error(
formatMessage(
{
defaultMessage: "Failed to parse zap split: {input}",
},
{
input: s.value,
},
),
)
}
} else if (s.value.includes("@")) {
const [name, domain] = s.value.split("@")
const pubkey = await fetchNip05Pubkey(name, domain)
if (pubkey) {
parsedSplits.push({ ...s, value: pubkey })
} else {
throw new Error(
formatMessage(
{
defaultMessage: "Failed to parse zap split: {input}",
},
{
input: s.value,
},
),
)
}
} else {
throw new Error(
formatMessage(
{
defaultMessage: "Invalid zap split: {input}",
},
{
input: s.value,
},
),
)
}
}
extraTags = parsedSplits.map(v => ["zap", v.value, "", String(v.weight)])
}
if (note.sensitive) {
extraTags ??= []
extraTags.push(["content-warning", note.sensitive])
}
if (note.pollOptions) {
extraTags ??= []
extraTags.push(...note.pollOptions.map((a, i) => ["poll_option", i.toString(), a]))
}
if (note.hashTags.length > 0) {
extraTags ??= []
extraTags.push(...note.hashTags.map(a => ["t", a.toLowerCase()]))
}
// attach 1 link and use other duplicates as fallback urls
for (const [, v] of Object.entries(note.attachments ?? {})) {
const at = v[0]
note.note += note.note.length > 0 ? `\n${at.url}` : at.url
const n94 =
(at.nip94?.length ?? 0) > 0
? readNip94Tags(at.nip94!)
: ({
url: at.url,
hash: at.sha256,
size: at.size,
mimeType: at.type,
} as Nip94Tags)
// attach fallbacks
n94.fallback ??= []
n94.fallback.push(
...v
.slice(1)
.filter(a => a.url)
.map(a => a.url!),
)
extraTags ??= []
extraTags.push(nip94TagsToIMeta(n94))
}
// add quote repost
if (no
gitextract_dgfwre9y/ ├── .dockerignore ├── .drone.yml ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ ├── docker.yml │ ├── nsite.yml │ └── release.yml ├── .gitignore ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── AGENTS.md ├── Dockerfile ├── Dockerfile.prebuilt ├── LICENSE ├── README.md ├── biome.json ├── crowdin.yml ├── docker/ │ └── nginx.conf ├── functions/ │ ├── _middleware.ts │ └── tsconfig.json ├── maintainers.yaml ├── package.json ├── packages/ │ ├── app/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── bun-env.d.ts │ │ ├── bunfig.toml │ │ ├── config/ │ │ │ ├── README.md │ │ │ ├── default.json │ │ │ ├── iris.json │ │ │ ├── meku.json │ │ │ ├── nostr.json │ │ │ ├── phoenix.json │ │ │ └── soloco.json │ │ ├── custom.d.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── public/ │ │ │ ├── iris/ │ │ │ │ ├── .well-known/ │ │ │ │ │ └── assetlinks.json │ │ │ │ ├── _headers │ │ │ │ ├── manifest.json │ │ │ │ └── robots.txt │ │ │ ├── nostr/ │ │ │ │ └── _headers │ │ │ ├── phoenix/ │ │ │ │ ├── .well-known/ │ │ │ │ │ ├── apple-app-site-association │ │ │ │ │ └── assetlinks.json │ │ │ │ ├── _headers │ │ │ │ ├── manifest.json │ │ │ │ └── robots.txt │ │ │ └── snort/ │ │ │ ├── .well-known/ │ │ │ │ ├── apple-app-site-association │ │ │ │ └── assetlinks.json │ │ │ ├── _headers │ │ │ ├── manifest.json │ │ │ └── robots.txt │ │ ├── src/ │ │ │ ├── Agent/ │ │ │ │ └── system-prompt.ts │ │ │ ├── Cache/ │ │ │ │ ├── CommunityLeadersStore.tsx │ │ │ │ ├── GiftWrapCache.ts │ │ │ │ ├── ProfileWorkerCache.ts │ │ │ │ ├── RefreshFeedCache.ts │ │ │ │ ├── RelaysWorkerCache.ts │ │ │ │ ├── UserFollowsWorker.ts │ │ │ │ ├── index.ts │ │ │ │ └── worker-cached.ts │ │ │ ├── Components/ │ │ │ │ ├── AskSnort/ │ │ │ │ │ └── AskSnortInput.tsx │ │ │ │ ├── Button/ │ │ │ │ │ ├── AsyncButton.tsx │ │ │ │ │ ├── AsyncIcon.tsx │ │ │ │ │ ├── BackButton.tsx │ │ │ │ │ ├── CloseButton.tsx │ │ │ │ │ ├── IconButton.tsx │ │ │ │ │ ├── LogoutButton.tsx │ │ │ │ │ └── NavLink.tsx │ │ │ │ ├── Collapsed.tsx │ │ │ │ ├── CommunityLeaders/ │ │ │ │ │ ├── Award.tsx │ │ │ │ │ └── LeaderBadge.tsx │ │ │ │ ├── Copy/ │ │ │ │ │ └── Copy.tsx │ │ │ │ ├── DvmSelector.tsx │ │ │ │ ├── Embed/ │ │ │ │ │ ├── AppleMusicEmbed.tsx │ │ │ │ │ ├── BlossomBlob.tsx │ │ │ │ │ ├── CashuNuts.tsx │ │ │ │ │ ├── GenericPlayer.tsx │ │ │ │ │ ├── Hashtag.tsx │ │ │ │ │ ├── HyperText.tsx │ │ │ │ │ ├── Invoice.tsx │ │ │ │ │ ├── LinkPreview.tsx │ │ │ │ │ ├── MagnetLink.tsx │ │ │ │ │ ├── MediaElement.tsx │ │ │ │ │ ├── Mention.tsx │ │ │ │ │ ├── MixCloudEmbed.tsx │ │ │ │ │ ├── NostrLink.tsx │ │ │ │ │ ├── NostrNestsEmbed.tsx │ │ │ │ │ ├── PubkeyList.tsx │ │ │ │ │ ├── SoundCloudEmded.tsx │ │ │ │ │ ├── SpotifyEmbed.tsx │ │ │ │ │ ├── TidalEmbed.tsx │ │ │ │ │ ├── TwitchEmbed.tsx │ │ │ │ │ ├── UrlStatusCheck.tsx │ │ │ │ │ ├── WavlakeEmbed.tsx │ │ │ │ │ ├── YoutubeEmbed.tsx │ │ │ │ │ ├── ZapstrEmbed.css │ │ │ │ │ └── ZapstrEmbed.tsx │ │ │ │ ├── ErrorBoundary.tsx │ │ │ │ ├── ErrorOrOffline.tsx │ │ │ │ ├── Event/ │ │ │ │ │ ├── Application.tsx │ │ │ │ │ ├── Create/ │ │ │ │ │ │ ├── NoteCreator.tsx │ │ │ │ │ │ ├── NoteCreatorButton.tsx │ │ │ │ │ │ ├── OkResponseRow.tsx │ │ │ │ │ │ └── util.ts │ │ │ │ │ ├── DVMJobFeedback.tsx │ │ │ │ │ ├── EventComponent.tsx │ │ │ │ │ ├── FileUpload.tsx │ │ │ │ │ ├── HiddenNote.tsx │ │ │ │ │ ├── LoadMore.tsx │ │ │ │ │ ├── LongFormText.tsx │ │ │ │ │ ├── Markdown.tsx │ │ │ │ │ ├── NostrFileHeader.tsx │ │ │ │ │ ├── Note/ │ │ │ │ │ │ ├── ClientFingerprinting.tsx │ │ │ │ │ │ ├── ClientTag.tsx │ │ │ │ │ │ ├── Note.tsx │ │ │ │ │ │ ├── NoteAppHandler.tsx │ │ │ │ │ │ ├── NoteContent.tsx │ │ │ │ │ │ ├── NoteContext.tsx │ │ │ │ │ │ ├── NoteContextMenu.tsx │ │ │ │ │ │ ├── NoteFooter/ │ │ │ │ │ │ │ ├── AsyncFooterIcon.tsx │ │ │ │ │ │ │ ├── FooterZapButton.tsx │ │ │ │ │ │ │ ├── LikeButton.tsx │ │ │ │ │ │ │ ├── NoteFooter.tsx │ │ │ │ │ │ │ ├── PowIcon.tsx │ │ │ │ │ │ │ ├── ReplyButton.tsx │ │ │ │ │ │ │ ├── RepostButton.tsx │ │ │ │ │ │ │ └── ZapperQueue.tsx │ │ │ │ │ │ ├── NoteHeader.tsx │ │ │ │ │ │ ├── NoteQuote.tsx │ │ │ │ │ │ ├── NoteText.tsx │ │ │ │ │ │ ├── NoteTime.tsx │ │ │ │ │ │ ├── ReactionsModal.tsx │ │ │ │ │ │ ├── ReplyTag.tsx │ │ │ │ │ │ ├── TranslationInfo.tsx │ │ │ │ │ │ └── types.tsx │ │ │ │ │ ├── NoteReaction.tsx │ │ │ │ │ ├── Poll.tsx │ │ │ │ │ ├── Reveal.tsx │ │ │ │ │ ├── RevealMedia.tsx │ │ │ │ │ ├── Thread/ │ │ │ │ │ │ ├── Subthread.tsx │ │ │ │ │ │ ├── Thread.tsx │ │ │ │ │ │ ├── ThreadRoute.tsx │ │ │ │ │ │ └── util.ts │ │ │ │ │ ├── Zap.tsx │ │ │ │ │ ├── ZapButton.tsx │ │ │ │ │ ├── ZapGoal.tsx │ │ │ │ │ └── ZapsSummary.tsx │ │ │ │ ├── Feed/ │ │ │ │ │ ├── ImageGridItem.tsx │ │ │ │ │ ├── LoadMore.tsx │ │ │ │ │ ├── RootTabItems.tsx │ │ │ │ │ ├── RootTabs.tsx │ │ │ │ │ ├── Timeline.tsx │ │ │ │ │ ├── TimelineChunk.tsx │ │ │ │ │ ├── TimelineFollows.tsx │ │ │ │ │ ├── TimelineFragment.tsx │ │ │ │ │ ├── TimelineRenderer.tsx │ │ │ │ │ └── UsersFeed.tsx │ │ │ │ ├── Icons/ │ │ │ │ │ ├── Alby.tsx │ │ │ │ │ ├── BlueWallet.tsx │ │ │ │ │ ├── Cashu.tsx │ │ │ │ │ ├── ECash.tsx │ │ │ │ │ ├── Icon.tsx │ │ │ │ │ ├── NWC.tsx │ │ │ │ │ ├── Nostrich.tsx │ │ │ │ │ ├── Spinner.tsx │ │ │ │ │ └── Toggle.tsx │ │ │ │ ├── IntlProvider/ │ │ │ │ │ ├── IntlProvider.tsx │ │ │ │ │ ├── IntlProviderUtils.tsx │ │ │ │ │ ├── langStore.tsx │ │ │ │ │ └── useLocale.tsx │ │ │ │ ├── Invite.tsx │ │ │ │ ├── LiveStream/ │ │ │ │ │ ├── LiveEvent.tsx │ │ │ │ │ ├── LiveStreams.tsx │ │ │ │ │ ├── VU.tsx │ │ │ │ │ ├── livekit.tsx │ │ │ │ │ └── nests-participants.tsx │ │ │ │ ├── Modal/ │ │ │ │ │ └── Modal.tsx │ │ │ │ ├── Nip5Service.tsx │ │ │ │ ├── Offline.tsx │ │ │ │ ├── PageSpinner.tsx │ │ │ │ ├── PinPrompt/ │ │ │ │ │ └── PinPrompt.tsx │ │ │ │ ├── Progress/ │ │ │ │ │ └── Progress.tsx │ │ │ │ ├── ProxyImg.tsx │ │ │ │ ├── QrCode.tsx │ │ │ │ ├── ReBroadcaster.tsx │ │ │ │ ├── Relay/ │ │ │ │ │ ├── Relay.tsx │ │ │ │ │ ├── RelaysMetadata.tsx │ │ │ │ │ ├── name.tsx │ │ │ │ │ ├── paid.tsx │ │ │ │ │ ├── permissions.tsx │ │ │ │ │ ├── software.tsx │ │ │ │ │ ├── status-label.tsx │ │ │ │ │ ├── uptime-label.tsx │ │ │ │ │ └── uptime.tsx │ │ │ │ ├── Review.tsx │ │ │ │ ├── RightWidgets/ │ │ │ │ │ ├── articles.tsx │ │ │ │ │ ├── base.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── invite-friends.tsx │ │ │ │ │ └── mini-stream.tsx │ │ │ │ ├── ScrollToTop.tsx │ │ │ │ ├── SearchBox/ │ │ │ │ │ └── SearchBox.tsx │ │ │ │ ├── Spotlight/ │ │ │ │ │ ├── SpotlightMedia.tsx │ │ │ │ │ ├── SpotlightThreadModal.tsx │ │ │ │ │ └── context.tsx │ │ │ │ ├── SuggestedProfiles.tsx │ │ │ │ ├── TabSelectors/ │ │ │ │ │ └── TabSelectors.tsx │ │ │ │ ├── Tasks/ │ │ │ │ │ ├── BackupKey.tsx │ │ │ │ │ ├── DonateTask.tsx │ │ │ │ │ ├── FollowMorePeople.tsx │ │ │ │ │ ├── Nip5Task.tsx │ │ │ │ │ ├── NoticeZapPool.tsx │ │ │ │ │ ├── PendingChangesTask.tsx │ │ │ │ │ ├── RenewSubscription.tsx │ │ │ │ │ ├── TaskList.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Text/ │ │ │ │ │ ├── DisableMedia.tsx │ │ │ │ │ ├── HighlightedText.tsx │ │ │ │ │ ├── Text.tsx │ │ │ │ │ └── const.ts │ │ │ │ ├── Textarea/ │ │ │ │ │ ├── Textarea.css │ │ │ │ │ └── Textarea.tsx │ │ │ │ ├── Toaster/ │ │ │ │ │ └── Toaster.tsx │ │ │ │ ├── Trending/ │ │ │ │ │ ├── ShortNote.tsx │ │ │ │ │ ├── TrendingHashtags.tsx │ │ │ │ │ ├── TrendingPosts.tsx │ │ │ │ │ └── TrendingUsers.tsx │ │ │ │ ├── Upload/ │ │ │ │ │ └── file-picker.tsx │ │ │ │ ├── User/ │ │ │ │ │ ├── AnimalName.ts │ │ │ │ │ ├── Avatar.tsx │ │ │ │ │ ├── AvatarEditor.tsx │ │ │ │ │ ├── AvatarGroup.tsx │ │ │ │ │ ├── BadgeList.tsx │ │ │ │ │ ├── Bookmarks.tsx │ │ │ │ │ ├── Debug.tsx │ │ │ │ │ ├── DisplayName.tsx │ │ │ │ │ ├── FollowButton.tsx │ │ │ │ │ ├── FollowDistanceIndicator.tsx │ │ │ │ │ ├── FollowListBase.tsx │ │ │ │ │ ├── FollowedBy.tsx │ │ │ │ │ ├── Following.tsx │ │ │ │ │ ├── FollowsYou.tsx │ │ │ │ │ ├── MuteButton.tsx │ │ │ │ │ ├── MutedList.tsx │ │ │ │ │ ├── Nip05.tsx │ │ │ │ │ ├── NoteToSelf.tsx │ │ │ │ │ ├── ProfileCard.tsx │ │ │ │ │ ├── ProfileCardWrapper.tsx │ │ │ │ │ ├── ProfileImage.tsx │ │ │ │ │ ├── ProfileLink.tsx │ │ │ │ │ ├── ProfilePreview.tsx │ │ │ │ │ ├── UserWebsiteLink.tsx │ │ │ │ │ └── Username.tsx │ │ │ │ ├── WarningNotice/ │ │ │ │ │ └── WarningNotice.tsx │ │ │ │ ├── ZapModal/ │ │ │ │ │ ├── SuccessAction.tsx │ │ │ │ │ ├── ZapModal.tsx │ │ │ │ │ ├── ZapModalInput.tsx │ │ │ │ │ ├── ZapModalInvoice.tsx │ │ │ │ │ ├── ZapModalTitle.tsx │ │ │ │ │ ├── ZapType.tsx │ │ │ │ │ └── ZapTypeSelector.tsx │ │ │ │ ├── flyout.tsx │ │ │ │ ├── json.tsx │ │ │ │ ├── kind-name.tsx │ │ │ │ ├── messages.ts │ │ │ │ ├── nip.tsx │ │ │ │ └── zap-amount.tsx │ │ │ ├── Db/ │ │ │ │ └── FuzzySearch.ts │ │ │ ├── External/ │ │ │ │ ├── NostrBand.ts │ │ │ │ ├── NostrServices.ts │ │ │ │ ├── SnortApi.ts │ │ │ │ ├── base.ts │ │ │ │ └── index.ts │ │ │ ├── Feed/ │ │ │ │ ├── ArticlesFeed.ts │ │ │ │ ├── BadgesFeed.ts │ │ │ │ ├── FollowersFeed.ts │ │ │ │ ├── FollowsFeed.ts │ │ │ │ ├── HashtagsFeed.ts │ │ │ │ ├── LoginFeed.ts │ │ │ │ ├── RelayState.ts │ │ │ │ ├── RelaysFeed.tsx │ │ │ │ ├── StatusFeed.ts │ │ │ │ ├── TimelineFeed.ts │ │ │ │ ├── WorkerRelayView.ts │ │ │ │ └── ZapsFeed.ts │ │ │ ├── Hooks/ │ │ │ │ ├── useAiAgent.ts │ │ │ │ ├── useAppHandler.ts │ │ │ │ ├── useBlindSpot.ts │ │ │ │ ├── useBlossomServers.ts │ │ │ │ ├── useCloseRelays.ts │ │ │ │ ├── useCommunityLeaders.tsx │ │ │ │ ├── useContentDiscovery.ts │ │ │ │ ├── useCopy.ts │ │ │ │ ├── useDiscoverMediaServers.ts │ │ │ │ ├── useDvmLinks.ts │ │ │ │ ├── useEventPublisher.tsx │ │ │ │ ├── useFollowControls.ts │ │ │ │ ├── useHistoryState.tsx │ │ │ │ ├── useHorizontalScroll.tsx │ │ │ │ ├── useHovering.ts │ │ │ │ ├── useImgProxy.ts │ │ │ │ ├── useKeyboardShortcut.ts │ │ │ │ ├── useLists.tsx │ │ │ │ ├── useLiveStreams.ts │ │ │ │ ├── useLoading.tsx │ │ │ │ ├── useLogin.tsx │ │ │ │ ├── useLoginHandler.tsx │ │ │ │ ├── useLoginRelays.tsx │ │ │ │ ├── useMediaServerList.ts │ │ │ │ ├── useModeration.tsx │ │ │ │ ├── usePageDimensions.tsx │ │ │ │ ├── usePreferences.ts │ │ │ │ ├── useProfileLink.ts │ │ │ │ ├── useProfileSearch.tsx │ │ │ │ ├── useRates.tsx │ │ │ │ ├── useRelays.tsx │ │ │ │ ├── useTextTransformCache.tsx │ │ │ │ ├── useTheme.tsx │ │ │ │ ├── useTimelineChunks.ts │ │ │ │ ├── useTimelineWindow.tsx │ │ │ │ ├── useTraceTimeline.tsx │ │ │ │ ├── useWindowSize.ts │ │ │ │ └── useWoT.ts │ │ │ ├── Pages/ │ │ │ │ ├── About.tsx │ │ │ │ ├── Agent/ │ │ │ │ │ └── AgentPage.tsx │ │ │ │ ├── CacheDebug.tsx │ │ │ │ ├── ComponentDebug.tsx │ │ │ │ ├── Deck/ │ │ │ │ │ ├── Articles.tsx │ │ │ │ │ ├── Columns.tsx │ │ │ │ │ └── DeckLayout.tsx │ │ │ │ ├── Discover.tsx │ │ │ │ ├── Donate/ │ │ │ │ │ ├── DonatePage.tsx │ │ │ │ │ ├── ZapPoolDonateSection.tsx │ │ │ │ │ └── const.ts │ │ │ │ ├── ErrorPage.tsx │ │ │ │ ├── FixedPage.tsx │ │ │ │ ├── FreeNostrAddressPage.tsx │ │ │ │ ├── HashTagsPage.tsx │ │ │ │ ├── HelpPage.tsx │ │ │ │ ├── Layout/ │ │ │ │ │ ├── Footer.tsx │ │ │ │ │ ├── HasNotificationsMarker.tsx │ │ │ │ │ ├── Header.tsx │ │ │ │ │ ├── LogoHeader.tsx │ │ │ │ │ ├── NavSidebar.tsx │ │ │ │ │ ├── NotificationsHeader.tsx │ │ │ │ │ ├── ProfileMenu.tsx │ │ │ │ │ ├── RightColumn.tsx │ │ │ │ │ ├── WalletBalance.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── ListFeedPage.tsx │ │ │ │ ├── Messages/ │ │ │ │ │ ├── ChatParticipant.tsx │ │ │ │ │ ├── DM.tsx │ │ │ │ │ ├── DmWindow.tsx │ │ │ │ │ ├── MessagesPage.tsx │ │ │ │ │ ├── NewChatWindow.tsx │ │ │ │ │ ├── UnreadCount.tsx │ │ │ │ │ └── WriteMessage.tsx │ │ │ │ ├── NostrAddressPage.tsx │ │ │ │ ├── NostrLinkHandler.tsx │ │ │ │ ├── Notifications/ │ │ │ │ │ ├── NotificationGroup.tsx │ │ │ │ │ ├── Notifications.tsx │ │ │ │ │ ├── getNotificationContext.tsx │ │ │ │ │ └── notificationContext.tsx │ │ │ │ ├── Profile/ │ │ │ │ │ ├── AvatarSection.tsx │ │ │ │ │ ├── MusicStatus.tsx │ │ │ │ │ ├── ProfileDetails.tsx │ │ │ │ │ ├── ProfilePage.tsx │ │ │ │ │ ├── ProfileTabComponents.tsx │ │ │ │ │ ├── ProfileTabSelectors.tsx │ │ │ │ │ └── ProfileTabType.tsx │ │ │ │ ├── Root/ │ │ │ │ │ ├── BlindSpots.tsx │ │ │ │ │ ├── ConversationsTab.tsx │ │ │ │ │ ├── DefaultTab.tsx │ │ │ │ │ ├── FollowSets.tsx │ │ │ │ │ ├── FollowedByFriendsTab.tsx │ │ │ │ │ ├── ForYouTab.tsx │ │ │ │ │ ├── Media.tsx │ │ │ │ │ ├── NotesTab.tsx │ │ │ │ │ ├── RelayFeedPage.tsx │ │ │ │ │ ├── RootRoutes.tsx │ │ │ │ │ ├── RootTabRoutes.tsx │ │ │ │ │ └── TagsTab.tsx │ │ │ │ ├── SearchPage.tsx │ │ │ │ ├── TopicsPage.tsx │ │ │ │ ├── ZapPool/ │ │ │ │ │ ├── ZapPool.css │ │ │ │ │ ├── ZapPool.tsx │ │ │ │ │ ├── ZapPoolPageInner.tsx │ │ │ │ │ └── ZapPoolTarget.tsx │ │ │ │ ├── messages.ts │ │ │ │ ├── onboarding/ │ │ │ │ │ ├── discover.tsx │ │ │ │ │ ├── fixedModeration.tsx │ │ │ │ │ ├── fixedTopics.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── moderation.tsx │ │ │ │ │ ├── profile.tsx │ │ │ │ │ ├── routes.ts │ │ │ │ │ ├── sign-in.tsx │ │ │ │ │ ├── sign-up.tsx │ │ │ │ │ └── topics.tsx │ │ │ │ ├── settings/ │ │ │ │ │ ├── Accounts.tsx │ │ │ │ │ ├── Cache.tsx │ │ │ │ │ ├── Keys.css │ │ │ │ │ ├── Keys.tsx │ │ │ │ │ ├── Menu/ │ │ │ │ │ │ ├── Menu.tsx │ │ │ │ │ │ └── SettingsMenuComponent.tsx │ │ │ │ │ ├── Moderation.tsx │ │ │ │ │ ├── Notifications.tsx │ │ │ │ │ ├── Preferences.tsx │ │ │ │ │ ├── Profile.tsx │ │ │ │ │ ├── Referrals.tsx │ │ │ │ │ ├── RelayInfo.tsx │ │ │ │ │ ├── Relays.tsx │ │ │ │ │ ├── Routes.tsx │ │ │ │ │ ├── SnortNostrAddressService.tsx │ │ │ │ │ ├── WalletSettings.tsx │ │ │ │ │ ├── handle/ │ │ │ │ │ │ ├── LNAddress.tsx │ │ │ │ │ │ ├── ListHandles.tsx │ │ │ │ │ │ ├── Manage.tsx │ │ │ │ │ │ ├── TransferHandle.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── routes.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── media-settings.tsx │ │ │ │ │ ├── messages.ts │ │ │ │ │ ├── relays/ │ │ │ │ │ │ └── discover.tsx │ │ │ │ │ ├── saveRelays.tsx │ │ │ │ │ ├── tools/ │ │ │ │ │ │ ├── follows-relay-health.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── prune-follows.tsx │ │ │ │ │ │ ├── routes.tsx │ │ │ │ │ │ └── sync-account.tsx │ │ │ │ │ └── wallet/ │ │ │ │ │ ├── Alby.tsx │ │ │ │ │ ├── LNDHub.tsx │ │ │ │ │ ├── NWC.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── routes.tsx │ │ │ │ │ └── utils.ts │ │ │ │ ├── subscribe/ │ │ │ │ │ ├── ManageSubscription.tsx │ │ │ │ │ ├── RenewSub.tsx │ │ │ │ │ ├── SubscriptionCard.tsx │ │ │ │ │ ├── index.css │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── utils.tsx │ │ │ │ └── wallet/ │ │ │ │ ├── index.tsx │ │ │ │ ├── price-chart.tsx │ │ │ │ ├── receive.tsx │ │ │ │ └── send.tsx │ │ │ ├── State/ │ │ │ │ └── NoteCreator.ts │ │ │ ├── Utils/ │ │ │ │ ├── Const.ts │ │ │ │ ├── Login/ │ │ │ │ │ ├── Functions.ts │ │ │ │ │ ├── LoginSession.ts │ │ │ │ │ ├── MultiAccountStore.ts │ │ │ │ │ ├── Nip7OsSigner.ts │ │ │ │ │ ├── Preferences.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── Nip05/ │ │ │ │ │ ├── ServiceProvider.ts │ │ │ │ │ └── SnortServiceProvider.ts │ │ │ │ ├── Notifications.ts │ │ │ │ ├── Number.ts │ │ │ │ ├── Subscription/ │ │ │ │ │ └── index.ts │ │ │ │ ├── Thread/ │ │ │ │ │ ├── ThreadContextWrapper.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Upload/ │ │ │ │ │ ├── blossom.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── ZapPoolController.ts │ │ │ │ ├── emoji-search.ts │ │ │ │ ├── getEventMedia.ts │ │ │ │ ├── index.ts │ │ │ │ ├── nip6.ts │ │ │ │ ├── stream.ts │ │ │ │ └── wasm.ts │ │ │ ├── Wallet/ │ │ │ │ └── index.ts │ │ │ ├── assets/ │ │ │ │ └── fonts/ │ │ │ │ └── inter.css │ │ │ ├── bench.html │ │ │ ├── benchmarks.ts │ │ │ ├── chat/ │ │ │ │ ├── index.ts │ │ │ │ └── nip17.ts │ │ │ ├── hug.json │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ ├── lang.json │ │ │ ├── service-worker.ts │ │ │ ├── setupTests.ts │ │ │ ├── system.ts │ │ │ ├── translations/ │ │ │ │ ├── af_ZA.json │ │ │ │ ├── ar_SA.json │ │ │ │ ├── az_AZ.json │ │ │ │ ├── ca_ES.json │ │ │ │ ├── cs_CZ.json │ │ │ │ ├── da_DK.json │ │ │ │ ├── de_DE.json │ │ │ │ ├── el_GR.json │ │ │ │ ├── en.json │ │ │ │ ├── es_ES.json │ │ │ │ ├── fa_IR.json │ │ │ │ ├── fi_FI.json │ │ │ │ ├── fr_FR.json │ │ │ │ ├── he_IL.json │ │ │ │ ├── hr_HR.json │ │ │ │ ├── hu_HU.json │ │ │ │ ├── id_ID.json │ │ │ │ ├── it_IT.json │ │ │ │ ├── ja_JP.json │ │ │ │ ├── ko_KR.json │ │ │ │ ├── ms_MY.json │ │ │ │ ├── nl_NL.json │ │ │ │ ├── no_NO.json │ │ │ │ ├── pa_IN.json │ │ │ │ ├── pl_PL.json │ │ │ │ ├── pt_BR.json │ │ │ │ ├── pt_PT.json │ │ │ │ ├── ro_RO.json │ │ │ │ ├── ru_RU.json │ │ │ │ ├── sr_SP.json │ │ │ │ ├── sv_SE.json │ │ │ │ ├── sw_KE.json │ │ │ │ ├── ta_IN.json │ │ │ │ ├── th_TH.json │ │ │ │ ├── tr_TR.json │ │ │ │ ├── uk_UA.json │ │ │ │ ├── vi_VN.json │ │ │ │ ├── zh_CN.json │ │ │ │ └── zh_TW.json │ │ │ └── tz.json │ │ ├── tests/ │ │ │ ├── Utils.test.ts │ │ │ └── worker-cached.test.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── bot/ │ │ ├── README.md │ │ ├── example/ │ │ │ └── simple.ts │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── typedoc.json │ ├── shared/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── SortedMap/ │ │ │ │ ├── SortedMap.test.ts │ │ │ │ └── SortedMap.ts │ │ │ ├── cache-store.ts │ │ │ ├── const.ts │ │ │ ├── custom.d.ts │ │ │ ├── external-store.ts │ │ │ ├── feed-cache.ts │ │ │ ├── imgproxy.ts │ │ │ ├── index.ts │ │ │ ├── invoices.ts │ │ │ ├── lnurl.ts │ │ │ ├── tlv.ts │ │ │ ├── utils.ts │ │ │ └── work-queue.ts │ │ ├── tsconfig.json │ │ └── typedoc.json │ ├── system/ │ │ ├── .npmignore │ │ ├── AUDIT.md │ │ ├── README.md │ │ ├── examples/ │ │ │ └── simple.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── background-loader.ts │ │ │ ├── cache/ │ │ │ │ ├── index.ts │ │ │ │ ├── user-follows-lists.ts │ │ │ │ ├── user-metadata.ts │ │ │ │ └── user-relays.ts │ │ │ ├── cache-relay.ts │ │ │ ├── connection-cache-relay.ts │ │ │ ├── connection-pool.ts │ │ │ ├── connection-stats.ts │ │ │ ├── connection.ts │ │ │ ├── const.ts │ │ │ ├── encryption/ │ │ │ │ ├── index.ts │ │ │ │ ├── nip44.ts │ │ │ │ └── pin-encrypted.ts │ │ │ ├── event-builder.ts │ │ │ ├── event-ext.ts │ │ │ ├── event-kind.ts │ │ │ ├── event-publisher.ts │ │ │ ├── filter-cache-layer.ts │ │ │ ├── impl/ │ │ │ │ ├── nip10.ts │ │ │ │ ├── nip11.ts │ │ │ │ ├── nip18.ts │ │ │ │ ├── nip22.ts │ │ │ │ ├── nip25.ts │ │ │ │ ├── nip4.ts │ │ │ │ ├── nip44.ts │ │ │ │ ├── nip46.ts │ │ │ │ ├── nip55.ts │ │ │ │ ├── nip57.ts │ │ │ │ ├── nip7.ts │ │ │ │ ├── nip90.ts │ │ │ │ ├── nip92.ts │ │ │ │ └── nip94.ts │ │ │ ├── index.ts │ │ │ ├── negentropy/ │ │ │ │ ├── accumulator.ts │ │ │ │ ├── negentropy-flow.ts │ │ │ │ ├── negentropy.ts │ │ │ │ ├── utils.ts │ │ │ │ ├── vector-storage.ts │ │ │ │ └── wrapped-buffer.ts │ │ │ ├── nips.ts │ │ │ ├── nostr-link.ts │ │ │ ├── nostr-system.ts │ │ │ ├── nostr.ts │ │ │ ├── note-collection.ts │ │ │ ├── outbox/ │ │ │ │ ├── index.ts │ │ │ │ ├── outbox-model.ts │ │ │ │ └── relay-loader.ts │ │ │ ├── pow-util.ts │ │ │ ├── pow-worker.ts │ │ │ ├── pow.ts │ │ │ ├── profile-cache.ts │ │ │ ├── query-manager.ts │ │ │ ├── query-optimizer/ │ │ │ │ ├── index.ts │ │ │ │ ├── request-expander.ts │ │ │ │ ├── request-merger.ts │ │ │ │ └── request-splitter.ts │ │ │ ├── query.ts │ │ │ ├── relays.ts │ │ │ ├── request-builder.ts │ │ │ ├── request-matcher.ts │ │ │ ├── request-router.ts │ │ │ ├── request-trim.ts │ │ │ ├── signer.ts │ │ │ ├── sync/ │ │ │ │ ├── diff-sync.ts │ │ │ │ ├── index.ts │ │ │ │ ├── json-in-event-sync.ts │ │ │ │ ├── range-sync.ts │ │ │ │ └── safe-sync.ts │ │ │ ├── system-base.ts │ │ │ ├── system.ts │ │ │ ├── text.ts │ │ │ ├── trace-timeline.ts │ │ │ ├── user-state.ts │ │ │ └── utils.ts │ │ ├── tests/ │ │ │ ├── background-loader.test.ts │ │ │ ├── event-ext.test.ts │ │ │ ├── feed-cache-subscribe.test.ts │ │ │ ├── negentropy.test.ts │ │ │ ├── nip10.test.ts │ │ │ ├── nip18.test.ts │ │ │ ├── node.ts │ │ │ ├── nostr-link.test.ts │ │ │ ├── note-collection-comprehensive.test.ts │ │ │ ├── note-collection.test.ts │ │ │ ├── pin-encrypted.test.ts │ │ │ ├── query-comprehensive.test.ts │ │ │ ├── query-manager-comprehensive.test.ts │ │ │ ├── query-manager-race.test.ts │ │ │ ├── query-system-edge-cases.test.ts │ │ │ ├── request-builder.test.ts │ │ │ ├── request-expander.test.ts │ │ │ ├── request-matcher.test.ts │ │ │ ├── request-merger.test.ts │ │ │ ├── request-splitter.test.ts │ │ │ ├── setupTests.ts │ │ │ ├── text.test.ts │ │ │ └── utils.test.ts │ │ ├── tsconfig.json │ │ └── typedoc.json │ ├── system-react/ │ │ ├── README.md │ │ ├── example/ │ │ │ └── example.tsx │ │ ├── package.json │ │ ├── src/ │ │ │ ├── TraceTimeline/ │ │ │ │ ├── TraceStatsView.tsx │ │ │ │ ├── TraceTimeline.css │ │ │ │ ├── TraceTimelineDetailPopup.tsx │ │ │ │ ├── TraceTimelineOverlay.tsx │ │ │ │ └── TraceTimelineView.tsx │ │ │ ├── context.tsx │ │ │ ├── index.ts │ │ │ ├── useCached.ts │ │ │ ├── useEventFeed.ts │ │ │ ├── useEventReactions.tsx │ │ │ ├── useReactions.ts │ │ │ ├── useRequestBuilder.tsx │ │ │ ├── useSystemState.tsx │ │ │ ├── useUserProfile.ts │ │ │ └── useUserSearch.tsx │ │ ├── tsconfig.json │ │ └── typedoc.json │ ├── system-svelte/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── request-builder.ts │ │ ├── tsconfig.json │ │ └── typedoc.json │ ├── system-wasm/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── benches/ │ │ │ └── basic.rs │ │ ├── package.json │ │ ├── pkg/ │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── system_wasm.d.ts │ │ │ ├── system_wasm.js │ │ │ ├── system_wasm_bg.js │ │ │ ├── system_wasm_bg.wasm │ │ │ └── system_wasm_bg.wasm.d.ts │ │ ├── src/ │ │ │ ├── diff.rs │ │ │ ├── filter.rs │ │ │ ├── lib.rs │ │ │ ├── merge.rs │ │ │ ├── pow.rs │ │ │ └── verify.rs │ │ ├── system-query.iml │ │ └── typedoc.json │ ├── wallet/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── AlbyWallet.ts │ │ │ ├── LNDHub.ts │ │ │ ├── NostrWalletConnect.ts │ │ │ ├── WebLN.ts │ │ │ ├── custom.d.ts │ │ │ ├── index.ts │ │ │ └── zapper.ts │ │ ├── tsconfig.json │ │ └── typedoc.json │ └── worker-relay/ │ ├── README.md │ ├── example/ │ │ └── basic.ts │ ├── package.json │ ├── src/ │ │ ├── custom.d.ts │ │ ├── debug.ts │ │ ├── forYouFeed.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── memory-relay.ts │ │ ├── queue.ts │ │ ├── sqlite/ │ │ │ ├── fixers.ts │ │ │ ├── migrations.ts │ │ │ └── sqlite-relay.ts │ │ ├── types.ts │ │ └── worker.ts │ ├── tsconfig.json │ └── typedoc.json ├── src-tauri/ │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ ├── capabilities/ │ │ └── migrated.json │ ├── gen/ │ │ └── schemas/ │ │ ├── acl-manifests.json │ │ ├── capabilities.json │ │ ├── desktop-schema.json │ │ └── linux-schema.json │ ├── src/ │ │ └── main.rs │ └── tauri.conf.json └── zapstore.yaml
Showing preview only (205K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2439 symbols across 507 files)
FILE: functions/_middleware.ts
type Env (line 1) | type Env = {}
constant HOST (line 3) | const HOST = "snort.social";
FILE: packages/app/src/Cache/CommunityLeadersStore.tsx
class CommunityLeadersStore (line 3) | class CommunityLeadersStore extends ExternalStore<Array<string>> {
method setLeaders (line 6) | setLeaders(arr: Array<string>) {
method takeSnapshot (line 11) | takeSnapshot(): string[] {
FILE: packages/app/src/Cache/GiftWrapCache.ts
type UnwrappedGift (line 8) | interface UnwrappedGift {
function getCachedDecryptedContent (line 18) | function getCachedDecryptedContent(id: string): string | undefined {
function setCachedDecryptedContent (line 22) | function setCachedDecryptedContent(id: string, content: string) {
constant NIP44_MIN_PAYLOAD_LEN (line 26) | const NIP44_MIN_PAYLOAD_LEN = 132
function isValidNip44Content (line 28) | function isValidNip44Content(content: string): boolean {
class GiftWrapCache (line 32) | class GiftWrapCache extends RefreshFeedCache<UnwrappedGift> {
method constructor (line 36) | constructor() {
method setRelay (line 40) | setRelay(relay: CacheRelay) {
method key (line 44) | key(of: UnwrappedGift): string {
method buildSub (line 48) | buildSub(): void {}
method takeSnapshot (line 50) | takeSnapshot(): Array<UnwrappedGift> {
method preload (line 54) | override async preload(): Promise<void> {
method onEvent (line 58) | override async onEvent(evs: Readonly<Array<TaggedNostrEvent>>, _: stri...
method loadPersistedAndDecrypt (line 124) | async loadPersistedAndDecrypt(pub: EventPublisher): Promise<void> {
method clear (line 140) | override async clear(): Promise<void> {
method search (line 153) | search(): Promise<TWithCreated<UnwrappedGift>[]> {
FILE: packages/app/src/Cache/ProfileWorkerCache.ts
class ProfileCacheRelayWorker (line 4) | class ProfileCacheRelayWorker extends WorkerBaseCache<CachedMetadata> {
method constructor (line 5) | constructor(relay: CacheRelay) {
method name (line 9) | name(): string {
method maxSize (line 13) | maxSize(): number {
method mapper (line 17) | mapper(ev: NostrEvent): CachedMetadata | undefined {
method preload (line 21) | override async preload(follows?: Array<string>) {
FILE: packages/app/src/Cache/RefreshFeedCache.ts
type TWithCreated (line 6) | type TWithCreated<T> = (T | Readonly<T>) & { created_at: number }
method newest (line 15) | protected newest(filter?: (e: TWithCreated<T>) => boolean) {
method preload (line 25) | override async preload(): Promise<void> {
FILE: packages/app/src/Cache/RelaysWorkerCache.ts
class RelaysWorkerCache (line 5) | class RelaysWorkerCache extends WorkerBaseCache<UsersRelays> {
method constructor (line 6) | constructor(relay: CacheRelay) {
method name (line 10) | name(): string {
method maxSize (line 14) | maxSize(): number {
method mapper (line 18) | mapper(ev: NostrEvent): UsersRelays | undefined {
method preload (line 30) | override async preload(follows?: Array<string>) {
FILE: packages/app/src/Cache/UserFollowsWorker.ts
class UserFollowsWorker (line 5) | class UserFollowsWorker extends WorkerBaseCache<UsersFollows> {
method constructor (line 6) | constructor(relay: CacheRelay) {
method name (line 10) | name(): string {
method maxSize (line 14) | maxSize(): number {
method mapper (line 18) | mapper(ev: NostrEvent): UsersFollows | undefined {
method preload (line 29) | override async preload(follows?: Array<string>) {
FILE: packages/app/src/Cache/index.ts
function tryUseCacheRelay (line 30) | async function tryUseCacheRelay(url: string) {
function tryUseLocalRelay (line 41) | async function tryUseLocalRelay() {
function initRelayWorker (line 49) | async function initRelayWorker() {
function preload (line 87) | async function preload(follows?: Array<string>) {
FILE: packages/app/src/Cache/worker-cached.ts
method constructor (line 21) | constructor(
method clear (line 29) | async clear() {
method key (line 34) | key(of: T): string {
method preload (line 45) | async preload() {
method preloadTable (line 52) | protected async preloadTable(id: string, f: ReqFilter) {
method search (line 66) | async search(q: string) {
method keysOnTable (line 78) | keysOnTable(): string[] {
method getFromCache (line 82) | getFromCache(key?: string | undefined) {
method discover (line 88) | discover(ev: NostrEvent) {
method get (line 92) | async get(key?: string | undefined): Promise<T | undefined> {
method bulkGet (line 101) | async bulkGet(keys: string[]) {
method setInternal (line 128) | private setInternal(obj: T) {
method set (line 139) | async set(obj: T) {
method bulkSet (line 146) | async bulkSet(obj: T[] | readonly T[]) {
method update (line 154) | async update(obj: T): Promise<"new" | "refresh" | "updated" | "no_change...
method subscribe (line 176) | subscribe(key: string, cb: () => void): () => void {
method #notifyKeyListeners (line 194) | #notifyKeyListeners(key: string) {
method buffer (line 203) | async buffer(keys: string[]): Promise<string[]> {
method snapshot (line 209) | snapshot(): T[] {
FILE: packages/app/src/Components/AskSnort/AskSnortInput.tsx
function AskSnortInput (line 9) | function AskSnortInput() {
FILE: packages/app/src/Components/Button/AsyncButton.tsx
type AsyncButtonProps (line 7) | interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButton...
FILE: packages/app/src/Components/Button/AsyncIcon.tsx
type AsyncIconProps (line 5) | type AsyncIconProps = React.HTMLProps<HTMLDivElement> & {
function AsyncIcon (line 11) | function AsyncIcon(props: AsyncIconProps) {
FILE: packages/app/src/Components/Button/BackButton.tsx
type BackButtonProps (line 6) | interface BackButtonProps {
function BackButton (line 11) | function BackButton({ text, onClick }: BackButtonProps) {
FILE: packages/app/src/Components/Button/CloseButton.tsx
function CloseButton (line 5) | function CloseButton({ onClick, className }: { onClick?: () => void; cla...
FILE: packages/app/src/Components/Button/IconButton.tsx
type IconButtonProps (line 6) | interface IconButtonProps {
FILE: packages/app/src/Components/Button/LogoutButton.tsx
function LogoutButton (line 9) | function LogoutButton() {
FILE: packages/app/src/Components/Button/NavLink.tsx
function NavLink (line 3) | function NavLink(props: NavLinkProps) {
FILE: packages/app/src/Components/Collapsed.tsx
type CollapsedProps (line 6) | interface CollapsedProps {
type CollapsedIconProps (line 23) | interface CollapsedIconProps {
type CollapsedSectionProps (line 28) | interface CollapsedSectionProps {
FILE: packages/app/src/Components/CommunityLeaders/Award.tsx
function AwardIcon (line 1) | function AwardIcon({ size }: { size?: number }) {
FILE: packages/app/src/Components/CommunityLeaders/LeaderBadge.tsx
function LeaderBadge (line 9) | function LeaderBadge() {
FILE: packages/app/src/Components/Copy/Copy.tsx
type CopyProps (line 6) | interface CopyProps {
function Copy (line 13) | function Copy({ text, maxSize = 32, className, showText, mask }: CopyPro...
FILE: packages/app/src/Components/DvmSelector.tsx
type DvmSelectorProps (line 10) | interface DvmSelectorProps {
function DvmSelector (line 17) | function DvmSelector({ kind, onClose, onSelect, currentProvider }: DvmSe...
FILE: packages/app/src/Components/Embed/BlossomBlob.tsx
type BlossomLink (line 13) | interface BlossomLink {
function parseBlossomLink (line 21) | function parseBlossomLink(link: string) {
function BlossomBlob (line 44) | function BlossomBlob({ creator, link }: { creator: string; link: string ...
FILE: packages/app/src/Components/Embed/CashuNuts.tsx
function CashuNuts (line 13) | function CashuNuts({ token }: { token: string }) {
FILE: packages/app/src/Components/Embed/GenericPlayer.tsx
function GenericPlayer (line 6) | function GenericPlayer({ url, poster }: { url: string; poster: string }) {
FILE: packages/app/src/Components/Embed/HyperText.tsx
type HypeTextProps (line 23) | interface HypeTextProps {
function HyperText (line 29) | function HyperText({ link, showLinkPreview, children }: HypeTextProps) {
FILE: packages/app/src/Components/Embed/Invoice.tsx
type InvoiceProps (line 12) | interface InvoiceProps {
function Invoice (line 16) | function Invoice(props: InvoiceProps) {
FILE: packages/app/src/Components/Embed/LinkPreview.tsx
function fetchUrlPreviewInfo (line 11) | async function fetchUrlPreviewInfo(url: string) {
function previewElement (line 58) | function previewElement() {
FILE: packages/app/src/Components/Embed/MagnetLink.tsx
type MagnetLinkProps (line 6) | interface MagnetLinkProps {
FILE: packages/app/src/Components/Embed/MediaElement.tsx
type MediaElementProps (line 11) | interface MediaElementProps {
type AudioElementProps (line 21) | interface AudioElementProps {
type VideoElementProps (line 25) | interface VideoElementProps {
type ImageElementProps (line 30) | type ImageElementProps = ProxyImgProps & {
function MediaElement (line 118) | function MediaElement(props: MediaElementProps) {
FILE: packages/app/src/Components/Embed/Mention.tsx
function Mention (line 10) | function Mention({
FILE: packages/app/src/Components/Embed/NostrLink.tsx
function NostrLink (line 9) | function NostrLink({ link, depth, className }: { link: string; depth?: n...
FILE: packages/app/src/Components/Embed/PubkeyList.tsx
function PubkeyList (line 16) | function PubkeyList({ ev, className }: { ev: NostrEvent; className?: str...
FILE: packages/app/src/Components/Embed/TidalEmbed.tsx
function oembedLookup (line 8) | async function oembedLookup(link: string) {
FILE: packages/app/src/Components/Embed/UrlStatusCheck.tsx
type UrlStatusCheckProps (line 5) | interface UrlStatusCheckProps {
function UrlStatusCheck (line 9) | function UrlStatusCheck({ url }: UrlStatusCheckProps) {
FILE: packages/app/src/Components/Embed/YoutubeEmbed.tsx
function YoutubeEmbed (line 3) | function YoutubeEmbed({ link }: { link: string }) {
FILE: packages/app/src/Components/Embed/ZapstrEmbed.tsx
function ZapstrEmbed (line 10) | function ZapstrEmbed({ ev }: { ev: NostrEvent }) {
FILE: packages/app/src/Components/ErrorBoundary.tsx
type ErrorBoundaryState (line 5) | interface ErrorBoundaryState {
type ErrorBoundaryProps (line 10) | interface ErrorBoundaryProps {
class ErrorBoundary (line 14) | class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBou...
method constructor (line 15) | constructor(props: ErrorBoundaryProps) {
method getDerivedStateFromError (line 20) | static getDerivedStateFromError(error: Error): ErrorBoundaryState {
method componentDidCatch (line 24) | componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
method render (line 29) | render() {
FILE: packages/app/src/Components/ErrorOrOffline.tsx
function ErrorOrOffline (line 8) | function ErrorOrOffline({
FILE: packages/app/src/Components/Event/Application.tsx
function ApplicationHandler (line 10) | function ApplicationHandler({ ev }: { ev: TaggedNostrEvent }) {
FILE: packages/app/src/Components/Event/Create/NoteCreator.tsx
function NoteCreator (line 64) | function NoteCreator() {
FILE: packages/app/src/Components/Event/Create/OkResponseRow.tsx
function OkResponseRow (line 13) | function OkResponseRow({ rsp, close }: { rsp: OkResponse; close: () => v...
FILE: packages/app/src/Components/Event/Create/util.ts
function sendEventToRelays (line 4) | async function sendEventToRelays(
FILE: packages/app/src/Components/Event/DVMJobFeedback.tsx
type DVMJobFeedbackProps (line 6) | interface DVMJobFeedbackProps {
function DVMJobFeedback (line 10) | function DVMJobFeedback({ ev }: DVMJobFeedbackProps) {
FILE: packages/app/src/Components/Event/EventComponent.tsx
type NotePropsOptions (line 16) | interface NotePropsOptions {
type NoteProps (line 43) | interface NoteProps {
function EventComponent (line 61) | function EventComponent(props: NoteProps) {
FILE: packages/app/src/Components/Event/FileUpload.tsx
function FileUploadProgress (line 4) | function FileUploadProgress({ progress }: { progress: Array<UploadProgre...
FILE: packages/app/src/Components/Event/LoadMore.tsx
type ShowMoreProps (line 8) | interface ShowMoreProps {
function AutoLoadMore (line 24) | function AutoLoadMore({ text, onClick, className }: ShowMoreProps) {
FILE: packages/app/src/Components/Event/LongFormText.tsx
type LongFormTextProps (line 17) | interface LongFormTextProps {
constant TEXT_TRUNCATE_LENGTH (line 24) | const TEXT_TRUNCATE_LENGTH = 400
function LongFormText (line 26) | function LongFormText(props: LongFormTextProps) {
FILE: packages/app/src/Components/Event/Markdown.tsx
type MarkdownProps (line 10) | interface MarkdownProps {
function renderToken (line 16) | function renderToken(t: Token | Footnotes | Footnote | FootnoteRef, tags...
FILE: packages/app/src/Components/Event/NostrFileHeader.tsx
function NostrFileHeader (line 10) | function NostrFileHeader({ link }: { link: NostrLink }) {
function NostrFileElement (line 17) | function NostrFileElement({ ev }: { ev: NostrEvent }) {
FILE: packages/app/src/Components/Event/Note/ClientFingerprinting.tsx
type CheckResult (line 5) | interface CheckResult {
type ClientEvaluation (line 12) | interface ClientEvaluation {
type FingerprintResult (line 17) | interface FingerprintResult {
type ClientResult (line 24) | interface ClientResult {
method getResult (line 37) | getResult(ev: NostrEvent): CheckResult {
class AltTagCheck (line 47) | class AltTagCheck extends FingerprintCheck {
method evaluate (line 51) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 56) | getDescription(): ReactNode {
class PTagNicknameCheck (line 61) | class PTagNicknameCheck extends FingerprintCheck {
method evaluate (line 65) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 69) | getDescription(): ReactNode {
class DoubleNewlinesBeforeImagesCheck (line 74) | class DoubleNewlinesBeforeImagesCheck extends FingerprintCheck {
method evaluate (line 78) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 83) | getDescription(): ReactNode {
class RootTagLastCheck (line 88) | class RootTagLastCheck extends FingerprintCheck {
method evaluate (line 92) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 98) | getDescription(): ReactNode {
class HasIMetaNoRTagsCheck (line 103) | class HasIMetaNoRTagsCheck extends FingerprintCheck {
method evaluate (line 107) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 114) | getDescription(): ReactNode {
class SingleNewlineBeforeImagesCheck (line 119) | class SingleNewlineBeforeImagesCheck extends FingerprintCheck {
method evaluate (line 123) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 129) | getDescription(): ReactNode {
class NoIMetaWithImagesCheck (line 134) | class NoIMetaWithImagesCheck extends FingerprintCheck {
method evaluate (line 138) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 144) | getDescription(): ReactNode {
class NoRTagsWithImagesCheck (line 149) | class NoRTagsWithImagesCheck extends FingerprintCheck {
method evaluate (line 153) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 159) | getDescription(): ReactNode {
class HasRTagsAndIMetaCheck (line 164) | class HasRTagsAndIMetaCheck extends FingerprintCheck {
method evaluate (line 168) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 175) | getDescription(): ReactNode {
class NewlinesBeforeQuotesCheck (line 180) | class NewlinesBeforeQuotesCheck extends FingerprintCheck {
method evaluate (line 184) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 188) | getDescription(): ReactNode {
class EndsDoubleNewlineCheck (line 193) | class EndsDoubleNewlineCheck extends FingerprintCheck {
method evaluate (line 197) | evaluate(ev: NostrEvent): boolean {
method getDescription (line 201) | getDescription(): ReactNode {
class ClientFingerprint (line 206) | class ClientFingerprint {
method constructor (line 207) | constructor(
method evaluate (line 212) | evaluate(ev: NostrEvent): ClientEvaluation {
class FingerprintEngine (line 226) | class FingerprintEngine {
method constructor (line 230) | constructor() {
method fingerprint (line 249) | fingerprint(ev: NostrEvent): FingerprintResult | undefined {
method getMinScore (line 275) | getMinScore(): number {
FILE: packages/app/src/Components/Event/Note/ClientTag.tsx
function ClientTag (line 9) | function ClientTag({ ev }: { ev: TaggedNostrEvent }) {
function ViaTag (line 21) | function ViaTag({ info }: { info: ClientInfo }) {
function FingerprintClientTag (line 47) | function FingerprintClientTag({ info }: { info: ClientInfo }) {
type ClientInfo (line 118) | interface ClientInfo {
function getClientInfo (line 124) | function getClientInfo(ev: NostrEvent): ClientInfo | undefined {
FILE: packages/app/src/Components/Event/Note/Note.tsx
function Note (line 35) | function Note(props: NoteProps) {
function useGoToEvent (line 106) | function useGoToEvent(props: NoteProps, options: NotePropsOptions) {
FILE: packages/app/src/Components/Event/Note/NoteAppHandler.tsx
function NoteAppHandler (line 11) | function NoteAppHandler({ ev }: { ev: TaggedNostrEvent | undefined }) {
FILE: packages/app/src/Components/Event/Note/NoteContent.tsx
type NoteContentProps (line 12) | interface NoteContentProps {
function NoteContent (line 21) | function NoteContent({ props, options, goToEvent, setSeenAtRef, waitUnti...
FILE: packages/app/src/Components/Event/Note/NoteContext.tsx
type NoteContextType (line 13) | interface NoteContextType {
type NoteProviderProps (line 30) | interface NoteProviderProps {
function NoteProvider (line 35) | function NoteProvider({ ev, children }: NoteProviderProps) {
function useNoteContext (line 127) | function useNoteContext() {
FILE: packages/app/src/Components/Event/Note/NoteContextMenu.tsx
function NoteContextMenu (line 15) | function NoteContextMenu() {
FILE: packages/app/src/Components/Event/Note/NoteFooter/FooterZapButton.tsx
type ZapIconProps (line 21) | interface ZapIconProps {
FILE: packages/app/src/Components/Event/Note/NoteFooter/NoteFooter.tsx
type NoteFooterProps (line 15) | interface NoteFooterProps {
function NoteFooter (line 20) | function NoteFooter(props: NoteFooterProps) {
FILE: packages/app/src/Components/Event/Note/NoteHeader.tsx
function NoteHeader (line 16) | function NoteHeader(props: { options: NotePropsOptions; context?: React....
FILE: packages/app/src/Components/Event/Note/NoteQuote.tsx
function NoteQuote (line 13) | function NoteQuote({
FILE: packages/app/src/Components/Event/Note/NoteText.tsx
constant TEXT_TRUNCATE_LENGTH (line 11) | const TEXT_TRUNCATE_LENGTH = 400
FILE: packages/app/src/Components/Event/Note/NoteTime.tsx
type NoteTimeProps (line 5) | interface NoteTimeProps {
FILE: packages/app/src/Components/Event/Note/ReactionsModal.tsx
type ReactionsModalProps (line 15) | interface ReactionsModalProps {
FILE: packages/app/src/Components/Event/Note/ReplyTag.tsx
function ReplyTag (line 8) | function ReplyTag({ ev }: { ev: TaggedNostrEvent }) {
FILE: packages/app/src/Components/Event/Note/TranslationInfo.tsx
function TranslationInfo (line 5) | function TranslationInfo() {
FILE: packages/app/src/Components/Event/Note/types.tsx
type NoteTranslation (line 1) | interface NoteTranslation {
FILE: packages/app/src/Components/Event/NoteReaction.tsx
type NoteReactionProps (line 12) | interface NoteReactionProps {
function NoteReaction (line 17) | function NoteReaction(props: NoteReactionProps) {
FILE: packages/app/src/Components/Event/Poll.tsx
type PollProps (line 16) | interface PollProps {
type PollTally (line 21) | type PollTally = "zaps" | "pubkeys"
function Poll (line 23) | function Poll(props: PollProps) {
FILE: packages/app/src/Components/Event/Reveal.tsx
type RevealProps (line 5) | interface RevealProps {
function Reveal (line 10) | function Reveal(props: RevealProps) {
FILE: packages/app/src/Components/Event/RevealMedia.tsx
type RevealMediaProps (line 12) | type RevealMediaProps = Omit<MediaElementProps, "mime"> & {
function RevealMedia (line 16) | function RevealMedia(props: RevealMediaProps) {
FILE: packages/app/src/Components/Event/Thread/Subthread.tsx
type SubthreadProps (line 6) | interface SubthreadProps {
FILE: packages/app/src/Components/Event/Thread/Thread.tsx
type ThreadProps (line 16) | interface ThreadProps {
function ThreadElement (line 21) | function ThreadElement(props: ThreadProps) {
function ThreadInner (line 35) | function ThreadInner({ thread, ...props }: ThreadProps & { thread: Threa...
function ThreadDebug (line 149) | function ThreadDebug() {
FILE: packages/app/src/Components/Event/Thread/ThreadRoute.tsx
function ThreadRoute (line 8) | function ThreadRoute({ id }: { id?: string | NostrLink }) {
FILE: packages/app/src/Components/Event/Thread/util.ts
function getReplies (line 4) | function getReplies(
FILE: packages/app/src/Components/Event/ZapGoal.tsx
function ZapGoal (line 13) | function ZapGoal({ ev }: { ev: NostrEvent }) {
FILE: packages/app/src/Components/Event/ZapsSummary.tsx
type ZapsSummaryProps (line 8) | interface ZapsSummaryProps {
FILE: packages/app/src/Components/Feed/ImageGridItem.tsx
type ImageGridItemProps (line 10) | interface ImageGridItemProps {
FILE: packages/app/src/Components/Feed/LoadMore.tsx
function LoadMore (line 7) | function LoadMore({
FILE: packages/app/src/Components/Feed/RootTabItems.tsx
function rootTabItems (line 7) | function rootTabItems(base: string, pubKey: string | undefined, tags: Ar...
FILE: packages/app/src/Components/Feed/RootTabs.tsx
function RootTabs (line 13) | function RootTabs({ base = "/" }: { base: string }) {
FILE: packages/app/src/Components/Feed/Timeline.tsx
type TimelineProps (line 11) | interface TimelineProps {
function onShowLatest (line 65) | function onShowLatest(scrollToTop = false) {
FILE: packages/app/src/Components/Feed/TimelineChunk.tsx
type TimelineChunkProps (line 8) | interface TimelineChunkProps {
function TimelineChunk (line 20) | function TimelineChunk(props: TimelineChunkProps) {
FILE: packages/app/src/Components/Feed/TimelineFollows.tsx
type TimelineFollowsProps (line 13) | interface TimelineFollowsProps {
FILE: packages/app/src/Components/Feed/TimelineFragment.tsx
type TimelineFragment (line 6) | interface TimelineFragment {
type TimelineFragProps (line 12) | interface TimelineFragProps {
function TimelineFragment (line 24) | function TimelineFragment(props: TimelineFragProps) {
FILE: packages/app/src/Components/Feed/TimelineRenderer.tsx
type TimelineRendererProps (line 13) | interface TimelineRendererProps {
function TimelineRenderer (line 27) | function TimelineRenderer(props: TimelineRendererProps) {
FILE: packages/app/src/Components/Feed/UsersFeed.tsx
function UsersFeed (line 9) | function UsersFeed({ keyword, sortPopular = true }: { keyword: string; s...
FILE: packages/app/src/Components/Icons/Alby.tsx
function AlbyIcon (line 1) | function AlbyIcon(props: { size?: number }) {
FILE: packages/app/src/Components/Icons/Cashu.tsx
function CashuIcon (line 1) | function CashuIcon(props: { size?: number }) {
FILE: packages/app/src/Components/Icons/ECash.tsx
function ECashIcon (line 1) | function ECashIcon(props: { width?: number; height?: number }) {
FILE: packages/app/src/Components/Icons/Icon.tsx
type IconProps (line 5) | type IconProps = {
FILE: packages/app/src/Components/Icons/NWC.tsx
function NWCIcon (line 1) | function NWCIcon(props: { width?: number; height?: number }) {
FILE: packages/app/src/Components/Icons/Nostrich.tsx
function NostrIcon (line 1) | function NostrIcon(props: { width?: number; height?: number }) {
FILE: packages/app/src/Components/Icons/Toggle.tsx
function ToggleSwitch (line 3) | function ToggleSwitch(props: Omit<IconProps, "name">) {
FILE: packages/app/src/Components/IntlProvider/IntlProvider.tsx
function IntlProvider (line 84) | function IntlProvider({ children }: { children: ReactNode }) {
FILE: packages/app/src/Components/IntlProvider/langStore.tsx
class LangStore (line 3) | class LangStore extends ExternalStore<string | null> {
method setLang (line 4) | setLang(s: string) {
method takeSnapshot (line 9) | takeSnapshot() {
FILE: packages/app/src/Components/IntlProvider/useLocale.tsx
function useLocale (line 7) | function useLocale() {
FILE: packages/app/src/Components/Invite.tsx
function close (line 26) | function close() {
FILE: packages/app/src/Components/LiveStream/LiveEvent.tsx
function LiveEvent (line 14) | function LiveEvent({ ev }: { ev: TaggedNostrEvent }) {
function LiveStreamEvent (line 32) | function LiveStreamEvent({ ev }: { ev: TaggedNostrEvent }) {
FILE: packages/app/src/Components/LiveStream/LiveStreams.tsx
function LiveStreams (line 16) | function LiveStreams() {
function LiveStreamEvent (line 38) | function LiveStreamEvent({ ev, className }: { ev: NostrEvent; className?...
function AudioRoom (line 83) | function AudioRoom({ ev, className }: { ev: NostrEvent; className?: stri...
FILE: packages/app/src/Components/LiveStream/VU.tsx
function VuBar (line 3) | function VuBar({
FILE: packages/app/src/Components/LiveStream/livekit.tsx
type RoomTab (line 38) | enum RoomTab {
function LiveKitRoom (line 43) | function LiveKitRoom({ ev, canJoin }: { ev: TaggedNostrEvent; canJoin?: ...
function RoomHeader (line 126) | function RoomHeader({ ev }: { ev: TaggedNostrEvent }) {
function RoomBody (line 145) | function RoomBody({ ev, tab, onSelectTab }: { ev: TaggedNostrEvent; tab:...
function MyControls (line 196) | function MyControls() {
function RoomChat (line 237) | function RoomChat({ ev }: { ev: TaggedNostrEvent }) {
function ChatMessage (line 260) | function ChatMessage({ ev }: { ev: TaggedNostrEvent }) {
function WriteChatMessage (line 269) | function WriteChatMessage({ ev }: { ev: TaggedNostrEvent }) {
function LiveKitUser (line 301) | function LiveKitUser({ p }: { p: RemoteParticipant | LocalParticipant }) {
FILE: packages/app/src/Components/LiveStream/nests-participants.tsx
function NestsParticipants (line 8) | function NestsParticipants({ ev }: { ev: TaggedNostrEvent }) {
FILE: packages/app/src/Components/Modal/Modal.tsx
type ModalProps (line 5) | interface ModalProps {
function Modal (line 42) | function Modal(props: ModalProps) {
FILE: packages/app/src/Components/Nip5Service.tsx
type Nip05ServiceProps (line 28) | type Nip05ServiceProps = {
function Nip5Service (line 40) | function Nip5Service(props: Nip05ServiceProps) {
FILE: packages/app/src/Components/Offline.tsx
function Offline (line 8) | function Offline({ onRetry, className }: { onRetry?: () => void | Promis...
FILE: packages/app/src/Components/PageSpinner.tsx
function PageSpinner (line 3) | function PageSpinner() {
FILE: packages/app/src/Components/PinPrompt/PinPrompt.tsx
function PinPrompt (line 15) | function PinPrompt({
function LoginUnlock (line 98) | function LoginUnlock() {
FILE: packages/app/src/Components/Progress/Progress.tsx
function Progress (line 4) | function Progress({ value, status }: { value: number; status?: ReactNode...
FILE: packages/app/src/Components/ProxyImg.tsx
type ProxyImgProps (line 8) | type ProxyImgProps = HTMLProps<HTMLImageElement> & {
FILE: packages/app/src/Components/QrCode.tsx
type QrCodeProps (line 4) | interface QrCodeProps {
function QrCode (line 13) | function QrCode(props: QrCodeProps) {
FILE: packages/app/src/Components/ReBroadcaster.tsx
function ReBroadcaster (line 10) | function ReBroadcaster({ onClose, ev }: { onClose: () => void; ev: Tagge...
FILE: packages/app/src/Components/Relay/Relay.tsx
type RelayProps (line 12) | interface RelayProps {
function Relay (line 16) | function Relay(props: RelayProps) {
FILE: packages/app/src/Components/Relay/name.tsx
function RelayName (line 6) | function RelayName({ url }: { url: string }) {
FILE: packages/app/src/Components/Relay/paid.tsx
function RelayPaymentLabel (line 5) | function RelayPaymentLabel({ info }: { info: RelayInfoDocument }) {
FILE: packages/app/src/Components/Relay/permissions.tsx
function RelayPermissions (line 6) | function RelayPermissions({ conn }: { conn: ConnectionType }) {
FILE: packages/app/src/Components/Relay/software.tsx
function RelaySoftware (line 3) | function RelaySoftware({ software }: { software: string }) {
FILE: packages/app/src/Components/Relay/status-label.tsx
function RelayStatusLabel (line 5) | function RelayStatusLabel({ conn }: { conn: ConnectionType }) {
FILE: packages/app/src/Components/Relay/uptime-label.tsx
function UptimeLabel (line 4) | function UptimeLabel({ avgPing }: { avgPing: number }) {
FILE: packages/app/src/Components/Relay/uptime.tsx
function RelayUptime (line 11) | function RelayUptime({ url }: { url: string }) {
FILE: packages/app/src/Components/Review.tsx
function ReviewSummary (line 5) | function ReviewSummary({ link }: { link: NostrLink }) {
FILE: packages/app/src/Components/RightWidgets/articles.tsx
function LatestArticlesWidget (line 14) | function LatestArticlesWidget() {
FILE: packages/app/src/Components/RightWidgets/base.tsx
type BaseWidgetProps (line 5) | interface BaseWidgetProps {
function BaseWidget (line 12) | function BaseWidget({ children, title, icon, iconClassName, contextMenu ...
FILE: packages/app/src/Components/RightWidgets/index.tsx
type RightColumnWidget (line 1) | enum RightColumnWidget {
FILE: packages/app/src/Components/RightWidgets/invite-friends.tsx
function InviteFriendsWidget (line 13) | function InviteFriendsWidget() {
FILE: packages/app/src/Components/RightWidgets/mini-stream.tsx
function MiniStreamWidget (line 13) | function MiniStreamWidget() {
FILE: packages/app/src/Components/ScrollToTop.tsx
function ScrollToTop (line 4) | function ScrollToTop() {
FILE: packages/app/src/Components/SearchBox/SearchBox.tsx
constant MAX_RESULTS (line 12) | const MAX_RESULTS = 3
function SearchBox (line 14) | function SearchBox() {
FILE: packages/app/src/Components/Spotlight/SpotlightMedia.tsx
type SpotlightMediaProps (line 7) | interface SpotlightMediaProps {
function SpotlightMedia (line 18) | function SpotlightMedia(props: SpotlightMediaProps) {
FILE: packages/app/src/Components/Spotlight/SpotlightThreadModal.tsx
type SpotlightThreadModalProps (line 9) | interface SpotlightThreadModalProps {
function SpotlightThreadModal (line 19) | function SpotlightThreadModal(props: SpotlightThreadModalProps) {
type SpotlightFromEventProps (line 54) | interface SpotlightFromEventProps {
function SpotlightFromEvent (line 61) | function SpotlightFromEvent({ event, onClose, onNext, onPrev }: Spotligh...
FILE: packages/app/src/Components/Spotlight/context.tsx
type SpotlightContextState (line 5) | interface SpotlightContextState {
function SpotlightContextWrapper (line 13) | function SpotlightContextWrapper({ children }: { children: ReactNode }) {
FILE: packages/app/src/Components/SuggestedProfiles.tsx
function SuggestedProfiles (line 21) | function SuggestedProfiles() {
FILE: packages/app/src/Components/TabSelectors/TabSelectors.tsx
type Tab (line 6) | interface Tab {
type TabsProps (line 12) | interface TabsProps {
type TabElementProps (line 19) | interface TabElementProps extends Omit<TabsProps, "tabs"> {
FILE: packages/app/src/Components/Tasks/BackupKey.tsx
class BackupKeyTask (line 9) | class BackupKeyTask extends BaseUITask {
method check (line 13) | check(_: CachedMetadata, session: LoginSession): boolean {
method render (line 17) | render() {
FILE: packages/app/src/Components/Tasks/DonateTask.tsx
class DonateTask (line 6) | class DonateTask extends BaseUITask {
method check (line 9) | check(): boolean {
method render (line 13) | render() {
FILE: packages/app/src/Components/Tasks/FollowMorePeople.tsx
class FollowMorePeopleTask (line 8) | class FollowMorePeopleTask extends BaseUITask {
method check (line 11) | check(_meta: CachedMetadata, session: LoginSession): boolean {
method render (line 15) | render() {
FILE: packages/app/src/Components/Tasks/Nip5Task.tsx
class Nip5Task (line 7) | class Nip5Task extends BaseUITask {
method check (line 10) | check(user: CachedMetadata): boolean {
method render (line 14) | render() {
FILE: packages/app/src/Components/Tasks/NoticeZapPool.tsx
class NoticeZapPoolDefault (line 7) | class NoticeZapPoolDefault extends BaseUITask {
method check (line 10) | check(): boolean {
method render (line 14) | render() {
FILE: packages/app/src/Components/Tasks/PendingChangesTask.tsx
class PendingChangesTask (line 9) | class PendingChangesTask extends BaseUITask {
method check (line 12) | check(_meta: CachedMetadata, session: LoginSession): boolean {
method render (line 16) | render() {
function PendingChangesContent (line 21) | function PendingChangesContent() {
FILE: packages/app/src/Components/Tasks/RenewSubscription.tsx
class RenewSubTask (line 9) | class RenewSubTask extends BaseUITask {
method check (line 12) | check(_user: CachedMetadata, session: LoginSession): boolean {
method render (line 17) | render() {
FILE: packages/app/src/Components/Tasks/TaskList.tsx
class TaskStore (line 18) | class TaskStore extends ExternalStore<Array<UITask>> {
method constructor (line 21) | constructor() {
method takeSnapshot (line 44) | takeSnapshot(): UITask[] {
function TaskList (line 51) | function TaskList() {
FILE: packages/app/src/Components/Tasks/index.ts
type UITask (line 6) | interface UITask {
type UITaskState (line 18) | interface UITaskState {
method constructor (line 33) | constructor() {
method mute (line 37) | mute(): void {
method load (line 42) | load(cb: () => void) {
method #save (line 50) | #save() {
FILE: packages/app/src/Components/Text/Text.tsx
type TextProps (line 23) | interface TextProps {
function Text (line 38) | function Text({
function findGallery (line 226) | function findGallery(elements: Array<ParsedFragment>): [number, number] ...
function buildGallery (line 254) | function buildGallery(
FILE: packages/app/src/Components/Text/const.ts
constant ROW_HEIGHT (line 50) | const ROW_HEIGHT = 200
constant GRID_GAP (line 51) | const GRID_GAP = 2
FILE: packages/app/src/Components/Textarea/Textarea.tsx
type EmojiItemProps (line 17) | interface EmojiItemProps {
type TextareaProps (line 40) | interface TextareaProps {
type TriggerData (line 53) | type TriggerData = EmojiItemProps | FuzzySearchResult
FILE: packages/app/src/Components/Toaster/Toaster.tsx
type ToastNotification (line 8) | interface ToastNotification {
class ToasterSlots (line 15) | class ToasterSlots extends ExternalStore<Array<ToastNotification>> {
method push (line 18) | push(n: ToastNotification) {
method takeSnapshot (line 25) | takeSnapshot(): ToastNotification[] {
method remove (line 29) | remove(id?: string) {
function Toaster (line 37) | function Toaster() {
FILE: packages/app/src/Components/Trending/ShortNote.tsx
function TrendingNote (line 8) | function TrendingNote({ event }: { event: TaggedNostrEvent }) {
FILE: packages/app/src/Components/Trending/TrendingHashtags.tsx
function TrendingHashtags (line 13) | function TrendingHashtags({
FILE: packages/app/src/Components/Trending/TrendingPosts.tsx
function TrendingNotes (line 14) | function TrendingNotes({ count = Infinity, small = false }: { count?: nu...
FILE: packages/app/src/Components/Trending/TrendingUsers.tsx
function TrendingUsers (line 11) | function TrendingUsers({
FILE: packages/app/src/Components/Upload/file-picker.tsx
function MediaServerFileList (line 13) | function MediaServerFileList({
function ServerFile (line 96) | function ServerFile({ file, checked, onClick }: { file: BlobDescriptor; ...
FILE: packages/app/src/Components/User/AnimalName.ts
function capitalize (line 1823) | function capitalize(s: string) {
FILE: packages/app/src/Components/User/Avatar.tsx
type AvatarProps (line 8) | interface AvatarProps {
FILE: packages/app/src/Components/User/AvatarEditor.tsx
type AvatarEditorProps (line 9) | interface AvatarEditorProps {
function AvatarEditor (line 16) | function AvatarEditor({ picture, onPictureChange, privKey, className }: ...
FILE: packages/app/src/Components/User/AvatarGroup.tsx
function AvatarGroup (line 3) | function AvatarGroup({ ids, ...props }: { ids: string[] } & Omit<Profile...
FILE: packages/app/src/Components/User/BadgeList.tsx
type BadgeInfo (line 11) | interface BadgeInfo {
function BadgeList (line 20) | function BadgeList({ badges }: { badges: TaggedNostrEvent[] }) {
function ProfileBadges (line 82) | function ProfileBadges({ pubkey }: { pubkey: string }) {
FILE: packages/app/src/Components/User/Bookmarks.tsx
type BookmarksProps (line 11) | interface BookmarksProps {
function renderOption (line 29) | function renderOption(p: string) {
FILE: packages/app/src/Components/User/Debug.tsx
function UserDebug (line 7) | function UserDebug({ pubkey }: { pubkey: string }) {
FILE: packages/app/src/Components/User/DisplayName.tsx
type DisplayNameProps (line 8) | interface DisplayNameProps {
FILE: packages/app/src/Components/User/FollowButton.tsx
type FollowButtonProps (line 11) | interface FollowButtonProps {
function FollowButton (line 15) | function FollowButton(props: FollowButtonProps) {
FILE: packages/app/src/Components/User/FollowDistanceIndicator.tsx
type FollowDistanceIndicatorProps (line 6) | interface FollowDistanceIndicatorProps {
function FollowDistanceIndicator (line 11) | function FollowDistanceIndicator({ pubkey, className }: FollowDistanceIn...
FILE: packages/app/src/Components/User/FollowListBase.tsx
type FollowListBaseProps (line 12) | interface FollowListBaseProps {
function FollowListBase (line 22) | function FollowListBase({
FILE: packages/app/src/Components/User/FollowedBy.tsx
constant MAX_FOLLOWED_BY_FRIENDS (line 10) | const MAX_FOLLOWED_BY_FRIENDS = 3
function FollowedBy (line 12) | function FollowedBy({ pubkey, ...props }: { pubkey: string } & Omit<Prof...
FILE: packages/app/src/Components/User/Following.tsx
function FollowingMark (line 6) | function FollowingMark({ pubkey }: { pubkey: string }) {
FILE: packages/app/src/Components/User/FollowsYou.tsx
type FollowsYouProps (line 5) | interface FollowsYouProps {
function FollowsYou (line 9) | function FollowsYou({ followsMe }: FollowsYouProps) {
FILE: packages/app/src/Components/User/MuteButton.tsx
type MuteButtonProps (line 7) | interface MuteButtonProps {
FILE: packages/app/src/Components/User/MutedList.tsx
type MutedListProps (line 9) | interface MutedListProps {
function MutedList (line 13) | function MutedList() {
FILE: packages/app/src/Components/User/Nip05.tsx
type Nip05Params (line 9) | interface Nip05Params {
function Nip05 (line 24) | function Nip05({ nip05, pubkey, forceVerified, showBadges, className }: ...
FILE: packages/app/src/Components/User/NoteToSelf.tsx
type NoteToSelfProps (line 8) | interface NoteToSelfProps {
function NoteLabel (line 12) | function NoteLabel() {
function NoteToSelf (line 20) | function NoteToSelf({ className }: NoteToSelfProps) {
FILE: packages/app/src/Components/User/ProfileCard.tsx
function ProfileCard (line 12) | function ProfileCard({ pubkey, user }: { pubkey: string; user?: UserMeta...
FILE: packages/app/src/Components/User/ProfileCardWrapper.tsx
type ProfileCardWrapperProps (line 7) | interface ProfileCardWrapperProps {
function ProfileCardWrapper (line 13) | function ProfileCardWrapper({ pubkey, user, children }: ProfileCardWrapp...
FILE: packages/app/src/Components/User/ProfileImage.tsx
type ProfileImageProps (line 15) | interface ProfileImageProps {
function ProfileImage (line 35) | function ProfileImage({
FILE: packages/app/src/Components/User/ProfileLink.tsx
function ProfileLink (line 7) | function ProfileLink({
FILE: packages/app/src/Components/User/ProfilePreview.tsx
type ProfilePreviewProps (line 9) | interface ProfilePreviewProps {
function handleClick (line 32) | function handleClick(e: React.MouseEvent<HTMLDivElement>) {
FILE: packages/app/src/Components/User/UserWebsiteLink.tsx
function UserWebsiteLink (line 5) | function UserWebsiteLink({ user }: { user?: CachedMetadata | UserMetadat...
FILE: packages/app/src/Components/User/Username.tsx
function Username (line 7) | function Username({ pubkey, onLinkVisit }: { pubkey: string; onLinkVisit...
FILE: packages/app/src/Components/WarningNotice/WarningNotice.tsx
function WarningNotice (line 3) | function WarningNotice({ children, onClick }: { children: React.ReactNod...
FILE: packages/app/src/Components/ZapModal/SuccessAction.tsx
function SuccessAction (line 6) | function SuccessAction({ success }: { success: LNURLSuccessAction }) {
FILE: packages/app/src/Components/ZapModal/ZapModal.tsx
type SendSatsProps (line 16) | interface SendSatsProps {
function ZapModal (line 26) | function ZapModal(props: SendSatsProps) {
FILE: packages/app/src/Components/ZapModal/ZapModalInput.tsx
type SendSatsInputSelection (line 15) | interface SendSatsInputSelection {
function ZapModalInput (line 21) | function ZapModalInput(props: {
FILE: packages/app/src/Components/ZapModal/ZapModalInvoice.tsx
function ZapModalInvoice (line 9) | function ZapModalInvoice(props: {
FILE: packages/app/src/Components/ZapModal/ZapModalTitle.tsx
function ZapModalTitle (line 8) | function ZapModalTitle({
FILE: packages/app/src/Components/ZapModal/ZapType.tsx
type ZapType (line 1) | enum ZapType {
FILE: packages/app/src/Components/ZapModal/ZapTypeSelector.tsx
function ZapTypeSelector (line 8) | function ZapTypeSelector({ zapType, setZapType }: { zapType: ZapType; se...
FILE: packages/app/src/Components/flyout.tsx
function Flyout (line 7) | function Flyout({
FILE: packages/app/src/Components/json.tsx
function JsonBlock (line 1) | function JsonBlock({ obj }: { obj: object }) {
FILE: packages/app/src/Components/kind-name.tsx
function KindName (line 8) | function KindName({ kind }: { kind: number }) {
FILE: packages/app/src/Components/nip.tsx
function NipDescription (line 3) | function NipDescription({ nip }: { nip: number }) {
FILE: packages/app/src/Components/zap-amount.tsx
function ZapAmount (line 5) | function ZapAmount({ n }: { n: number }) {
FILE: packages/app/src/Db/FuzzySearch.ts
type FuzzySearchResult (line 4) | type FuzzySearchResult = {
FILE: packages/app/src/External/NostrBand.ts
type TrendingProfile (line 4) | interface TrendingProfile {
type TrendingProfilesResponse (line 11) | interface TrendingProfilesResponse {
type TrendingHashtagsResponse (line 15) | interface TrendingHashtagsResponse {
class NostrBandApi (line 19) | class NostrBandApi extends JsonApi {
method trendingProfiles (line 23) | async trendingProfiles() {
method trendingHashtags (line 35) | async trendingHashtags(lang?: string) {
FILE: packages/app/src/External/NostrServices.ts
type LinkPreviewData (line 3) | interface LinkPreviewData {
class NostrServices (line 13) | class NostrServices extends JsonApi {
method constructor (line 15) | constructor(url?: string) {
method linkPreview (line 20) | linkPreview(url: string) {
FILE: packages/app/src/External/SnortApi.ts
type RevenueToday (line 7) | interface RevenueToday {
type RevenueSplit (line 12) | interface RevenueSplit {
type InvoiceResponse (line 17) | interface InvoiceResponse {
type Subscription (line 21) | interface Subscription {
type SubscriptionErrorCode (line 30) | enum SubscriptionErrorCode {
class SubscriptionError (line 36) | class SubscriptionError extends Error {
method constructor (line 39) | constructor(msg: string, code: SubscriptionErrorCode) {
type PushNotifications (line 45) | interface PushNotifications {
type TranslationRequest (line 52) | interface TranslationRequest {
type TranslationResponse (line 57) | interface TranslationResponse {
type RelayDistance (line 64) | interface RelayDistance {
type RefCodeResponse (line 74) | interface RefCodeResponse {
class SnortApi (line 84) | class SnortApi extends JsonApi {
method constructor (line 88) | constructor(url?: string, signer?: EventSigner | EventPublisher) {
method revenueSplits (line 94) | revenueSplits() {
method revenueToday (line 98) | revenueToday() {
method createSubscription (line 102) | createSubscription(type: number, refCode?: string) {
method renewSubscription (line 109) | renewSubscription(id: string, months = 1) {
method listSubscriptions (line 116) | listSubscriptions() {
method onChainDonation (line 123) | onChainDonation() {
method getPushNotificationInfo (line 127) | getPushNotificationInfo() {
method registerPushNotifications (line 131) | registerPushNotifications(sub: PushNotifications) {
method translate (line 138) | translate(tx: TranslationRequest) {
method closeRelays (line 142) | closeRelays(lat: number, lon: number, count = 5) {
method getRefCode (line 146) | getRefCode() {
method getRefCodeInfo (line 153) | getRefCodeInfo(code: string) {
method applyForLeader (line 157) | applyForLeader() {
FILE: packages/app/src/External/base.ts
method getJsonAuthd (line 6) | protected async getJsonAuthd<T>(
method getJson (line 25) | protected async getJson<T>(
FILE: packages/app/src/Feed/ArticlesFeed.ts
function useArticles (line 8) | function useArticles(limit = 10) {
function useCachedArticles (line 22) | function useCachedArticles(limit = 10) {
FILE: packages/app/src/Feed/BadgesFeed.ts
type BadgeAwards (line 7) | type BadgeAwards = {
function useProfileBadges (line 12) | function useProfileBadges(pubkey: string) {
FILE: packages/app/src/Feed/FollowersFeed.ts
function useFollowersFeed (line 7) | function useFollowersFeed(pubkey?: string) {
FILE: packages/app/src/Feed/FollowsFeed.ts
function useFollowsFeed (line 5) | function useFollowsFeed(pubkey?: string) {
function getFollowing (line 20) | function getFollowing(notes: readonly TaggedNostrEvent[], pubkey?: strin...
FILE: packages/app/src/Feed/HashtagsFeed.ts
function useHashtagsFeed (line 9) | function useHashtagsFeed() {
FILE: packages/app/src/Feed/LoginFeed.ts
function useLoginFeed (line 18) | function useLoginFeed() {
FILE: packages/app/src/Feed/RelayState.ts
function useRelayState (line 4) | function useRelayState(addr: string) {
FILE: packages/app/src/Feed/RelaysFeed.tsx
function useRelaysFeed (line 5) | function useRelaysFeed(pubkey?: string) {
FILE: packages/app/src/Feed/StatusFeed.ts
function useStatusFeed (line 8) | function useStatusFeed(id?: string, leaveOpen = false) {
FILE: packages/app/src/Feed/TimelineFeed.ts
type TimelineFeedOptions (line 11) | interface TimelineFeedOptions {
type TimelineSubject (line 17) | interface TimelineSubject {
type TimelineFeed (line 26) | type TimelineFeed = ReturnType<typeof useTimelineFeed>
function useTimelineFeed (line 28) | function useTimelineFeed(subject: TimelineSubject, options: TimelineFeed...
FILE: packages/app/src/Feed/WorkerRelayView.ts
function useNotificationsView (line 7) | function useNotificationsView() {
FILE: packages/app/src/Feed/ZapsFeed.ts
function useZapsFeed (line 5) | function useZapsFeed(link?: NostrLink) {
FILE: packages/app/src/Hooks/useAiAgent.ts
class CustomModelProvider (line 24) | class CustomModelProvider implements ModelProvider {
method constructor (line 28) | constructor(apiUrl: string, apiKey: string | undefined, modelName: str...
method getModel (line 37) | async getModel(modelName?: string) {
method listModels (line 41) | async listModels(): Promise<Array<{ id: string }>> {
constant AI_CONFIG (line 52) | const AI_CONFIG = {
type ModelInfo (line 58) | interface ModelInfo {
type AiStreamEvent (line 63) | type AiStreamEvent =
type ChatHistoryItem (line 71) | interface ChatHistoryItem {
type AgentInstance (line 76) | interface AgentInstance {
function useAiAgent (line 83) | function useAiAgent() {
FILE: packages/app/src/Hooks/useAppHandler.ts
type AppHandler (line 14) | interface AppHandler {
function useAppHandler (line 20) | function useAppHandler(kind: EventKind | undefined): Array<AppHandler> {
FILE: packages/app/src/Hooks/useBlindSpot.ts
function useBlindSpot (line 7) | function useBlindSpot(count = 10) {
FILE: packages/app/src/Hooks/useBlossomServers.ts
function useBlossomServers (line 6) | function useBlossomServers(authors?: Array<string> | Array<NostrLink>) {
FILE: packages/app/src/Hooks/useCloseRelays.ts
type RelayDistance (line 10) | interface RelayDistance {
function useCloseRelays (line 16) | function useCloseRelays() {
FILE: packages/app/src/Hooks/useCommunityLeaders.tsx
function useCommunityLeaders (line 9) | function useCommunityLeaders() {
function useCommunityLeader (line 26) | function useCommunityLeader(pubkey?: string) {
FILE: packages/app/src/Hooks/useContentDiscovery.ts
function useContentDiscovery (line 6) | function useContentDiscovery(serviceProvider: string, relays?: Array<str...
FILE: packages/app/src/Hooks/useDiscoverMediaServers.ts
function useDiscoverMediaServers (line 9) | function useDiscoverMediaServers() {
FILE: packages/app/src/Hooks/useDvmLinks.ts
function getEphemeralSigner (line 7) | function getEphemeralSigner() {
function useDVMLinks (line 17) | function useDVMLinks(
FILE: packages/app/src/Hooks/useEventPublisher.tsx
function useEventPublisher (line 7) | function useEventPublisher() {
FILE: packages/app/src/Hooks/useFollowControls.ts
function useFollowsControls (line 9) | function useFollowsControls() {
FILE: packages/app/src/Hooks/useHistoryState.tsx
function useHistoryState (line 3) | function useHistoryState<T>(initialValue: T, key: string) {
FILE: packages/app/src/Hooks/useHorizontalScroll.tsx
function useHorizontalScroll (line 3) | function useHorizontalScroll() {
FILE: packages/app/src/Hooks/useHovering.ts
type HoveringProps (line 3) | interface HoveringProps {
function useHovering (line 8) | function useHovering<T extends HTMLElement>(props?: HoveringProps) {
FILE: packages/app/src/Hooks/useImgProxy.ts
function useImgProxy (line 5) | function useImgProxy() {
FILE: packages/app/src/Hooks/useKeyboardShortcut.ts
function useKeyboardShortcut (line 3) | function useKeyboardShortcut(key: string, callback: (event: KeyboardEven...
FILE: packages/app/src/Hooks/useLists.tsx
function useLinkList (line 12) | function useLinkList(id: string, kinds: Array<EventKind>, pubkey: string...
function useLinkListEvents (line 30) | function useLinkListEvents(id: string, kinds: Array<EventKind>, pubkey: ...
function usePinList (line 35) | function usePinList(pubkey: string | undefined) {
function useBookmarkList (line 39) | function useBookmarkList(pubkey: string | undefined) {
function useInterestsList (line 43) | function useInterestsList(pubkey: string | undefined) {
FILE: packages/app/src/Hooks/useLiveStreams.ts
function useLiveStreams (line 9) | function useLiveStreams() {
FILE: packages/app/src/Hooks/useLoading.tsx
function useLoading (line 3) | function useLoading<T>(fn: ((e: React.MouseEvent) => Promise<T> | T) | u...
FILE: packages/app/src/Hooks/useLogin.tsx
function useLogin (line 5) | function useLogin<T = LoginSession>(selector?: (v: LoginSession) => T) {
FILE: packages/app/src/Hooks/useLoginHandler.tsx
function useLoginHandler (line 8) | function useLoginHandler() {
FILE: packages/app/src/Hooks/useLoginRelays.tsx
function useLoginRelays (line 7) | function useLoginRelays() {
function updateRelayConnections (line 18) | async function updateRelayConnections(system: SystemInterface, relays: R...
FILE: packages/app/src/Hooks/useMediaServerList.ts
function useMediaServerList (line 15) | function useMediaServerList() {
FILE: packages/app/src/Hooks/useModeration.tsx
class MutedWordTag (line 15) | class MutedWordTag implements ToNostrEventTag {
method constructor (line 16) | constructor(readonly word: string) {}
method equals (line 17) | equals(other: ToNostrEventTag): boolean {
method toEventTag (line 21) | toEventTag(): string[] | undefined {
function useModeration (line 26) | function useModeration() {
FILE: packages/app/src/Hooks/usePageDimensions.tsx
function usePageDimensions (line 3) | function usePageDimensions() {
FILE: packages/app/src/Hooks/usePreferences.ts
function usePreferences (line 5) | function usePreferences<T = UserPreferences>(selector?: (v: UserPreferen...
function useAllPreferences (line 17) | function useAllPreferences() {
FILE: packages/app/src/Hooks/useProfileLink.ts
function useProfileLink (line 7) | function useProfileLink(pubkey?: string, user?: UserMetadata | CachedMet...
FILE: packages/app/src/Hooks/useProfileSearch.tsx
function useProfileSearch (line 5) | function useProfileSearch() {
function userSearch (line 10) | function userSearch(wot: WoT, search: string | undefined) {
FILE: packages/app/src/Hooks/useRates.tsx
function useRates (line 9) | function useRates(symbol: string, leaveOpen = true) {
function useRateHistory (line 37) | function useRateHistory(symbol: string, size: number, leaveOpen = false) {
FILE: packages/app/src/Hooks/useRelays.tsx
function useRelays (line 3) | function useRelays() {
FILE: packages/app/src/Hooks/useTextTransformCache.tsx
function transformTextCached (line 8) | function transformTextCached(id: string, content: string, tags: Array<Ar...
function useTextTransformer (line 19) | function useTextTransformer(id: string, content: string, tags: Array<Arr...
FILE: packages/app/src/Hooks/useTheme.tsx
function useTheme (line 5) | function useTheme() {
function setTheme (line 24) | function setTheme(theme: "light" | "dark") {
FILE: packages/app/src/Hooks/useTimelineChunks.ts
type WindowChunk (line 3) | interface WindowChunk {
function useTimelineChunks (line 8) | function useTimelineChunks(opt: { window?: number; firstChunkSize?: numb...
FILE: packages/app/src/Hooks/useTimelineWindow.tsx
function useTimelineWindow (line 3) | function useTimelineWindow(opt: { window?: number; now: number }) {
FILE: packages/app/src/Hooks/useTraceTimeline.tsx
constant TRACE_TIMELINE_KEY (line 3) | const TRACE_TIMELINE_KEY = "trace-timeline-open"
function useTraceTimeline (line 8) | function useTraceTimeline() {
FILE: packages/app/src/Hooks/useWindowSize.ts
function useWindowSize (line 3) | function useWindowSize() {
FILE: packages/app/src/Hooks/useWoT.ts
type WoT (line 5) | interface WoT {
function wotOnSystem (line 13) | function wotOnSystem(system: SystemInterface) {
function useWoT (line 26) | function useWoT() {
FILE: packages/app/src/Pages/About.tsx
function AboutPage (line 6) | function AboutPage() {
FILE: packages/app/src/Pages/Agent/AgentPage.tsx
type ChatMessage (line 18) | interface ChatMessage {
class MessageStore (line 27) | class MessageStore extends ExternalStore<Array<ChatMessage>> {
method addMessage (line 30) | addMessage(msg: ChatMessage) {
method addStreamChunk (line 35) | addStreamChunk(msgId: string, event: AiStreamEvent) {
method updateMessage (line 63) | updateMessage(msgId: string, fnUpdate: (old: ChatMessage) => void) {
method takeSnapshot (line 71) | takeSnapshot(p?: any): ChatMessage[] {
function AgentPage (line 76) | function AgentPage() {
FILE: packages/app/src/Pages/CacheDebug.tsx
function DebugPage (line 7) | function DebugPage() {
FILE: packages/app/src/Pages/ComponentDebug.tsx
constant SAMPLE_HEX_PUBKEY (line 114) | const SAMPLE_HEX_PUBKEY = bech32ToHex(KieranPubKey)
constant SAMPLE_PUBKEY_2 (line 116) | const SAMPLE_PUBKEY_2 = "4523be58d395b1b196a9b8c82b038b6895cb02b683d0c25...
constant SAMPLE_PUBKEY_3 (line 117) | const SAMPLE_PUBKEY_3 = "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9...
constant SAMPLE_INVOICE (line 119) | const SAMPLE_INVOICE =
constant SAMPLE_YOUTUBE_URL (line 121) | const SAMPLE_YOUTUBE_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
constant SAMPLE_SPOTIFY_URL (line 122) | const SAMPLE_SPOTIFY_URL = "https://open.spotify.com/track/4cOdK2wGLETKB...
constant SAMPLE_APPLE_MUSIC_URL (line 123) | const SAMPLE_APPLE_MUSIC_URL = "https://music.apple.com/us/album/example"
constant SAMPLE_TIDAL_URL (line 124) | const SAMPLE_TIDAL_URL = "https://tidal.com/browse/track/111398735"
constant SAMPLE_SOUNDCLOUD_URL (line 125) | const SAMPLE_SOUNDCLOUD_URL = "https://soundcloud.com/djgrooveteck/sidep...
constant SAMPLE_WAVLAKE_URL (line 126) | const SAMPLE_WAVLAKE_URL = "https://wavlake.com/track/1079f9b0-e60a-4eba...
constant SAMPLE_MIXCLOUD_URL (line 127) | const SAMPLE_MIXCLOUD_URL = "https://www.mixcloud.com/johndigweed/transi...
constant SAMPLE_NOSTR_NESTS_URL (line 128) | const SAMPLE_NOSTR_NESTS_URL = "https://nostrnests.com/example"
constant SAMPLE_CASHU_TOKEN (line 129) | const SAMPLE_CASHU_TOKEN =
constant SAMPLE_POLL_EVENT (line 133) | const SAMPLE_POLL_EVENT = {
constant SAMPLE_LONG_FORM_EVENT (line 148) | const SAMPLE_LONG_FORM_EVENT = {
constant SAMPLE_ZAP_GOAL_EVENT (line 165) | const SAMPLE_ZAP_GOAL_EVENT = {
constant SAMPLE_TEXT_NOTE (line 177) | const SAMPLE_TEXT_NOTE = {
constant SAMPLE_GALLERY_1_NOTE (line 190) | const SAMPLE_GALLERY_1_NOTE = {
constant SAMPLE_GALLERY_2_NOTE (line 202) | const SAMPLE_GALLERY_2_NOTE = {
constant SAMPLE_GALLERY_3_NOTE (line 214) | const SAMPLE_GALLERY_3_NOTE = {
constant SAMPLE_GALLERY_4_NOTE (line 227) | const SAMPLE_GALLERY_4_NOTE = {
constant SAMPLE_GALLERY_5_NOTE (line 240) | const SAMPLE_GALLERY_5_NOTE = {
constant SAMPLE_GALLERY_6_NOTE (line 253) | const SAMPLE_GALLERY_6_NOTE = {
constant SAMPLE_REPOST (line 266) | const SAMPLE_REPOST = {
constant SAMPLE_REPLY (line 281) | const SAMPLE_REPLY = {
constant SAMPLE_LIVE_STREAM_EVENT (line 295) | const SAMPLE_LIVE_STREAM_EVENT = {
function ComponentDebugPage (line 340) | function ComponentDebugPage() {
FILE: packages/app/src/Pages/Deck/Articles.tsx
function Articles (line 7) | function Articles({ noteProps }: { noteProps?: Omit<NoteProps, "data"> }) {
FILE: packages/app/src/Pages/Deck/Columns.tsx
function NotesCol (line 13) | function NotesCol() {
function ArticlesCol (line 32) | function ArticlesCol() {
function MediaCol (line 46) | function MediaCol({ setThread }: { setThread: (e: NostrLink) => void }) {
function NotificationsCol (line 73) | function NotificationsCol({ setThread }: { setThread: (e: NostrLink) => ...
FILE: packages/app/src/Pages/Deck/DeckLayout.tsx
type Cols (line 22) | type Cols = "notes" | "articles" | "media" | "streams" | "notifications"
type DeckState (line 24) | interface DeckState {
type DeckScope (line 29) | interface DeckScope {
function SnortDeckLayout (line 37) | function SnortDeckLayout() {
FILE: packages/app/src/Pages/Discover.tsx
type DiscoverTab (line 10) | type DiscoverTab = Tab & {
function Discover (line 13) | function Discover() {
FILE: packages/app/src/Pages/Donate/DonatePage.tsx
function getOnChainAddress (line 24) | async function getOnChainAddress() {
function loadData (line 29) | async function loadData() {
function actions (line 40) | function actions(pk: string) {
FILE: packages/app/src/Pages/Donate/ZapPoolDonateSection.tsx
function ZapPoolDonateSection (line 10) | function ZapPoolDonateSection() {
FILE: packages/app/src/Pages/FixedPage.tsx
function FixedPage (line 3) | function FixedPage({ children, className }: { children?: ReactNode, clas...
FILE: packages/app/src/Pages/FreeNostrAddressPage.tsx
function FreeNostrAddressPage (line 4) | function FreeNostrAddressPage() {
FILE: packages/app/src/Pages/HashTagsPage.tsx
function HashTagHeader (line 42) | function HashTagHeader({ tag, events, className }: { tag: string; events...
FILE: packages/app/src/Pages/HelpPage.tsx
function HelpPage (line 7) | function HelpPage() {
FILE: packages/app/src/Pages/Layout/Footer.tsx
type MenuItem (line 13) | type MenuItem = {
constant MENU_ITEMS (line 22) | const MENU_ITEMS: MenuItem[] = [
FILE: packages/app/src/Pages/Layout/HasNotificationsMarker.tsx
function HasNotificationsMarker (line 6) | function HasNotificationsMarker() {
FILE: packages/app/src/Pages/Layout/Header.tsx
function Header (line 21) | function Header() {
function NoteTitle (line 123) | function NoteTitle({ link }: { link: NostrLink }) {
FILE: packages/app/src/Pages/Layout/LogoHeader.tsx
function ordinal_suffix_of (line 12) | function ordinal_suffix_of(i: number) {
function LogoHeader (line 41) | function LogoHeader({ showText = false }: { showText: boolean }) {
FILE: packages/app/src/Pages/Layout/NavSidebar.tsx
constant MENU_ITEMS (line 18) | const MENU_ITEMS = [
function NavSidebar (line 66) | function NavSidebar({ narrow = false }: { narrow?: boolean }) {
FILE: packages/app/src/Pages/Layout/ProfileMenu.tsx
function ProfileMenu (line 14) | function ProfileMenu({ className }: { className?: string }) {
FILE: packages/app/src/Pages/Layout/RightColumn.tsx
function RightColumn (line 23) | function RightColumn() {
FILE: packages/app/src/Pages/Layout/index.tsx
function Index (line 26) | function Index() {
function StalkerModal (line 100) | function StalkerModal({ id }: { id: string }) {
FILE: packages/app/src/Pages/ListFeedPage.tsx
function ListFeedPage (line 13) | function ListFeedPage() {
FILE: packages/app/src/Pages/Messages/ChatParticipant.tsx
function ChatParticipantProfile (line 8) | function ChatParticipantProfile({ participant }: { participant: ChatPart...
FILE: packages/app/src/Pages/Messages/DM.tsx
type DMProps (line 13) | interface DMProps {
function DM (line 18) | function DM(props: DMProps) {
FILE: packages/app/src/Pages/Messages/DmWindow.tsx
function DmWindow (line 12) | function DmWindow({ id }: { id: string }) {
function DmChatSelected (line 54) | function DmChatSelected({ chat }: { chat: Chat }) {
FILE: packages/app/src/Pages/Messages/MessagesPage.tsx
function MessagesPage (line 22) | function MessagesPage() {
FILE: packages/app/src/Pages/Messages/NewChatWindow.tsx
function NewChatWindow (line 14) | function NewChatWindow() {
FILE: packages/app/src/Pages/Messages/WriteMessage.tsx
function WriteMessage (line 8) | function WriteMessage({ chat }: { chat: Chat }) {
FILE: packages/app/src/Pages/NostrAddressPage.tsx
function NostrAddressPage (line 8) | function NostrAddressPage() {
FILE: packages/app/src/Pages/NostrLinkHandler.tsx
function NostrLinkHandler (line 11) | function NostrLinkHandler() {
FILE: packages/app/src/Pages/Notifications/NotificationGroup.tsx
function NotificationGroup (line 20) | function NotificationGroup({
FILE: packages/app/src/Pages/Notifications/Notifications.tsx
type NotificationSummaryFilter (line 16) | enum NotificationSummaryFilter {
function FilterIcon (line 35) | function FilterIcon({
function NotificationsPage (line 59) | function NotificationsPage({ onClick }: { onClick?: (link: NostrLink) =>...
FILE: packages/app/src/Pages/Notifications/getNotificationContext.tsx
function getNotificationContext (line 3) | function getNotificationContext(ev: TaggedNostrEvent) {
FILE: packages/app/src/Pages/Notifications/notificationContext.tsx
function NotificationContext (line 10) | function NotificationContext({ link }: { link: NostrLink }) {
FILE: packages/app/src/Pages/Profile/ProfilePage.tsx
type ProfilePageProps (line 30) | interface ProfilePageProps {
function ProfilePage (line 35) | function ProfilePage({ id: propId, state }: ProfilePageProps) {
FILE: packages/app/src/Pages/Profile/ProfileTabComponents.tsx
function ZapsProfileTab (line 20) | function ZapsProfileTab({ id }: { id: string }) {
function FollowersTab (line 75) | function FollowersTab({ id }: { id: string }) {
function FollowsTab (line 90) | function FollowsTab({ id }: { id: string }) {
function RelaysTab (line 105) | function RelaysTab({ id }: { id: string }) {
function BookMarksTab (line 125) | function BookMarksTab({ id }: { id: string }) {
function ReactionsTab (line 130) | function ReactionsTab({ id }: { id: string }) {
function ProfileNotesTab (line 146) | function ProfileNotesTab({ id, relays, isMe }: { id: string; relays?: Ar...
FILE: packages/app/src/Pages/Profile/ProfileTabType.tsx
type ProfileTabType (line 1) | enum ProfileTabType {
FILE: packages/app/src/Pages/Root/BlindSpots.tsx
function BlindSpots (line 4) | function BlindSpots() {
FILE: packages/app/src/Pages/Root/FollowSets.tsx
function FollowSetsPage (line 17) | function FollowSetsPage() {
FILE: packages/app/src/Pages/Root/Media.tsx
function MediaPosts (line 6) | function MediaPosts() {
FILE: packages/app/src/Pages/Root/RelayFeedPage.tsx
function RelayFeedPage (line 7) | function RelayFeedPage() {
FILE: packages/app/src/Pages/Root/RootRoutes.tsx
function RootPage (line 9) | function RootPage() {
FILE: packages/app/src/Pages/Root/RootTabRoutes.tsx
type RootTabRoutePath (line 3) | type RootTabRoutePath =
type RootTabRoute (line 20) | type RootTabRoute = RouteObject
method lazy (line 25) | async lazy() {
method lazy (line 32) | async lazy() {
method lazy (line 39) | async lazy() {
method lazy (line 46) | async lazy() {
method lazy (line 53) | async lazy() {
method lazy (line 60) | async lazy() {
method lazy (line 67) | async lazy() {
method lazy (line 74) | async lazy() {
method lazy (line 81) | async lazy() {
method lazy (line 93) | async lazy() {
method lazy (line 100) | async lazy() {
method lazy (line 107) | async lazy() {
method lazy (line 114) | async lazy() {
method lazy (line 121) | async lazy() {
method lazy (line 128) | async lazy() {
method lazy (line 135) | async lazy() {
FILE: packages/app/src/Pages/SearchPage.tsx
constant NOTES (line 11) | const NOTES = 0
constant PROFILES (line 12) | const PROFILES = 1
FILE: packages/app/src/Pages/TopicsPage.tsx
function TopicsPage (line 9) | function TopicsPage() {
FILE: packages/app/src/Pages/ZapPool/ZapPool.tsx
function ZapPoolPage (line 6) | function ZapPoolPage() {
FILE: packages/app/src/Pages/ZapPool/ZapPoolPageInner.tsx
function ZapPoolPageInner (line 22) | function ZapPoolPageInner() {
FILE: packages/app/src/Pages/ZapPool/ZapPoolTarget.tsx
function ZapPoolTargetInner (line 8) | function ZapPoolTargetInner({ target }: { target: ZapPoolRecipient }) {
function ZapPoolTarget (line 44) | function ZapPoolTarget({ target }: { target: ZapPoolRecipient }) {
FILE: packages/app/src/Pages/onboarding/discover.tsx
function Discover (line 9) | function Discover() {
FILE: packages/app/src/Pages/onboarding/index.tsx
type NewUserState (line 16) | interface NewUserState {
function OnboardingLayout (line 23) | function OnboardingLayout() {
FILE: packages/app/src/Pages/onboarding/moderation.tsx
function Moderation (line 11) | function Moderation() {
FILE: packages/app/src/Pages/onboarding/profile.tsx
function Profile (line 14) | function Profile() {
FILE: packages/app/src/Pages/onboarding/routes.ts
method lazy (line 3) | async lazy() {
method lazy (line 10) | async lazy() {
method lazy (line 17) | async lazy() {
method lazy (line 24) | async lazy() {
method lazy (line 31) | async lazy() {
method lazy (line 38) | async lazy() {
method lazy (line 45) | async lazy() {
FILE: packages/app/src/Pages/onboarding/sign-in.tsx
constant NIP46_PERMS (line 17) | const NIP46_PERMS =
function SignIn (line 20) | function SignIn() {
FILE: packages/app/src/Pages/onboarding/sign-up.tsx
function SignUp (line 12) | function SignUp() {
FILE: packages/app/src/Pages/onboarding/topics.tsx
function Topics (line 12) | function Topics() {
FILE: packages/app/src/Pages/settings/Accounts.tsx
function AccountsPage (line 8) | function AccountsPage() {
FILE: packages/app/src/Pages/settings/Cache.tsx
function CacheSettings (line 14) | function CacheSettings() {
function CacheDetails (line 30) | function CacheDetails<T>({ cache, name }: { cache: CachedTable<T>; name:...
function RelayCacheStats (line 65) | function RelayCacheStats() {
FILE: packages/app/src/Pages/settings/Keys.tsx
function ExportKeys (line 12) | function ExportKeys() {
FILE: packages/app/src/Pages/settings/Menu/Menu.tsx
type SettingsMenuItems (line 10) | type SettingsMenuItems = Array<{
FILE: packages/app/src/Pages/settings/Menu/SettingsMenuComponent.tsx
function SettingsMenuComponent (line 7) | function SettingsMenuComponent({ menu }: { menu: SettingsMenuItems }) {
FILE: packages/app/src/Pages/settings/Moderation.tsx
function ModerationSettingsPage (line 8) | function ModerationSettingsPage() {
FILE: packages/app/src/Pages/settings/Notifications.tsx
type StatusIndicatorProps (line 11) | interface StatusIndicatorProps {
FILE: packages/app/src/Pages/settings/Preferences.tsx
function row (line 20) | function row(title: ReactNode, description: ReactNode | undefined, contr...
FILE: packages/app/src/Pages/settings/Profile.tsx
type ProfileSettingsProps (line 18) | interface ProfileSettingsProps {
function ProfileSettings (line 23) | function ProfileSettings(props: ProfileSettingsProps) {
FILE: packages/app/src/Pages/settings/Referrals.tsx
function ReferralsPage (line 12) | function ReferralsPage() {
FILE: packages/app/src/Pages/settings/Relays.tsx
function addNewRelay (line 21) | async function addNewRelay() {
function addRelay (line 37) | function addRelay() {
function myRelays (line 61) | function myRelays() {
FILE: packages/app/src/Pages/settings/Routes.tsx
method lazy (line 19) | async lazy() {
method lazy (line 26) | async lazy() {
method lazy (line 33) | async lazy() {
method lazy (line 40) | async lazy() {
method lazy (line 47) | async lazy() {
method lazy (line 54) | async lazy() {
method lazy (line 61) | async lazy() {
method lazy (line 68) | async lazy() {
method lazy (line 75) | async lazy() {
method lazy (line 82) | async lazy() {
method lazy (line 89) | async lazy() {
method lazy (line 96) | async lazy() {
method lazy (line 103) | async lazy() {
FILE: packages/app/src/Pages/settings/handle/LNAddress.tsx
function LNForwardAddress (line 10) | function LNForwardAddress({ handle }: { handle: ManageHandle }) {
FILE: packages/app/src/Pages/settings/handle/ListHandles.tsx
function ListHandles (line 11) | function ListHandles() {
FILE: packages/app/src/Pages/settings/handle/Manage.tsx
function ManageHandleIndex (line 9) | function ManageHandleIndex() {
FILE: packages/app/src/Pages/settings/handle/TransferHandle.tsx
function TransferHandle (line 11) | function TransferHandle({ handle }: { handle: ManageHandle }) {
FILE: packages/app/src/Pages/settings/handle/routes.tsx
function ManageHandlePage (line 4) | function ManageHandlePage() {
method lazy (line 24) | async lazy() {
method lazy (line 31) | async lazy() {
FILE: packages/app/src/Pages/settings/media-settings.tsx
function MediaSettingsPage (line 14) | function MediaSettingsPage() {
FILE: packages/app/src/Pages/settings/relays/discover.tsx
function DiscoverRelays (line 17) | function DiscoverRelays() {
FILE: packages/app/src/Pages/settings/saveRelays.tsx
function saveRelays (line 5) | async function saveRelays(
FILE: packages/app/src/Pages/settings/tools/follows-relay-health.tsx
function FollowsRelayHealth (line 12) | function FollowsRelayHealth({
FILE: packages/app/src/Pages/settings/tools/index.tsx
function ToolsPage (line 7) | function ToolsPage() {
FILE: packages/app/src/Pages/settings/tools/prune-follows.tsx
type PruneStage (line 14) | enum PruneStage {
function PruneFollowList (line 19) | function PruneFollowList() {
FILE: packages/app/src/Pages/settings/tools/routes.tsx
method lazy (line 53) | async lazy() {
method lazy (line 60) | async lazy() {
method lazy (line 67) | async lazy() {
FILE: packages/app/src/Pages/settings/tools/sync-account.tsx
function SyncAccountTool (line 12) | function SyncAccountTool() {
FILE: packages/app/src/Pages/settings/wallet/Alby.tsx
function AlbyOAuth (line 10) | function AlbyOAuth() {
FILE: packages/app/src/Pages/settings/wallet/LNDHub.tsx
function tryConnect (line 17) | async function tryConnect(config: string) {
FILE: packages/app/src/Pages/settings/wallet/NWC.tsx
function tryConnect (line 17) | async function tryConnect(config: string) {
FILE: packages/app/src/Pages/settings/wallet/routes.tsx
method lazy (line 6) | async lazy() {
method lazy (line 13) | async lazy() {
method lazy (line 20) | async lazy() {
method lazy (line 27) | async lazy() {
FILE: packages/app/src/Pages/settings/wallet/utils.ts
function getAlbyOAuth (line 6) | function getAlbyOAuth() {
type OAuthToken (line 70) | interface OAuthToken {
FILE: packages/app/src/Pages/subscribe/ManageSubscription.tsx
function ManageSubscriptionPage (line 13) | function ManageSubscriptionPage() {
FILE: packages/app/src/Pages/subscribe/RenewSub.tsx
function RenewSub (line 13) | function RenewSub({ sub: s }: { sub?: Subscription }) {
FILE: packages/app/src/Pages/subscribe/SubscriptionCard.tsx
function SubscriptionCard (line 12) | function SubscriptionCard({ sub }: { sub: Subscription }) {
FILE: packages/app/src/Pages/subscribe/index.tsx
function SubscribePage (line 18) | function SubscribePage() {
FILE: packages/app/src/Pages/subscribe/utils.tsx
function mapPlanName (line 6) | function mapPlanName(id: number) {
function mapFeatureName (line 15) | function mapFeatureName(k: LockedFeatures) {
function mapSubscriptionErrorCode (line 58) | function mapSubscriptionErrorCode(c: SubscriptionError) {
FILE: packages/app/src/Pages/wallet/index.tsx
function WalletPage (line 18) | function WalletPage(props: { showHistory: boolean }) {
FILE: packages/app/src/Pages/wallet/price-chart.tsx
type TimeRange (line 7) | type TimeRange = "1D" | "1W" | "1M" | "3M" | "1Y" | "ALL"
type ChartData (line 9) | interface ChartData {
function CustomTooltip (line 15) | function CustomTooltip({ active, payload }: TooltipProps<number, string>) {
constant TIME_RANGES (line 41) | const TIME_RANGES: Record<TimeRange, { interval: number; range: number }...
function PriceChart (line 50) | function PriceChart() {
FILE: packages/app/src/Pages/wallet/receive.tsx
function WalletReceivePage (line 9) | function WalletReceivePage() {
FILE: packages/app/src/Pages/wallet/send.tsx
function WalletSendPage (line 9) | function WalletSendPage() {
FILE: packages/app/src/State/NoteCreator.ts
type NoteCreatorDataSnapshot (line 8) | interface NoteCreatorDataSnapshot {
class NoteCreatorStore (line 32) | class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
method constructor (line 35) | constructor() {
method #reset (line 51) | #reset(d: NoteCreatorDataSnapshot) {
method takeSnapshot (line 83) | takeSnapshot(): NoteCreatorDataSnapshot {
function useNoteCreator (line 95) | function useNoteCreator<T extends object = NoteCreatorDataSnapshot>(
FILE: packages/app/src/Utils/Login/Functions.ts
function logout (line 30) | function logout(id: string) {
function markNotificationsRead (line 37) | function markNotificationsRead(state: LoginSession) {
function clearEntropy (line 42) | function clearEntropy(state: LoginSession) {
function generateNewLoginKeys (line 50) | async function generateNewLoginKeys() {
function generateNewLogin (line 59) | async function generateNewLogin(
function updateSession (line 100) | function updateSession(id: string, fn: (state: LoginSession) => void) {
function setAppData (line 108) | function setAppData(state: LoginSession, data: SnortAppData) {
function updateAppData (line 113) | function updateAppData(id: string, fn: (data: SnortAppData) => SnortAppD...
function setPreference (line 121) | function setPreference(obj: Partial<UserPreferences>) {
function saveAppData (line 134) | async function saveAppData(id: string) {
function addSubscription (line 141) | function addSubscription(state: LoginSession, ...subs: SubscriptionEvent...
function sessionNeedsPin (line 149) | function sessionNeedsPin(l: LoginSession) {
function createPublisher (line 153) | function createPublisher(l: LoginSession) {
FILE: packages/app/src/Utils/Login/LoginSession.ts
type Newest (line 9) | interface Newest<T> {
type LoginSessionType (line 14) | enum LoginSessionType {
type SnortAppData (line 23) | interface SnortAppData {
type LoginSession (line 27) | interface LoginSession {
FILE: packages/app/src/Utils/Login/MultiAccountStore.ts
class MultiAccountStore (line 59) | class MultiAccountStore extends ExternalStore<LoginSession> {
method constructor (line 65) | constructor() {
method enableStandardLists (line 122) | private static enableStandardLists<T>(state: UserState<T>) {
method getSessions (line 128) | getSessions() {
method get (line 135) | get(id: string) {
method allSubscriptions (line 142) | allSubscriptions() {
method switchAccount (line 146) | switchAccount(id: string) {
method getPublisher (line 153) | getPublisher(id: string) {
method setPublisher (line 157) | setPublisher(id: string, pub: EventPublisher) {
method loginWithPubkey (line 162) | loginWithPubkey(
method decideInitRelays (line 211) | decideInitRelays(relays: Record<string, RelaySettings> | undefined): R...
method loginWithPrivateKey (line 219) | loginWithPrivateKey(key: KeyStorage, entropy?: string, relays?: Record...
method updateSession (line 265) | updateSession(s: LoginSession) {
method removeSession (line 272) | removeSession(id: string) {
method takeSnapshot (line 281) | takeSnapshot(): LoginSession {
method #migrate (line 288) | #migrate() {
method #save (line 358) | #save() {
FILE: packages/app/src/Utils/Login/Nip7OsSigner.ts
class Nip7OsSigner (line 6) | class Nip7OsSigner implements EventSigner {
method constructor (line 9) | constructor() {
method supports (line 17) | get supports(): string[] {
method init (line 21) | init(): Promise<void> {
method getPubKey (line 25) | getPubKey(): string | Promise<string> {
method nip4Encrypt (line 29) | nip4Encrypt(content: string, key: string): Promise<string> {
method nip4Decrypt (line 33) | nip4Decrypt(content: string, otherKey: string): Promise<string> {
method nip44Encrypt (line 37) | nip44Encrypt(_content: string, _key: string): Promise<string> {
method nip44Decrypt (line 41) | nip44Decrypt(_content: string, _otherKey: string): Promise<string> {
method sign (line 45) | sign(ev: NostrEvent): Promise<NostrEvent> {
FILE: packages/app/src/Utils/Login/Preferences.ts
type UserPreferences (line 5) | interface UserPreferences {
FILE: packages/app/src/Utils/Login/index.ts
type Nip7os (line 5) | interface Nip7os {
type Window (line 14) | interface Window {
FILE: packages/app/src/Utils/Nip05/ServiceProvider.ts
type ServiceErrorCode (line 3) | type ServiceErrorCode =
type ServiceError (line 19) | interface ServiceError {
type ServiceConfig (line 24) | interface ServiceConfig {
type DomainConfig (line 28) | type DomainConfig = {
type HandleAvailability (line 36) | type HandleAvailability = {
type HandleQuote (line 43) | type HandleQuote = {
type HandleData (line 48) | type HandleData = {
type HandleRegisterResponse (line 52) | type HandleRegisterResponse = {
type CheckRegisterResponse (line 59) | type CheckRegisterResponse = {
class ServiceProvider (line 65) | class ServiceProvider {
method constructor (line 68) | constructor(url: URL | string) {
method GetConfig (line 72) | async GetConfig(): Promise<ServiceConfig | ServiceError> {
method CheckAvailable (line 76) | async CheckAvailable(handle: string, domain: string): Promise<HandleAv...
method RegisterHandle (line 83) | async RegisterHandle(handle: string, domain: string, pubkey: string): ...
method CheckRegistration (line 92) | async CheckRegistration(token: string): Promise<CheckRegisterResponse ...
method getJson (line 98) | protected async getJson<T>(
FILE: packages/app/src/Utils/Nip05/SnortServiceProvider.ts
type ManageHandle (line 5) | interface ManageHandle {
type ForwardType (line 15) | enum ForwardType {
type PatchHandle (line 21) | interface PatchHandle {
class SnortServiceProvider (line 26) | class SnortServiceProvider extends ServiceProvider {
method constructor (line 29) | constructor(publisher: EventPublisher, url: string | URL) {
method list (line 34) | async list() {
method transfer (line 38) | async transfer(id: string, to: string) {
method patch (line 42) | async patch(id: string, obj: PatchHandle) {
method registerForSubscription (line 46) | async registerForSubscription(handle: string, domain: string, id: stri...
method getJsonAuthd (line 55) | async getJsonAuthd<T>(
FILE: packages/app/src/Utils/Notifications.ts
type NotificationRequest (line 11) | interface NotificationRequest {
function makeNotification (line 18) | async function makeNotification(ev: TaggedNostrEvent): Promise<Notificat...
function replaceTagsWithUser (line 41) | function replaceTagsWithUser(ev: TaggedNostrEvent, users: CachedMetadata...
function sendNotification (line 59) | async function sendNotification(state: LoginSession, req: NotificationRe...
function subscribeToNotifications (line 75) | async function subscribeToNotifications(publisher: EventPublisher) {
FILE: packages/app/src/Utils/Number.ts
function formatShort (line 6) | function formatShort(n: number) {
FILE: packages/app/src/Utils/Subscription/index.ts
type SubscriptionType (line 3) | enum SubscriptionType {
type LockedFeatures (line 8) | enum LockedFeatures {
type SubscriptionEvent (line 46) | interface SubscriptionEvent {
function getActiveSubscriptions (line 53) | function getActiveSubscriptions(s: Array<SubscriptionEvent>) {
function getCurrentSubscription (line 58) | function getCurrentSubscription(s: Array<SubscriptionEvent>) {
function mostRecentSubscription (line 62) | function mostRecentSubscription(s: Array<SubscriptionEvent>) {
FILE: packages/app/src/Utils/Thread/ThreadContextWrapper.tsx
function ThreadContextWrapper (line 20) | function ThreadContextWrapper({ link, children }: { link: NostrLink; chi...
function useFilteredThread (line 91) | function useFilteredThread(notes: Array<TaggedNostrEvent>) {
FILE: packages/app/src/Utils/Thread/index.ts
function replyChainKey (line 12) | function replyChainKey(ev: TaggedNostrEvent) {
type ThreadContextState (line 23) | interface ThreadContextState {
FILE: packages/app/src/Utils/Upload/blossom.ts
type BlobDescriptor (line 6) | interface BlobDescriptor {
class Blossom (line 15) | class Blossom {
method constructor (line 16) | constructor(
method upload (line 23) | async upload(file: File) {
method media (line 42) | async media(file: File) {
method mirror (line 57) | async mirror(url: string) {
method list (line 83) | async list(pk: string) {
method delete (line 95) | async delete(id: string) {
method #fixTags (line 105) | #fixTags(r: BlobDescriptor) {
method #req (line 116) | async #req(
FILE: packages/app/src/Utils/Upload/index.ts
type UploadResult (line 11) | interface UploadResult {
type Uploader (line 48) | interface Uploader {
type UploadProgress (line 53) | interface UploadProgress {
type UploadStage (line 60) | type UploadStage = "starting" | "hashing" | "uploading" | "done" | undef...
function useFileUpload (line 62) | function useFileUpload(privKey?: string) {
class MultiServerBlossom (line 77) | class MultiServerBlossom {
method constructor (line 78) | constructor(
method upload (line 84) | async upload(file: File | Blob) {
FILE: packages/app/src/Utils/ZapPoolController.ts
type ZapPoolRecipientType (line 11) | enum ZapPoolRecipientType {
type ZapPoolRecipient (line 18) | interface ZapPoolRecipient {
class ZapPool (line 25) | class ZapPool extends ExternalStore<Array<ZapPoolRecipient>> {
method constructor (line 31) | constructor() {
method payout (line 37) | async payout(wallet: LNWallet) {
method calcAllocation (line 92) | calcAllocation(n: number) {
method allocate (line 100) | allocate(n: number) {
method getOrDefault (line 111) | getOrDefault(rcpt: ZapPoolRecipient): ZapPoolRecipient {
method set (line 120) | set(rcpt: ZapPoolRecipient) {
method #key (line 132) | #key(rcpt: ZapPoolRecipient) {
method #save (line 136) | #save() {
method #load (line 141) | #load() {
method #autoPayout (line 166) | async #autoPayout() {
method takeSnapshot (line 185) | takeSnapshot(): ZapPoolRecipient[] {
FILE: packages/app/src/Utils/emoji-search.ts
function searchEmoji (line 3) | async function searchEmoji(key: string) {
FILE: packages/app/src/Utils/getEventMedia.ts
function getEventMedia (line 5) | function getEventMedia(event: TaggedNostrEvent) {
FILE: packages/app/src/Utils/index.ts
function openFile (line 13) | async function openFile(): Promise<File | undefined> {
function parseId (line 49) | function parseId(id: string) {
function eventLink (line 66) | function eventLink(hex: string, relays?: Array<string> | string) {
function getLinkReactions (line 73) | function getLinkReactions(
function getAllLinkReactions (line 81) | function getAllLinkReactions(
function deepClone (line 89) | function deepClone<T>(obj: T) {
function debounce (line 100) | function debounce(timeout: number, fn: () => void) {
function dedupeByPubkey (line 105) | function dedupeByPubkey(events: TaggedNostrEvent[]) {
function dedupeById (line 122) | function dedupeById<T extends { id: string }>(events: Array<T>) {
function getLatestByPubkey (line 144) | function getLatestByPubkey(events: TaggedNostrEvent[]): Map<string, Tagg...
function getLatestProfileByPubkey (line 157) | function getLatestProfileByPubkey(profiles: CachedMetadata[]): Map<strin...
function dedupe (line 170) | function dedupe<T>(v: Array<T>) {
function appendDedupe (line 174) | function appendDedupe<T>(a?: Array<T>, b?: Array<T>) {
function unwrap (line 178) | function unwrap<T>(v: T | undefined | null): T {
function randomSample (line 192) | function randomSample<T>(coll: T[], size: number): T[] {
function getNewest (line 208) | function getNewest(rawNotes: readonly TaggedNostrEvent[]) {
function getNewestProfile (line 216) | function getNewestProfile(rawNotes: CachedMetadata[]) {
function getNewestEventTagsByKey (line 224) | function getNewestEventTagsByKey(evs: TaggedNostrEvent[], tag: string) {
function tagFilterOfTextRepost (line 235) | function tagFilterOfTextRepost(note: TaggedNostrEvent, id?: string): (ta...
function groupByPubkey (line 239) | function groupByPubkey(acc: Record<string, CachedMetadata>, user: Cached...
function orderAscending (line 249) | function orderAscending<T>(arr: Array<T & { created_at: number }>) {
type Magnet (line 253) | interface Magnet {
function magnetURIDecode (line 269) | function magnetURIDecode(uri: string): Magnet | undefined {
function chunks (line 360) | function chunks<T>(arr: T[], length: number) {
function findTag (line 372) | function findTag(e: NostrEvent, tag: string) {
function getRelayName (line 379) | function getRelayName(url: string) {
function getUrlHostname (line 388) | function getUrlHostname(url?: string) {
function sanitizeRelayUrl (line 396) | function sanitizeRelayUrl(url?: string) {
function kvToObject (line 405) | function kvToObject<T>(o: string, sep?: string) {
function defaultAvatar (line 417) | function defaultAvatar(input?: string) {
function isFormElement (line 423) | function isFormElement(target: HTMLElement): boolean {
function getDisplayName (line 458) | function getDisplayName(user: UserMetadata | undefined, pubkey: string):...
function getDisplayNameOrPlaceHolder (line 462) | function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pub...
function getCountry (line 474) | function getCountry() {
function calculateDistance (line 489) | function calculateDistance(lat1: number, lon1: number, lat2: number, lon...
function toRadians (line 501) | function toRadians(degrees: number): number {
function trackEvent (line 505) | function trackEvent(
function storeRefCode (line 531) | function storeRefCode() {
function getCurrentRefCode (line 538) | function getCurrentRefCode() {
function getRefCode (line 548) | function getRefCode() {
function deleteRefCode (line 553) | function deleteRefCode() {
FILE: packages/app/src/Utils/nip6.ts
function generateBip39Entropy (line 11) | function generateBip39Entropy(mnemonic?: string) {
function seedToMnemonic (line 23) | function seedToMnemonic(hex: string) {
function seedToPrivateKey (line 31) | function seedToPrivateKey(seed: Uint8Array) {
function entropyToPrivateKey (line 42) | async function entropyToPrivateKey(entropy: Uint8Array) {
FILE: packages/app/src/Utils/stream.ts
function getHost (line 3) | function getHost(ev?: TaggedNostrEvent) {
type StreamState (line 7) | type StreamState = "live" | "ended" | "planned"
type StreamInfo (line 9) | interface StreamInfo {
function extractStreamInfo (line 30) | function extractStreamInfo(ev?: TaggedNostrEvent) {
function sortStreamTags (line 65) | function sortStreamTags(tags: Array<string | Array<string>>) {
FILE: packages/app/src/Utils/wasm.ts
class WasmPowWorker (line 73) | class WasmPowWorker implements PowMiner {
method minePow (line 74) | minePow(ev: NostrEvent, target: number): Promise<NostrEvent> {
FILE: packages/app/src/Wallet/index.ts
type WalletConfig (line 5) | interface WalletConfig {
type WalletStoreSnapshot (line 17) | interface WalletStoreSnapshot {
class WalletStore (line 23) | class WalletStore extends ExternalStore<WalletStoreSnapshot> {
method constructor (line 27) | constructor() {
method list (line 35) | list() {
method get (line 39) | get() {
method add (line 71) | add(cfg: WalletConfig) {
method remove (line 76) | remove(id: string) {
method switch (line 88) | switch(id: string) {
method save (line 93) | save() {
method load (line 99) | load(snapshot = true) {
method free (line 109) | free() {
method takeSnapshot (line 113) | takeSnapshot(): WalletStoreSnapshot {
method #activateWallet (line 121) | async #activateWallet(cfg: WalletConfig) {
method #onWalletChange (line 129) | #onWalletChange(cfg: WalletConfig, data?: string) {
function useWallet (line 147) | function useWallet() {
function setupWebLNWalletConfig (line 163) | function setupWebLNWalletConfig(store: WalletStore) {
FILE: packages/app/src/benchmarks.ts
method minePow (line 126) | minePow(ev, target) {
method minePow (line 131) | minePow(ev, target) {
FILE: packages/app/src/chat/index.ts
type ChatType (line 23) | enum ChatType {
type ChatMessage (line 29) | interface ChatMessage {
type ChatParticipant (line 39) | interface ChatParticipant {
type Chat (line 45) | interface Chat {
type ChatSystem (line 58) | interface ChatSystem {
function chatTo (line 64) | function chatTo(e: NostrEvent) {
function inChatWith (line 74) | function inChatWith(e: NostrEvent, myPk: string) {
function selfChat (line 82) | function selfChat(e: NostrEvent, myPk: string) {
function lastReadInChat (line 86) | function lastReadInChat(id: string) {
function setLastReadIn (line 91) | function setLastReadIn(id: string, time?: number) {
function createChatLink (line 100) | function createChatLink(type: ChatType, ...params: Array<string>) {
function createEmptyChatObject (line 127) | function createEmptyChatObject(id: string) {
function useChatSystem (line 134) | function useChatSystem<T extends ChatSystem & ExternalStore<Array<Chat>>...
function useChatSystems (line 183) | function useChatSystems() {
function useChat (line 188) | function useChat(id: string) {
FILE: packages/app/src/chat/nip17.ts
function computeChatId (line 11) | function computeChatId(u: UnwrappedGift, pk: string): string | undefined {
class Nip17ChatSystem (line 31) | class Nip17ChatSystem extends ExternalStore<Array<Chat>> implements Chat...
method constructor (line 35) | constructor(cache: GiftWrapCache) {
method subscription (line 41) | subscription(session: LoginSession) {
method processEvents (line 52) | async processEvents(pub: EventPublisher, evs: Array<TaggedNostrEvent>) {
method listChats (line 60) | listChats(pk: string): Chat[] {
method createChatObj (line 81) | static createChatObj(id: string, messages: Array<UnwrappedGift>, cache...
method takeSnapshot (line 159) | takeSnapshot(p: string): Chat[] {
method #nip24Events (line 163) | #nip24Events() {
FILE: packages/app/src/index.tsx
function initSite (line 26) | async function initSite() {
method lazy (line 71) | async lazy() {
method lazy (line 78) | async lazy() {
method lazy (line 85) | async lazy() {
method lazy (line 92) | async lazy() {
method lazy (line 99) | async lazy() {
method lazy (line 106) | async lazy() {
method lazy (line 113) | async lazy() {
method lazy (line 120) | async lazy() {
method lazy (line 127) | async lazy() {
method lazy (line 134) | async lazy() {
method lazy (line 141) | async lazy() {
method lazy (line 148) | async lazy() {
method lazy (line 155) | async lazy() {
method lazy (line 167) | async lazy() {
method lazy (line 174) | async lazy() {
method lazy (line 186) | async lazy() {
method lazy (line 196) | async lazy() {
method lazy (line 203) | async lazy() {
method lazy (line 213) | async lazy() {
method lazy (line 221) | async lazy() {
method lazy (line 244) | async lazy() {
FILE: packages/app/src/service-worker.ts
type PushType (line 134) | enum PushType {
type PushNotification (line 142) | interface PushNotification {
type CompactMention (line 147) | interface CompactMention {
type CompactReaction (line 155) | interface CompactReaction {
type CompactProfile (line 164) | interface CompactProfile {
function replaceMentions (line 244) | function replaceMentions(content: string, profiles: Array<CompactProfile...
function displayNameOrDefault (line 260) | function displayNameOrDefault(p: CompactProfile) {
function makeNotification (line 267) | function makeNotification(n: PushNotification) {
function formatShort (line 295) | function formatShort(n: number) {
FILE: packages/app/tests/worker-cached.test.ts
method event (line 17) | async event(ev: TaggedNostrEvent): Promise<OkResponse> {
method query (line 20) | async query(_req: ReqCommand): Promise<TaggedNostrEvent[]> {
method delete (line 23) | async delete(_req: ReqCommand): Promise<string[]> {
type TestEntry (line 32) | type TestEntry = CachedBase & { name: string }
class TestWorkerCache (line 34) | class TestWorkerCache extends WorkerBaseCache<TestEntry> {
method constructor (line 35) | constructor() {
method name (line 39) | name() {
method maxSize (line 42) | maxSize() {
method mapper (line 46) | mapper(ev: NostrEvent): TestEntry | undefined {
function entry (line 51) | function entry(pubkey: string, created: number, loaded: number, name = "...
FILE: packages/app/vite.config.ts
method transformIndexHtml (line 21) | transformIndexHtml(html: string) {
FILE: packages/bot/src/index.ts
type BotEvents (line 18) | interface BotEvents {
type BotMessage (line 23) | interface BotMessage {
type CommandHandler (line 46) | type CommandHandler = (msg: BotMessage) => void
class SnortBot (line 48) | class SnortBot extends EventEmitter<BotEvents> {
method constructor (line 53) | constructor(
method simple (line 64) | static simple(name: string) {
method activeStreams (line 70) | get activeStreams() {
method link (line 80) | link(a: NostrLink) {
method relay (line 88) | relay(r: string) {
method profile (line 96) | profile(p: UserMetadata) {
method command (line 104) | command(cmd: string, h: CommandHandler) {
method run (line 116) | run() {
method notify (line 154) | async notify(msg: string) {
method #handleEvent (line 163) | #handleEvent(e: TaggedNostrEvent) {
method #checkActiveStreams (line 191) | #checkActiveStreams(e: TaggedNostrEvent) {
method #sendReplyTo (line 210) | async #sendReplyTo(link: NostrLink, msg: string) {
FILE: packages/shared/src/SortedMap/SortedMap.test.ts
function runTestsForMap (line 4) | function runTestsForMap(MapConstructor: any, mapName: string) {
FILE: packages/shared/src/SortedMap/SortedMap.ts
type Comparator (line 1) | type Comparator<K, V> = (a: [K, V], b: [K, V]) => number
class SortedMap (line 3) | class SortedMap<K, V extends Record<string, any>> {
method constructor (line 8) | constructor(initialEntries?: Iterable<readonly [K, V]>, compare?: stri...
method binarySearch (line 25) | private binarySearch(key: K, value: V): number {
method set (line 42) | set(key: K, value: V) {
method get (line 57) | get(key: K): V | undefined {
method last (line 61) | last(): [K, V] | undefined {
method first (line 69) | first(): [K, V] | undefined {
method reverse (line 83) | *reverse(): Iterator<[K, V]> {
method keys (line 90) | *keys(): IterableIterator<K> {
method values (line 96) | *values(): IterableIterator<V> {
method entries (line 102) | *entries(): IterableIterator<[K, V]> {
method range (line 108) | *range(options: { gte?: K; lte?: K; direction?: "asc" | "desc" } = {})...
method has (line 127) | has(key: K): boolean {
method delete (line 131) | delete(key: K): boolean {
method clear (line 142) | clear(): void {
method size (line 147) | get size(): number {
method [Symbol.iterator] (line 77) | *[Symbol.iterator](): Iterator<[K, V]> {
FILE: packages/shared/src/cache-store.ts
type CacheStore (line 5) | interface CacheStore<T = any> {
FILE: packages/shared/src/custom.d.ts
type ParsedInvoice (line 4) | interface ParsedInvoice {
type Section (line 9) | interface Section {
FILE: packages/shared/src/external-store.ts
type ExternalStoreEvents (line 3) | interface ExternalStoreEvents {
method hook (line 14) | hook(cb: () => void) {
method snapshot (line 19) | snapshot(p?: any) {
method notifyChange (line 27) | protected notifyChange(sn?: TSnapshot) {
FILE: packages/shared/src/feed-cache.ts
type HookFn (line 6) | type HookFn = () => void
type KeyedHookFilter (line 8) | interface KeyedHookFilter {
type CacheEvents (line 13) | interface CacheEvents<T> {
type CachedTable (line 18) | type CachedTable<T> = {
method constructor (line 70) | constructor(name: string, store?: CacheStore<TCached>) {
method preload (line 89) | async preload() {
method hook (line 97) | hook(fn: HookFn, key: string | undefined) {
method subscribe (line 112) | subscribe(key: string, cb: () => void): () => void {
method #notifyKeyListeners (line 130) | #notifyKeyListeners(key: string) {
method keysOnTable (line 139) | keysOnTable() {
method getFromCache (line 143) | getFromCache(key?: string) {
method get (line 155) | async get(key?: string) {
method bulkGet (line 167) | async bulkGet(keys: Array<string>) {
method set (line 180) | async set(obj: TCached) {
method bulkSet (line 195) | async bulkSet(obj: Array<TCached> | Readonly<Array<TCached>>) {
method update (line 214) | async update<TCachedWithCreated extends TCached & { created: number; loa...
method buffer (line 240) | async buffer(keys: Array<string>): Promise<Array<string>> {
method clear (line 273) | async clear() {
method snapshot (line 279) | snapshot() {
FILE: packages/shared/src/imgproxy.ts
type ImgProxySettings (line 11) | interface ImgProxySettings {
function proxyImg (line 17) | function proxyImg(url: string, settings?: ImgProxySettings, resize?: num...
FILE: packages/shared/src/index.ts
type NostrPrefix (line 16) | enum NostrPrefix {
function isPrefixTlvIdHex (line 31) | function isPrefixTlvIdHex(prefix: NostrPrefix) {
FILE: packages/shared/src/invoices.ts
type InvoiceDetails (line 4) | interface InvoiceDetails {
function decodeInvoice (line 15) | function decodeInvoice(pr: string): InvoiceDetails | undefined {
FILE: packages/shared/src/lnurl.ts
type LNURLErrorCode (line 6) | enum LNURLErrorCode {
class LNURLError (line 11) | class LNURLError extends Error {
method constructor (line 14) | constructor(code: LNURLErrorCode, msg: string) {
class LNURL (line 20) | class LNURL {
method constructor (line 28) | constructor(lnurl: string) {
method url (line 53) | get url() {
method lnurl (line 60) | get lnurl() {
method name (line 70) | get name() {
method isLNAddress (line 82) | get isLNAddress() {
method getLNAddress (line 89) | getLNAddress() {
method load (line 95) | async load() {
method getInvoice (line 111) | async getInvoice(amount: number, comment?: string, zap?: object) {
method canZap (line 155) | get canZap() {
method zapperPubkey (line 162) | get zapperPubkey() {
method maxCommentLength (line 169) | get maxCommentLength() {
method min (line 176) | get min() {
method max (line 183) | get max() {
method #validateService (line 187) | #validateService() {
type LNURLService (line 197) | interface LNURLService {
type LNURLStatus (line 207) | interface LNURLStatus {
type LNURLInvoice (line 212) | interface LNURLInvoice extends LNURLStatus {
type LNURLSuccessAction (line 217) | interface LNURLSuccessAction {
FILE: packages/shared/src/tlv.ts
type TLVEntryType (line 5) | enum TLVEntryType {
type TLVEntry (line 12) | interface TLVEntry {
function encodeTLV (line 21) | function encodeTLV(prefix: string, id: Uint8Array, relays?: string[], ki...
function encodeTLVEntries (line 35) | function encodeTLVEntries(prefix: string, ...entries: Array<TLVEntry>) {
function decodeTLV (line 66) | function decodeTLV(str: string) {
function decodeTLVEntry (line 86) | function decodeTLVEntry(type: TLVEntryType, prefix: string, data: Uint8A...
FILE: packages/shared/src/utils.ts
function unwrap (line 7) | function unwrap<T>(v: T | undefined | null): T {
function sanitizeRelayUrl (line 14) | function sanitizeRelayUrl(url: string) {
function unixNow (line 22) | function unixNow() {
function unixNowMs (line 26) | function unixNowMs() {
function jitter (line 30) | function jitter(n: number) {
function deepClone (line 34) | function deepClone<T>(obj: T) {
function deepEqual (line 42) | function deepEqual(x: any, y: any): boolean {
function countMembers (line 52) | function countMembers(a: any) {
function equalProp (line 62) | function equalProp(
function distance (line 85) | function distance(a: any, b: any): number {
function dedupe (line 114) | function dedupe<T>(v: Array<T>) {
function appendDedupe (line 118) | function appendDedupe<T>(a?: Array<T>, b?: Array<T>) {
function dedupeBy (line 122) | function dedupeBy<T>(v: Array<T>, mapper: (x: T) => string): Array<T> {
function hmacSha256 (line 141) | function hmacSha256(key: Uint8Array, ...messages: Uint8Array[]) {
function getPublicKey (line 145) | function getPublicKey(privKey: string | Uint8Array) {
function bech32ToHex (line 150) | function bech32ToHex(str: string) {
function hexToBech32 (line 159) | function hexToBech32(hrp: string, id?: string) {
function bech32ToText (line 178) | function bech32ToText(str: string) {
type NostrJson (line 184) | interface NostrJson {
function fetchNip05PubkeyWithThrow (line 190) | async function fetchNip05PubkeyWithThrow(name: string, domain: string, t...
function fetchNip05Pubkey (line 202) | async function fetchNip05Pubkey(name: string, domain: string, timeout?: ...
function fetchNostrAddress (line 211) | async function fetchNostrAddress(name: string, domain: string, timeout?:...
function fetchNostrAddressWithThrow (line 223) | async function fetchNostrAddressWithThrow(name: string, domain: string, ...
function removeUndefined (line 243) | function removeUndefined<T>(v: Array<T | undefined>) {
type Reaction (line 250) | enum Reaction {
function normalizeReaction (line 258) | function normalizeReaction(content: string) {
class OfflineError (line 269) | class OfflineError extends Error {}
function throwIfOffline (line 271) | function throwIfOffline() {
function isOffline (line 277) | function isOffline() {
function isHex (line 292) | function isHex(s?: string) {
FILE: packages/shared/src/work-queue.ts
type WorkQueueItem (line 1) | interface WorkQueueItem {
function processWorkQueue (line 7) | async function processWorkQueue(queue?: Array<WorkQueueItem>, queueDelay...
FILE: packages/system-react/example/example.tsx
function Note (line 11) | function Note({ ev }: { ev: TaggedNostrEvent }) {
function UserPosts (line 22) | function UserPosts(props: { pubkey: string }) {
function MyApp (line 40) | function MyApp() {
FILE: packages/system-react/src/TraceTimeline/TraceStatsView.tsx
function TraceStatsView (line 7) | function TraceStatsView() {
FILE: packages/system-react/src/TraceTimeline/TraceTimelineDetailPopup.tsx
type TraceTimelineDetailPopupProps (line 4) | interface TraceTimelineDetailPopupProps {
function TraceTimelineDetailPopup (line 9) | function TraceTimelineDetailPopup({ selectedEntry, onClose }: TraceTimel...
FILE: packages/system-react/src/TraceTimeline/TraceTimelineOverlay.tsx
type TraceTimelineOverlayProps (line 5) | interface TraceTimelineOverlayProps {
function TraceTimelineOverlay (line 10) | function TraceTimelineOverlay({ isOpen, onClose }: TraceTimelineOverlayP...
FILE: packages/system-react/src/TraceTimeline/TraceTimelineView.tsx
function TraceTimelineView (line 8) | function TraceTimelineView() {
FILE: packages/system-react/src/useCached.ts
type CachedObj (line 4) | interface CachedObj<T> {
function formatKey (line 10) | function formatKey(key: string): string {
function loadData (line 14) | function loadData<T>(key: string | undefined): CachedObj<T> | undefined {
function storeObj (line 29) | async function storeObj<T>(key: string | undefined, loader: () => Promis...
function useCached (line 54) | function useCached<T>(key: string | undefined, loader: () => Promise<T>,...
FILE: packages/system-react/src/useEventFeed.ts
function useEventFeed (line 5) | function useEventFeed(link: NostrLink) {
function useEventsFeed (line 15) | function useEventsFeed(id: string, links: Array<NostrLink>) {
FILE: packages/system-react/src/useEventReactions.tsx
function useEventReactions (line 11) | function useEventReactions(link: NostrLink, related: ReadonlyArray<Tagge...
FILE: packages/system-react/src/useReactions.ts
function useReactions (line 18) | function useReactions(
FILE: packages/system-react/src/useRequestBuilder.tsx
function useRequestBuilder (line 8) | function useRequestBuilder(rb: RequestBuilder): Array<TaggedNostrEvent> {
function useRequestBuilderAdvanced (line 38) | function useRequestBuilderAdvanced(rb: RequestBuilder) {
FILE: packages/system-react/src/useSystemState.tsx
function useSystemState (line 9) | function useSystemState(system: ExternalStore<SystemSnapshot>) {
FILE: packages/system-react/src/useUserProfile.ts
function useUserProfile (line 15) | function useUserProfile(pubKey?: string, ref?: RefObject<Element | null>...
FILE: packages/system-react/src/useUserSearch.tsx
function useUserSearch (line 6) | function useUserSearch() {
FILE: packages/system-svelte/src/request-builder.ts
function useRequestBuilder (line 4) | function useRequestBuilder(rb: RequestBuilder) {
FILE: packages/system-wasm/benches/basic.rs
function random_pubkey (line 9) | fn random_pubkey(rng: &mut ThreadRng) -> String {
function real_event (line 16) | fn real_event() -> Event {
function criterion_benchmark (line 34) | fn criterion_benchmark(c: &mut Criterion) {
FILE: packages/system-wasm/pkg/system_wasm.d.ts
type InitInput (line 36) | type InitInput = RequestInfo | URL | Response | BufferSource | WebAssemb...
type InitOutput (line 38) | interface InitOutput {
type SyncInitInput (line 63) | type SyncInitInput = BufferSource | WebAssembly.Module;
FILE: packages/system-wasm/pkg/system_wasm.js
function compress (line 7) | function compress(val) {
function diff_filters (line 20) | function diff_filters(prev, next) {
function expand_filter (line 32) | function expand_filter(val) {
function flat_merge (line 44) | function flat_merge(val) {
function get_diff (line 57) | function get_diff(prev, next) {
function pow (line 70) | function pow(val, target) {
function schnorr_verify (line 89) | function schnorr_verify(hash, sig, pub_key) {
function schnorr_verify_batch (line 101) | function schnorr_verify_batch(events) {
function schnorr_verify_event (line 120) | function schnorr_verify_event(event) {
function __wbg_get_imports (line 128) | function __wbg_get_imports() {
function addToExternrefTable0 (line 352) | function addToExternrefTable0(obj) {
function debugString (line 358) | function debugString(val) {
function getArrayU8FromWasm0 (line 423) | function getArrayU8FromWasm0(ptr, len) {
function getDataViewMemory0 (line 429) | function getDataViewMemory0() {
function getStringFromWasm0 (line 436) | function getStringFromWasm0(ptr, len) {
function getUint8ArrayMemory0 (line 442) | function getUint8ArrayMemory0() {
function handleError (line 449) | function handleError(f, args) {
function isLikeNone (line 458) | function isLikeNone(x) {
function passStringToWasm0 (line 462) | function passStringToWasm0(arg, malloc, realloc) {
function takeFromExternrefTable0 (line 499) | function takeFromExternrefTable0(idx) {
constant MAX_SAFARI_DECODE_BYTES (line 507) | const MAX_SAFARI_DECODE_BYTES = 2146435072;
function decodeText (line 509) | function decodeText(ptr, len) {
constant WASM_VECTOR_LEN (line 532) | let WASM_VECTOR_LEN = 0;
function __wbg_finalize_init (line 535) | function __wbg_finalize_init(instance, module) {
function __wbg_load (line 544) | async function __wbg_load(module, imports) {
function initSync (line 579) | function initSync(module) {
function __wbg_init (line 599) | async function __wbg_init(module_or_path) {
FILE: packages/system-wasm/pkg/system_wasm_bg.js
function __wbg_set_wasm (line 2) | function __wbg_set_wasm(val) {
function getObject (line 10) | function getObject(idx) {
function dropObject (line 16) | function dropObject(idx) {
function takeObject (line 22) | function takeObject(idx) {
constant WASM_VECTOR_LEN (line 28) | let WASM_VECTOR_LEN = 0
function getUint8Memory0 (line 32) | function getUint8Memory0() {
function passStringToWasm0 (line 55) | function passStringToWasm0(arg, malloc, realloc) {
function isLikeNone (line 94) | function isLikeNone(x) {
function getInt32Memory0 (line 100) | function getInt32Memory0() {
function addHeapObject (line 107) | function addHeapObject(obj) {
function getStringFromWasm0 (line 122) | function getStringFromWasm0(ptr, len) {
function getFloat64Memory0 (line 129) | function getFloat64Memory0() {
function getBigInt64Memory0 (line 138) | function getBigInt64Memory0() {
function debugString (line 145) | function debugString(val) {
function diff_filters (line 214) | function diff_filters(prev, next) {
function expand_filter (line 234) | function expand_filter(val) {
function get_diff (line 255) | function get_diff(prev, next) {
function flat_merge (line 275) | function flat_merge(val) {
function compress (line 295) | function compress(val) {
function pow (line 316) | function pow(val, target) {
function argon2 (line 337) | function argon2(password, salt) {
function handleError (line 353) | function handleError(f, args) {
function __wbindgen_object_drop_ref (line 361) | function __wbindgen_object_drop_ref(arg0) {
function __wbindgen_string_get (line 365) | function __wbindgen_string_get(arg0, arg1) {
function __wbindgen_is_object (line 374) | function __wbindgen_is_object(arg0) {
function __wbindgen_is_undefined (line 380) | function __wbindgen_is_undefined(arg0) {
function __wbindgen_in (line 385) | function __wbindgen_in(arg0, arg1) {
function __wbindgen_is_bigint (line 390) | function __wbindgen_is_bigint(arg0) {
function __wbindgen_bigint_from_u64 (line 395) | function __wbindgen_bigint_from_u64(arg0) {
function __wbindgen_jsval_eq (line 400) | function __wbindgen_jsval_eq(arg0, arg1) {
function __wbindgen_error_new (line 405) | function __wbindgen_error_new(arg0, arg1) {
function __wbindgen_object_clone_ref (line 410) | function __wbindgen_object_clone_ref(arg0) {
function __wbindgen_jsval_loose_eq (line 415) | function __wbindgen_jsval_loose_eq(arg0, arg1) {
function __wbindgen_boolean_get (line 420) | function __wbindgen_boolean_get(arg0) {
function __wbindgen_number_get (line 426) | function __wbindgen_number_get(arg0, arg1) {
function __wbindgen_number_new (line 433) | function __wbindgen_number_new(arg0) {
function __wbindgen_string_new (line 438) | function __wbindgen_string_new(arg0, arg1) {
function __wbg_getwithrefkey_5e6d9547403deab8 (line 443) | function __wbg_getwithrefkey_5e6d9547403deab8(arg0, arg1) {
function __wbg_set_841ac57cff3d672b (line 448) | function __wbg_set_841ac57cff3d672b(arg0, arg1, arg2) {
function __wbg_get_44be0491f933a435 (line 452) | function __wbg_get_44be0491f933a435(arg0, arg1) {
function __wbg_length_fff51ee6522a1a18 (line 457) | function __wbg_length_fff51ee6522a1a18(arg0) {
function __wbg_new_898a68150f225f2e (line 462) | function __wbg_new_898a68150f225f2e() {
function __wbindgen_is_function (line 467) | function __wbindgen_is_function(arg0) {
function __wbg_next_526fc47e980da008 (line 472) | function __wbg_next_526fc47e980da008(arg0) {
function __wbg_next_ddb3312ca1c4e32a (line 477) | function __wbg_next_ddb3312ca1c4e32a() {
function __wbg_done_5c1f01fb660d73b5 (line 484) | function __wbg_done_5c1f01fb660d73b5(arg0) {
function __wbg_value_1695675138684bd5 (line 489) | function __wbg_value_1695675138684bd5(arg0) {
function __wbg_iterator_97f0c81209c6c35a (line 494) | function __wbg_iterator_97f0c81209c6c35a() {
function __wbg_get_97b561fb56f034b5 (line 499) | function __wbg_get_97b561fb56f034b5() {
function __wbg_call_cb65541d95d71282 (line 506) | function __wbg_call_cb65541d95d71282() {
function __wbg_new_b51585de1b234aff (line 513) | function __wbg_new_b51585de1b234aff() {
function __wbg_set_502d29070ea18557 (line 518) | function __wbg_set_502d29070ea18557(arg0, arg1, arg2) {
function __wbg_isArray_4c24b343cb13cfb1 (line 522) | function __wbg_isArray_4c24b343cb13cfb1(arg0) {
function __wbg_instanceof_ArrayBuffer_39ac22089b74fddb (line 527) | function __wbg_instanceof_ArrayBuffer_39ac22089b74fddb(arg0) {
function __wbg_isSafeInteger_bb8e18dd21c97288 (line 538) | function __wbg_isSafeInteger_bb8e18dd21c97288(arg0) {
function __wbg_buffer_085ec1f694018c4f (line 543) | function __wbg_buffer_085ec1f694018c4f(arg0) {
function __wbg_new_8125e318e6245eed (line 548) | function __wbg_new_8125e318e6245eed(arg0) {
function __wbg_set_5cf90238115182c3 (line 553) | function __wbg_set_5cf90238115182c3(arg0, arg1, arg2) {
function __wbg_length_72e2208bbc0efc61 (line 557) | function __wbg_length_72e2208bbc0efc61(arg0) {
function __wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 (line 562) | function __wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4(arg0) {
function __wbg_new_abda76e883ba8a5f (line 573) | function __wbg_new_abda76e883ba8a5f() {
function __wbg_stack_658279fe44541cf6 (line 578) | function __wbg_stack_658279fe44541cf6(arg0, arg1) {
function __wbg_error_f851667af71bcfc6 (line 586) | function __wbg_error_f851667af71bcfc6(arg0, arg1) {
function __wbindgen_bigint_get_as_i64 (line 598) | function __wbindgen_bigint_get_as_i64(arg0, arg1) {
function __wbindgen_debug_string (line 605) | function __wbindgen_debug_string(arg0, arg1) {
function __wbindgen_throw (line 613) | function __wbindgen_throw(arg0, arg1) {
function __wbindgen_memory (line 617) | function __wbindgen_memory() {
FILE: packages/system-wasm/src/diff.rs
function diff_filter (line 10) | pub fn diff_filter(prev: &[FlatReqFilter], next: &[FlatReqFilter]) -> Ve...
function simple_diff_same (line 23) | fn simple_diff_same() {
function simple_diff_add (line 38) | fn simple_diff_add() {
function simple_diff_replace (line 65) | fn simple_diff_replace() {
FILE: packages/system-wasm/src/filter.rs
type ReqFilter (line 10) | pub struct ReqFilter {
method from (line 147) | fn from(value: Vec<&FlatReqFilter>) -> Self {
method from (line 174) | fn from(value: Vec<&ReqFilter>) -> Self {
method fmt (line 49) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type FlatReqFilter (line 58) | pub struct FlatReqFilter {
method fmt (line 97) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Distance (line 102) | pub trait Distance {
method distance (line 104) | fn distance(&self, other: &Self) -> u32;
method distance (line 112) | fn distance(&self, b: &Self) -> u32 {
method distance (line 377) | fn distance(&self, b: &Self) -> u32 {
type CanMerge (line 107) | pub trait CanMerge {
method can_merge (line 108) | fn can_merge(&self, other: &Self) -> bool;
method can_merge (line 133) | fn can_merge(&self, other: &Self) -> bool {
method can_merge (line 399) | fn can_merge(&self, other: &Self) -> bool {
function expand (line 205) | pub fn expand(f: &ReqFilter) -> Vec<FlatReqFilter> {
function from (line 371) | fn from(f: &ReqFilter) -> Vec<FlatReqFilter> {
function prop_dist (line 413) | fn prop_dist<T: Eq>(a: &Option<T>, b: &Option<T>) -> u32 {
function prop_dist_vec (line 423) | fn prop_dist_vec<T: Eq + Hash>(a: &Option<HashSet<T>>, b: &Option<HashSe...
function array_prop_append (line 443) | fn array_prop_append<T: Clone + Eq + Hash>(val: &Option<T>, arr: &mut Op...
function array_prop_append_vec (line 457) | fn array_prop_append_vec<T: Clone + Eq + Hash>(
function test_expand_filter (line 481) | fn test_expand_filter() {
FILE: packages/system-wasm/src/lib.rs
type Event (line 15) | pub struct Event {
function diff_filters (line 28) | pub fn diff_filters(prev: JsValue, next: JsValue) -> Result<JsValue, JsV...
function expand_filter (line 37) | pub fn expand_filter(val: JsValue) -> Result<JsValue, JsValue> {
function get_diff (line 45) | pub fn get_diff(prev: JsValue, next: JsValue) -> Result<JsValue, JsValue> {
function flat_merge (line 58) | pub fn flat_merge(val: JsValue) -> Result<JsValue, JsValue> {
function compress (line 66) | pub fn compress(val: JsValue) -> Result<JsValue, JsValue> {
function pow (line 74) | pub fn pow(val: JsValue, target: JsValue) -> Result<JsValue, JsValue> {
function schnorr_verify (line 88) | pub fn schnorr_verify(hash: JsValue, sig: JsValue, pub_key: JsValue) -> ...
function log_error (line 106) | fn log_error(msg: &str, err: &str) {
function schnorr_verify_event (line 116) | pub fn schnorr_verify_event(event: JsValue) -> Result<bool, JsValue> {
function schnorr_verify_batch (line 129) | pub fn schnorr_verify_batch(events: JsValue) -> Result<Box<[u8]>, JsValu...
function flat_merge_expanded (line 148) | fn flat_merge_expanded() {
FILE: packages/system-wasm/src/merge.rs
function merge (line 3) | pub fn merge<'a, T, Z>(all: Vec<&'a T>) -> Vec<Z>
function merge_once (line 19) | fn merge_once<'a, T, Z>(all: Vec<&'a T>) -> Vec<Z>
function distance (line 59) | fn distance() {
function merge_set (line 89) | fn merge_set() {
function can_merge_filters (line 113) | fn can_merge_filters() {
function flat_merge (line 137) | fn flat_merge() {
FILE: packages/system-wasm/src/pow.rs
function pow (line 3) | pub fn pow(ev: &mut Event, target: u8) {
function count_leading_zeros (line 34) | fn count_leading_zeros(hex_str: &str) -> u8 {
function hex_nibble (line 52) | fn hex_nibble(c: u8) -> u8 {
function make_id (line 62) | pub fn make_id(ev: &Event) -> String {
function make_id (line 74) | fn make_id() {
function make_id_with_special_chars_in_content (line 95) | fn make_id_with_special_chars_in_content() {
function make_id_with_escaped_backslash_in_content (line 110) | fn make_id_with_escaped_backslash_in_content() {
function count_zeros (line 125) | fn count_zeros() {
FILE: packages/system-wasm/src/verify.rs
type VerifyError (line 13) | pub enum VerifyError {
function from (line 31) | fn from(e: VerifyError) -> Self {
function decode_hex_fixed (line 38) | fn decode_hex_fixed<const N: usize>(s: &str, field: &'static str) -> Res...
function verify_event (line 62) | pub fn verify_event(event: &Event, trust_id: bool) -> Result<bool, Verif...
function verify_batch (line 95) | pub fn verify_batch(events: &[Event]) -> Vec<bool> {
function verify_batch_with_errors (line 104) | pub fn verify_batch_with_errors(events: &[Event]) -> Vec<bool> {
function make_valid_event (line 128) | fn make_valid_event() -> Event {
function verify_valid_event (line 147) | fn verify_valid_event() {
function verify_valid_event_trust_id (line 153) | fn verify_valid_event_trust_id() {
function verify_bad_sig (line 160) | fn verify_bad_sig() {
function verify_bad_pubkey_length (line 173) | fn verify_bad_pubkey_length() {
function verify_missing_sig (line 186) | fn verify_missing_sig() {
function batch_verify_mixed (line 196) | fn batch_verify_mixed() {
function batch_verify_empty (line 206) | fn batch_verify_empty() {
function batch_verify_events_with_special_content (line 211) | fn batch_verify_events_with_special_content() {
function batch_verify_user_event (line 239) | fn batch_verify_user_event() {
FILE: packages/system/src/background-loader.ts
type ProfilePriority (line 18) | type ProfilePriority = keyof typeof FlushIntervals
class DebouncedFlush (line 25) | class DebouncedFlush {
method constructor (line 30) | constructor(delayMs: number, fn: () => void) {
method schedule (line 35) | schedule() {
method cancel (line 43) | cancel() {
method constructor (line 81) | constructor(system: SystemInterface, cache: CachedTable<T>) {
method destroy (line 95) | destroy() {
method TrackKeys (line 127) | TrackKeys(pk: string | Array<string>, priority: ProfilePriority = "norma...
method UntrackKeys (line 140) | UntrackKeys(pk: string | Array<string>) {
method fetch (line 151) | async fetch(key: string, timeoutMs = 30_000) {
method #wantSetFor (line 182) | #wantSetFor(priority: ProfilePriority): Set<string> {
method #allWanted (line 194) | get #allWanted(): Array<string> {
method #dispatch (line 202) | async #dispatch(triggeredBy: ProfilePriority) {
method #loadChunk (line 265) | async #loadChunk(keys: Array<string>, chunkIndex: number): Promise<Array...
method #buildChunkSub (line 287) | #buildChunkSub(keys: Array<string>, chunkIndex: number): RequestBuilder {
function chunk (line 296) | function chunk<T>(arr: Array<T>, size: number): Array<Array<T>> {
FILE: packages/system/src/cache-relay.ts
type CacheRelay (line 7) | interface CacheRelay {
FILE: packages/system/src/cache/index.ts
type CachedBase (line 4) | interface CachedBase {
type CachedMetadata (line 21) | type CachedMetadata = CachedBase & UserMetadata
type UsersRelays (line 23) | type UsersRelays = {
type UsersFollows (line 27) | type UsersFollows = {
function mapEventToProfile (line 31) | function mapEventToProfile(ev: NostrEvent) {
FILE: packages/system/src/cache/user-follows-lists.ts
class UserFollowsCache (line 4) | class UserFollowsCache extends FeedCache<UsersFollows> {
method constructor (line 5) | constructor(store?: CacheStore<UsersFollows>) {
method key (line 9) | key(of: UsersFollows): string {
method preload (line 13) | override async preload(follows?: Array<string>): Promise<void> {
method newest (line 20) | newest(): number {
method takeSnapshot (line 26) | takeSnapshot(): Array<UsersFollows> {
method search (line 30) | async search() {
FILE: packages/system/src/cache/user-metadata.ts
class UserProfileCache (line 4) | class UserProfileCache extends FeedCache<CachedMetadata> {
method constructor (line 5) | constructor(store?: CacheStore<CachedMetadata>) {
method key (line 9) | key(of: CachedMetadata): string {
method preload (line 13) | override async preload(follows?: Array<string>): Promise<void> {
method search (line 21) | async search(q: string): Promise<Array<CachedMetadata>> {
method takeSnapshot (line 35) | takeSnapshot(): CachedMetadata[] {
FILE: packages/system/src/cache/user-relays.ts
class UserRelaysCache (line 4) | class UserRelaysCache extends FeedCache<UsersRelays> {
method constructor (line 5) | constructor(store?: CacheStore<UsersRelays>) {
method key (line 9) | key(of: UsersRelays): string {
method preload (line 13) | override async preload(follows?: Array<string>): Promise<void> {
method newest (line 20) | newest(): number {
method takeSnapshot (line 26) | takeSnapshot(): Array<UsersRelays> {
method search (line 30) | async search() {
FILE: packages/system/src/connection-cache-relay.ts
class ConnectionCacheRelay (line 9) | class ConnectionCacheRelay implements CacheRelay {
method constructor (line 12) | constructor(readonly connection: Connection) {}
method event (line 14) | async event(ev: NostrEvent): Promise<OkResponse> {
method query (line 25) | query(req: ReqCommand): Promise<Array<TaggedNostrEvent>> {
method delete (line 50) | delete(req: ReqCommand): Promise<string[]> {
FILE: packages/system/src/connection-pool.ts
type ConnectionTypeEvents (line 11) | interface ConnectionTypeEvents {
type ConnectionSubscription (line 24) | type ConnectionSubscription = Record<string, never>
type ConnectionType (line 29) | type ConnectionType = {
type ConnectionPoolEvents (line 74) | interface ConnectionPoolEvents {
type ConnectionPool (line 87) | type ConnectionPool = {
type ConnectionBuilder (line 99) | type ConnectionBuilder<T extends ConnectionType> = (
class DefaultConnectionPool (line 108) | class DefaultConnectionPool<T extends ConnectionType = Connection>
method constructor (line 130) | constructor(system: SystemInterface, builder?: ConnectionBuilder<T>) {
method getConnection (line 145) | getConnection(id: string) {
method connect (line 155) | async connect(address: string, options: RelaySettings, ephemeral: bool...
method disconnect (line 253) | disconnect(address: string) {
method broadcast (line 266) | async broadcast(ev: NostrEvent, cb?: (rsp: OkResponse) => void) {
method broadcastTo (line 290) | async broadcastTo(address: string, ev: NostrEvent): Promise<OkResponse> {
method [Symbol.iterator] (line 309) | *[Symbol.iterator]() {
FILE: packages/system/src/connection-stats.ts
class ConnectionStats (line 4) | class ConnectionStats {
FILE: packages/system/src/connection.ts
type RelaySettings (line 15) | interface RelaySettings {
class Connection (line 20) | class Connection extends EventEmitter<ConnectionTypeEvents> implements C...
method constructor (line 48) | constructor(addr: string, options: RelaySettings, ephemeral: boolean =...
method ephemeral (line 59) | get ephemeral() {
method ephemeral (line 63) | set ephemeral(v: boolean) {
method isOpen (line 68) | get isOpen() {
method isConnecting (line 72) | get isConnecting() {
method isDown (line 76) | get isDown() {
method ActiveRequests (line 80) | get ActiveRequests() {
method connect (line 84) | async connect(awaitOpen = false) {
method close (line 131) | close() {
method #onOpen (line 136) | #onOpen(wasReconnect: boolean) {
method #onClose (line 148) | #onClose(e: WebSocket.CloseEvent) {
method #reconnectTimer (line 174) | #reconnectTimer(e: WebSocket.CloseEvent) {
method #onMessage (line 191) | #onMessage(e: WebSocket.MessageEvent) {
method #onError (line 260) | #onError(e: WebSocket.ErrorEvent) {
method sendEvent (line 279) | sendEvent(e: NostrEvent) {
method publish (line 291) | async publish(e: NostrEvent, timeout = 5000) {
method supportsNip (line 340) | supportsNip(n: number) {
method request (line 349) | request(cmd: ReqCommand, cbSent?: () => void): void {
method closeRequest (line 363) | closeRequest(id: string) {
method activeSubscriptions (line 372) | get activeSubscriptions() {
method maxSubscriptions (line 376) | get maxSubscriptions() {
method #sendRequestCommand (line 380) | #sendRequestCommand(cmd: ReqCommand) {
method #reset (line 389) | #reset() {
method sendRaw (line 411) | sendRaw(obj: object) {
method #send (line 415) | #send(obj: object) {
method #sendPendingRaw (line 426) | #sendPendingRaw() {
method #sendOnWire (line 435) | #sendOnWire(obj: unknown) {
method #onAuthAsync (line 445) | async #onAuthAsync(challenge: string): Promise<void> {
method #setupEphemeral (line 490) | #setupEphemeral() {
FILE: packages/system/src/encryption/index.ts
type MessageEncryptorVersion (line 1) | enum MessageEncryptorVersion {
type MessageEncryptor (line 6) | interface MessageEncryptor {
FILE: packages/system/src/encryption/nip44.ts
method utf8Decode (line 18) | utf8Decode(bytes: Uint8Array) {
method getConversationKey (line 22) | getConversationKey(privkeyA: string, pubkeyB: string): Uint8Array {
method getMessageKeys (line 27) | getMessageKeys(conversationKey: Uint8Array, nonce: Uint8Array) {
method calcPaddedLen (line 36) | calcPaddedLen(len: number): number {
method writeU16BE (line 44) | writeU16BE(num: number) {
method pad (line 52) | pad(plaintext: string): Uint8Array {
method unpad (line 60) | unpad(padded: Uint8Array): string {
method hmacAad (line 73) | hmacAad(key: Uint8Array, message: Uint8Array, aad: Uint8Array) {
method decodePayload (line 85) | decodePayload(payload: string) {
function encrypt_v2 (line 111) | function encrypt_v2(plaintext: string, conversationKey: Uint8Array, nonc...
function decrypt_v2 (line 119) | function decrypt_v2(ciphertext: Uint8Array, nonce: Uint8Array, mac: Uint...
function decrypt_v1 (line 127) | function decrypt_v1(ciphertext: Uint8Array, nonce: Uint8Array, conversat...
FILE: packages/system/src/encryption/pin-encrypted.ts
class InvalidPinError (line 10) | class InvalidPinError extends Error {
method constructor (line 11) | constructor() {
method fromPayload (line 35) | static fromPayload(o: object) {
class PinEncrypted (line 54) | class PinEncrypted extends KeyStorage {
method constructor (line 59) | constructor(enc: PinEncryptedPayload) {
method value (line 64) | get value() {
method shouldUnlock (line 69) | override shouldUnlock(): boolean {
method unlock (line 73) | override async unlock(pin: string) {
method toPayload (line 113) | toPayload() {
method create (line 117) | static async create(content: string, pin: string) {
class NotEncrypted (line 147) | class NotEncrypted extends KeyStorage {
method constructor (line 150) | constructor(key: string) {
method value (line 155) | get value() {
method shouldUnlock (line 159) | override shouldUnlock(): boolean {
method unlock (line 163) | override unlock(code: string): Promise<void> {
method toPayload (line 167) | override toPayload(): Object {
type PinEncryptedPayload (line 174) | interface PinEncryptedPayload {
FILE: packages/system/src/event-builder.ts
class EventBuilder (line 7) | class EventBuilder {
method pubkey (line 22) | get pubkey() {
method fromLink (line 29) | fromLink(link: NostrLink) {
method jitter (line 41) | jitter(n: number) {
method kind (line 46) | kind(k: EventKind) {
method content (line 51) | content(c: string) {
method createdAt (line 56) | createdAt(n: number) {
method pubKey (line 61) | pubKey(k: string) {
method tag (line 66) | tag(t: Array<string>): EventBuilder {
method pow (line 73) | pow(target: number, miner?: PowMiner) {
method processContent (line 87) | processContent() {
method build (line 99) | build() {
method buildAndSign (line 120) | async buildAndSign(pk: string | EventSigner) {
method #mine (line 130) | async #mine(ev: NostrEvent) {
method #validate (line 138) | #validate() {
method #replaceMention (line 147) | #replaceMention(match: string) {
method #addHashtag (line 160) | #addHashtag(match: string) {
FILE: packages/system/src/event-ext.ts
type Thread (line 12) | interface Thread {
type EventType (line 20) | enum EventType {
constant MAX_VERIFIED_CACHE (line 38) | const MAX_VERIFIED_CACHE = 50_000
method getRootPubKey (line 53) | static getRootPubKey(e: NostrEvent): string {
method sign (line 70) | static sign(e: NostrEvent, key: string) {
method verify (line 87) | static verify(e: NostrEvent) {
method isVerified (line 114) | static isVerified(e: NostrEvent) {
method markVerified (line 123) | static markVerified(e: NostrEvent) {
method createId (line 130) | static createId(e: NostrEvent | NotSignedNostrEvent) {
method minePow (line 138) | static minePow(e: NostrEvent, target: number) {
method forPubKey (line 145) | static forPubKey(pk: string, kind: EventKind) {
method extractThread (line 157) | static extractThread(ev: NostrEvent): Thread | undefined {
method fixupEvent (line 177) | static fixupEvent(e: NostrEvent) {
method getType (line 187) | static getType(kind: number) {
method isReplaceable (line 200) | static isReplaceable(kind: number) {
method isAddressable (line 205) | static isAddressable(kind: number) {
method isWellFormed (line 217) | static isWellFormed(ev: NostrEvent) {
method isValid (line 232) | static isValid(ev: NostrEvent) {
method keyOf (line 245) | static keyOf(e: NostrEvent) {
FILE: packages/system/src/event-kind.ts
type EventKind (line 1) | enum EventKind {
FILE: packages/system/src/event-publisher.ts
type EventBuilderHook (line 27) | type EventBuilderHook = (ev: EventBuilder) => EventBuilder
class EventPublisher (line 29) | class EventPublisher {
method constructor (line 35) | constructor(signer: EventSigner, pubKey: string) {
method signer (line 40) | get signer() {
method nip7 (line 47) | static async nip7() {
method privateKey (line 60) | static privateKey(privateKey: string | Uint8Array) {
method supports (line 65) | supports(t: SignerSupports) {
method pubKey (line 69) | get pubKey() {
method pow (line 76) | pow(target: number, miner?: PowMiner) {
method #eb (line 83) | #eb(k: EventKind) {
method #sign (line 88) | async #sign(eb: EventBuilder) {
method nip4Encrypt (line 92) | async nip4Encrypt(content: string, otherKey: string) {
method nip4Decrypt (line 96) | async nip4Decrypt(content: string, otherKey: string) {
method nip42Auth (line 100) | async nip42Auth(challenge: string, relay: string) {
method muted (line 112) | async muted(pub: Array<string>, priv: Array<string>) {
method pinned (line 128) | async pinned(notes: Array<ToNostrEventTag>) {
method bookmarks (line 140) | async bookmarks(notes: Array<ToNostrEventTag>) {
method metadata (line 148) | async metadata(obj: UserMetadata) {
method note (line 157) | async note(msg: string, fnExtra?: EventBuilderHook) {
method zap (line 172) | async zap(
method reply (line 199) | async reply(replyTo: TaggedNostrEvent, msg: string, fnExtra?: EventBui...
method react (line 214) | async react(evRef: NostrEvent, content = "+") {
method relayList (line 223) | async relayList(relays: Array<FullRelaySettings> | Record<string, Rela...
method contactList (line 240) | async contactList(tags: Array<[string, string]>, relays?: Record<strin...
method delete (line 252) | async delete(id: string) {
method repost (line 261) | async repost(note: NostrEvent) {
method decryptDm (line 268) | async decryptDm(note: NostrEvent) {
method sendDm (line 284) | async sendDm(content: string, to: string) {
method generic (line 291) | async generic(fnHook: EventBuilderHook) {
method appData (line 298) | async appData(data: object, id: string) {
method giftWrap (line 308) | async giftWrap(inner: NostrEvent, explicitP?: string, powTarget?: numb...
method unwrapGift (line 327) | async unwrapGift(gift: NostrEvent) {
method createUnsigned (line 339) | createUnsigned(kind: EventKind, content: string, fnHook: EventBuilderH...
method sealRumor (line 351) | async sealRumor(inner: NotSignedNostrEvent, toKey: string) {
method unsealRumor (line 360) | async unsealRumor(inner: NostrEvent) {
FILE: packages/system/src/filter-cache-layer.ts
type EventCache (line 5) | interface EventCache {
type FilterCacheLayer (line 9) | interface FilterCacheLayer {
FILE: packages/system/src/impl/nip10.ts
class Nip10 (line 10) | class Nip10 {
method replyTo (line 14) | static replyTo(ev: TaggedNostrEvent, eb: EventBuilder) {
method parseThread (line 42) | static parseThread(ev: NostrEvent) {
method fromLinks (line 56) | static fromLinks(links: Array<NostrLink>) {
method linkToTag (line 105) | static linkToTag(link: NostrLink, withScope?: LinkScope) {
method scopeToMarker (line 141) | static scopeToMarker(scope?: LinkScope) {
FILE: packages/system/src/impl/nip11.ts
type RelayInfoDocument (line 3) | interface RelayInfoDocument {
class Nip11 (line 31) | class Nip11 {
method loadRelayDocument (line 32) | static async loadRelayDocument(url: string) {
FILE: packages/system/src/impl/nip18.ts
class Nip18 (line 7) | class Nip18 {
method linkToTag (line 8) | static linkToTag(link: NostrLink) {
FILE: packages/system/src/impl/nip22.ts
class Nip22 (line 5) | class Nip22 {
method rootScopeOf (line 10) | static rootScopeOf(other: NostrEvent) {
method replyTo (line 17) | static replyTo(other: NostrEvent, eb: EventBuilder) {
method parseThread (line 43) | static parseThread(ev: NostrEvent): Thread | undefined {
method fromLinks (line 51) | static fromLinks(links: Array<NostrLink>, ev: NostrEvent) {
method linkToTag (line 110) | static linkToTag(link: NostrLink) {
FILE: packages/system/src/impl/nip25.ts
class Nip25 (line 8) | class Nip25 {
method reactToEvent (line 12) | static reactToEvent(ev: TaggedNostrEvent | NostrEvent) {
FILE: packages/system/src/impl/nip4.ts
class Nip4WebCryptoEncryptor (line 6) | class Nip4WebCryptoEncryptor implements MessageEncryptor {
method constructor (line 8) | constructor(
method getSharedSecret (line 13) | getSharedSecret(privateKey: string, publicKey: string) {
method encryptData (line 19) | async encryptData(content: string) {
method decryptData (line 36) | async decryptData(payload: string) {
method #importKey (line 52) | async #importKey(sharedSecet: Uint8Array) {
FILE: packages/system/src/impl/nip44.ts
class Nip44Encryptor (line 4) | class Nip44Encryptor implements MessageEncryptor {
method constructor (line 5) | constructor(
method encryptData (line 10) | encryptData(plaintext: string) {
method decryptData (line 15) | decryptData(payload: string): string {
FILE: packages/system/src/impl/nip46.ts
constant NIP46_KIND (line 13) | const NIP46_KIND = 24_133
constant PERMS (line 15) | const PERMS =
type Nip46Request (line 18) | interface Nip46Request {
type Nip46Response (line 24) | interface Nip46Response {
type QueueObj (line 30) | interface QueueObj {
type Nip46Events (line 36) | interface Nip46Events {
class Nip46Signer (line 41) | class Nip46Signer extends EventEmitter<Nip46Events> implements EventSign...
method constructor (line 58) | constructor(config: string, insideSigner?: EventSigner) {
method supports (line 81) | get supports(): string[] {
method relays (line 85) | get relays() {
method privateKey (line 89) | get privateKey(): string | undefined {
method isBunker (line 96) | get isBunker() {
method init (line 105) | async init(autoConnect = true) {
method close (line 157) | async close() {
method describe (line 167) | async describe() {
method getPubKey (line 172) | async getPubKey() {
method nip4Encrypt (line 179) | async nip4Encrypt(content: string, otherKey: string) {
method nip4Decrypt (line 184) | async nip4Decrypt(content: string, otherKey: string) {
method nip44Encrypt (line 189) | nip44Encrypt(_content: string, _key: string): Promise<string> {
method nip44Decrypt (line 193) | nip44Decrypt(_content: string, _otherKey: string): Promise<string> {
method sign (line 197) | async sign(ev: NostrEvent) {
method createAccount (line 218) | async createAccount(name: string, domain: string, email?: string) {
method #disconnect (line 226) | async #disconnect() {
method #connect (line 230) | async #connect(pk: string) {
method #onReply (line 235) | async #onReply(e: NostrEvent) {
method #rpc (line 300) | async #rpc(method: string, params: Array<string>) {
method #sendCommand (line 331) | async #sendCommand(payload: Nip46Request | Nip46Response, target: stri...
FILE: packages/system/src/impl/nip55.ts
class Nip55Signer (line 7) | class Nip55Signer implements EventSigner {
method init (line 11) | init(): Promise<void> {
method getPubKey (line 16) | async getPubKey() {
method nip4Encrypt (line 24) | async nip4Encrypt(content: string, key: string) {
method nip4Decrypt (line 28) | async nip4Decrypt(content: string, otherKey: string) {
method nip44Encrypt (line 32) | async nip44Encrypt(content: string, key: string) {
method nip44Decrypt (line 36) | async nip44Decrypt(content: string, otherKey: string) {
method sign (line 40) | async sign(ev: NostrEvent | NotSignedNostrEvent) {
method supports (line 45) | get supports(): string[] {
method #call (line 49) | #call(
FILE: packages/system/src/impl/nip57.ts
function getInvoice (line 16) | function getInvoice(zap: NostrEvent): InvoiceDetails | undefined {
function parseZap (line 24) | function parseZap(zapReceipt: NostrEvent): ParsedZap {
type ParsedZap (line 95) | interface ParsedZap {
FILE: packages/system/src/impl/nip7.ts
type NostrEncryptor (line 8) | interface NostrEncryptor {
type Window (line 12) | interface Window {
class Nip7Signer (line 22) | class Nip7Signer implements EventSigner {
method supports (line 23) | get supports(): string[] {
method init (line 31) | init(): Promise<void> {
method getPubKey (line 35) | async getPubKey(): Promise<string> {
method nip4Encrypt (line 42) | async nip4Encrypt(content: string, key: string): Promise<string> {
method nip4Decrypt (line 51) | async nip4Decrypt(content: string, otherKey: string): Promise<string> {
method nip44Encrypt (line 60) | async nip44Encrypt(content: string, key: string): Promise<string> {
method nip44Decrypt (line 71) | async nip44Decrypt(content: string, otherKey: string): Promise<string> {
method sign (line 82) | async sign(ev: NostrEvent): Promise<NostrEvent> {
FILE: packages/system/src/impl/nip90.ts
type DVMJobState (line 12) | enum DVMJobState {
type DVMJobEvents (line 52) | interface DVMJobEvents {
type DVMJobInput (line 77) | interface DVMJobInput {
class DVMJobRequest (line 102) | class DVMJobRequest extends EventEmitter<DVMJobEvents> {
method constructor (line 165) | constructor(kind: number, response?: number) {
method id (line 175) | get id() {
method state (line 179) | get state() {
method state (line 183) | private set state(v: DVMJobState) {
method addInput (line 191) | addInput(input: DVMJobInput) {
method setParam (line 202) | setParam(k: string, v: string) {
method removeParam (line 211) | removeParam(k: string) {
method setEncrypted (line 219) | setEncrypted(encrypted: boolean) {
method setServiceProvider (line 227) | setServiceProvider(p: string) {
method setOutput (line 238) | setOutput(mime: string) {
method setBid (line 246) | setBid(amount: number) {
method addRelay (line 254) | addRelay(relay: string) {
method removeRelay (line 267) | removeRelay(relay: string) {
method buildEvent (line 278) | async buildEvent(signer: EventSigner): Promise<NostrEvent> {
method #handleReplyEvents (line 329) | #handleReplyEvents(evs: Array<TaggedNostrEvent>) {
method request (line 376) | async request(signer: EventSigner, system: SystemInterface, relays?: A...
method abort (line 414) | abort(system: SystemInterface) {
FILE: packages/system/src/impl/nip92.ts
class Nip92 (line 6) | class Nip92 {
method parse (line 10) | static parse(tag: Array<string>) {
method parseAll (line 17) | static parseAll(tags: Array<Array<string>>) {
function readNip94TagsFromIMeta (line 26) | function readNip94TagsFromIMeta(tag: Array<string>) {
function nip94TagsToIMeta (line 34) | function nip94TagsToIMeta(meta: Nip94Tags) {
FILE: packages/system/src/impl/nip94.ts
class Nip94 (line 6) | class Nip94 {
method parse (line 7) | static parse(tags: Array<Array<string>>) {
type Nip94Tags (line 12) | interface Nip94Tags {
function readNip94Tags (line 33) | function readNip94Tags(tags: Array<Array<string>>) {
function addExtensionToNip94Url (line 112) | function addExtensionToNip94Url(meta: Nip94Tags) {
FILE: packages/system/src/negentropy/accumulator.ts
class Accumulator (line 4) | class Accumulator {
method constructor (line 7) | constructor() {
method setToZero (line 11) | setToZero() {
method add (line 15) | add(otherBuf: Uint8Array) {
method negate (line 38) | negate() {
method getFingerprint (line 51) | getFingerprint(n: number) {
FILE: packages/system/src/negentropy/negentropy-flow.ts
type NegentropyFlowEvents (line 9) | interface NegentropyFlowEvents {
class NegentropyFlow (line 24) | class NegentropyFlow extends EventEmitter<NegentropyFlowEvents> {
method constructor (line 33) | constructor(id: string, conn: ConnectionType, set: Array<TaggedNostrEv...
method start (line 51) | start() {
method #handleMessage (line 56) | #handleMessage(msg: Array<any>) {
method #cleanup (line 96) | #cleanup(error = false) {
FILE: packages/system/src/negentropy/negentropy.ts
class Negentropy (line 15) | class Negentropy {
method constructor (line 22) | constructor(storage: NegentropyStorageVector, frameSizeLimit = 0) {
method #bound (line 32) | #bound(timestamp: number, id?: Uint8Array) {
method initiate (line 36) | initiate() {
method setInitiator (line 48) | setInitiator() {
method reconcile (line 52) | reconcile(query: WrappedBuffer | Uint8Array): [Uint8Array | undefined,...
method splitRange (line 182) | async splitRange(lower: number, upper: number, upperBound: VectorStora...
method #renderOutput (line 228) | #renderOutput(o: WrappedBuffer) {
method exceededFrameSizeLimit (line 232) | exceededFrameSizeLimit(n: number) {
method decodeTimestampIn (line 237) | decodeTimestampIn(encoded: Uint8Array | WrappedBuffer) {
method decodeBound (line 249) | decodeBound(encoded: Uint8Array | WrappedBuffer) {
method encodeTimestampOut (line 260) | encodeTimestampOut(timestamp: number) {
method encodeBound (line 272) | encodeBound(key: VectorStorageItem) {
method getMinimalBound (line 282) | getMinimalBound(prev: VectorStorageItem, curr: VectorStorageItem) {
FILE: packages/system/src/negentropy/utils.ts
constant PROTOCOL_VERSION (line 4) | const PROTOCOL_VERSION = 0x61 // Version 1
constant FINGERPRINT_SIZE (line 5) | const FINGERPRINT_SIZE = 16
type Mode (line 7) | enum Mode {
function decodeVarInt (line 16) | function decodeVarInt(buf: Uint8Array | WrappedBuffer) {
function encodeVarInt (line 35) | function encodeVarInt(n: number) {
function getByte (line 50) | function getByte(buf: WrappedBuffer) {
function getBytes (line 54) | function getBytes(buf: WrappedBuffer | Uint8Array, n: number) {
function compareUint8Array (line 65) | function compareUint8Array(a: Uint8Array, b: Uint8Array) {
function itemCompare (line 77) | function itemCompare(a: VectorStorageItem, b: VectorStorageItem) {
FILE: packages/system/src/negentropy/vector-storage.ts
type VectorStorageItem (line 5) | interface VectorStorageItem {
class NegentropyStorageVector (line 12) | class NegentropyStorageVector {
method constructor (line 16) | constructor(other?: Array<VectorStorageItem>) {
method idSize (line 23) | get idSize() {
method insert (line 27) | insert(timestamp: number, id: string) {
method seal (line 34) | seal() {
method unseal (line 47) | unseal() {
method size (line 51) | size() {
method getItem (line 56) | getItem(i: number) {
method iterate (line 62) | iterate(begin: number, end: number, cb: (item: VectorStorageItem, inde...
method findLowerBound (line 71) | findLowerBound(begin: number, end: number, bound: VectorStorageItem) {
method fingerprint (line 78) | fingerprint(begin: number, end: number) {
method #checkSealed (line 89) | #checkSealed() {
method #checkBounds (line 93) | #checkBounds(begin: number, end: number) {
method #binarySearch (line 97) | #binarySearch(arr: Array<VectorStorageItem>, first: number, last: numb...
FILE: packages/system/src/negentropy/wrapped-buffer.ts
class WrappedBuffer (line 1) | class WrappedBuffer {
method constructor (line 5) | constructor(buffer?: Uint8Array) {
method unwrap (line 10) | unwrap() {
method capacity (line 14) | get capacity() {
method length (line 18) | get length() {
method set (line 22) | set(val: ArrayLike<number>, offset?: number) {
method append (line 27) | append(val: ArrayLike<number>) {
method clear (line 35) | clear() {
method resize (line 40) | resize(newSize: number) {
method shift (line 49) | shift() {
method shiftN (line 56) | shiftN(n = 1) {
FILE: packages/system/src/nips.ts
type Nips (line 1) | enum Nips {
FILE: packages/system/src/nostr-link.ts
type ToNostrEventTag (line 24) | interface ToNostrEventTag {
class NostrHashtagLink (line 32) | class NostrHashtagLink implements ToNostrEventTag {
method constructor (line 33) | constructor(readonly tag: string) {}
method equals (line 35) | equals(other: ToNostrEventTag): boolean {
method toEventTag (line 40) | toEventTag() {
class UnknownTag (line 48) | class UnknownTag implements ToNostrEventTag {
method constructor (line 49) | constructor(readonly value: Array<string>) {}
method equals (line 51) | equals(other: ToNostrEventTag): boolean {
method toEventTag (line 56) | toEventTag(): string[] | undefined {
type LinkScope (line 64) | enum LinkScope {
class NostrLink (line 86) | class NostrLink implements ToNostrEventTag {
method constructor (line 96) | constructor(
method encode (line 141) | encode(type?: NostrPrefix): string {
method tagKey (line 163) | get tagKey() {
method toEventTag (line 173) | toEventTag() {
method matchesEvent (line 177) | matchesEvent(ev: NostrEvent) {
method isReplyToThis (line 200) | isReplyToThis(ev: NostrEvent) {
method referencesThis (line 212) | referencesThis(ev: NostrEvent) {
method equals (line 234) | equals(other: NostrLink) {
method fromTag (line 238) | static fromTag(tag: Array<string>, author?: string, kind?: number) {
method tryFromTag (line 274) | static tryFromTag(tag: Array<string>, author?: string, kind?: number) {
method fromTags (line 282) | static fromTags(tags: ReadonlyArray<Array<string>>) {
method fromAllTags (line 297) | static fromAllTags(tags: ReadonlyArray<Array<string>>): Array<ToNostrE...
method replyTags (line 312) | static replyTags(tags: ReadonlyArray<Array<string>>): Array<NostrLink> {
method fromEvent (line 319) | static fromEvent(ev: TaggedNostrEvent | NostrEvent) {
method profile (line 337) | static profile(pk: string, relays?: Array<string>) {
method publicKey (line 341) | static publicKey(pk: string, relays?: Array<string>) {
function tryParseNostrLink (line 346) | function tryParseNostrLink(link: string, prefixHint?: NostrPrefix): Nost...
function isNostrLink (line 354) | function isNostrLink(link: string) {
function trimNostrLink (line 360) | function trimNostrLink(link: string) {
function parseNostrLink (line 368) | function parseNostrLink(link: string, prefixHint?: NostrPrefix): NostrLi...
FILE: packages/system/src/nostr-system.ts
class NostrSystem (line 28) | class NostrSystem extends SystemBase implements SystemInterface {
method constructor (line 38) | constructor(props: Partial<SystemConfig>) {
method Init (line 109) | async Init(follows?: Array<string>) {
method PreloadSocialGraph (line 119) | async PreloadSocialGraph(follows?: Array<string>, root?: string) {
method ConnectToRelay (line 153) | async ConnectToRelay(address: string, options: RelaySettings) {
method ConnectEphemeralRelay (line 157) | ConnectEphemeralRelay(address: string) {
method DisconnectRelay (line 161) | DisconnectRelay(address: string) {
method GetQuery (line 165) | GetQuery(id: string): QueryLike | undefined {
method Fetch (line 169) | Fetch(req: RequestBuilder, cb?: (evs: Array<TaggedNostrEvent>) => void) {
method Query (line 173) | Query(req: RequestBuilder): QueryLike {
method HandleEvent (line 177) | HandleEvent(subId: string, ev: TaggedNostrEvent) {
method BroadcastEvent (line 182) | async BroadcastEvent(ev: NostrEvent, cb?: (rsp: OkResponse) => void): ...
method WriteOnceToRelay (line 187) | async WriteOnceToRelay(address: string, ev: NostrEvent): Promise<OkRes...
method takeSnapshot (line 192) | takeSnapshot(): SystemSnapshot {
FILE: packages/system/src/nostr.ts
type NostrEvent (line 3) | interface NostrEvent {
type TaggedNostrEvent (line 13) | interface TaggedNostrEvent extends NostrEvent {
type ReqCommand (line 25) | type ReqCommand = [cmd: "REQ", id: string, ...filters: Array<ReqFilter>]
type ReqFilter (line 30) | interface ReqFilter {
type UserMetadata (line 52) | type UserMetadata = {
type FullRelaySettings (line 64) | interface FullRelaySettings {
type NotSignedNostrEvent (line 69) | type NotSignedNostrEvent = Omit<NostrEvent, "sig">
type OkResponse (line 71) | interface OkResponse {
FILE: packages/system/src/note-collection.ts
type NoteStoreSnapshotData (line 7) | type NoteStoreSnapshotData = Array<TaggedNostrEvent>
type NoteStoreHook (line 8) | type NoteStoreHook = () => void
type NoteStoreHookRelease (line 9) | type NoteStoreHookRelease = () => void
type OnEventCallback (line 10) | type OnEventCallback = (e: Readonly<Array<TaggedNostrEvent>>) => void
type OnEventCallbackRelease (line 11) | type OnEventCallbackRelease = () => void
type OnEoseCallback (line 12) | type OnEoseCallback = (c: string) => void
type OnEoseCallbackRelease (line 13) | type OnEoseCallbackRelease = () => void
type NosteStoreEvents (line 15) | interface NosteStoreEvents {
method snapshot (line 43) | get snapshot() {
method onChange (line 54) | protected onChange(changes: Array<TaggedNostrEvent>): void {
method flushEmit (line 65) | flushEmit() {
method onClear (line 79) | protected onClear() {
class KeyedReplaceableNoteStore (line 93) | class KeyedReplaceableNoteStore extends HookedNoteStore {
method constructor (line 97) | constructor(fn: (ev: TaggedNostrEvent) => string) {
method add (line 102) | add(ev: TaggedNostrEvent | Array<TaggedNostrEvent>) {
method clear (line 122) | clear() {
method takeSnapshot (line 127) | takeSnapshot() {
class NoteCollection (line 135) | class NoteCollection extends KeyedReplaceableNoteStore {
method constructor (line 136) | constructor() {
FILE: packages/system/src/outbox/index.ts
type AuthorsRelaysCache (line 5) | interface AuthorsRelaysCache {
type PickedRelays (line 12) | interface PickedRelays {
type EventFetcher (line 17) | type EventFetcher = {
FILE: packages/system/src/outbox/outbox-model.ts
class OutboxModel (line 19) | class OutboxModel extends BaseRequestRouter {
method constructor (line 24) | constructor(relays: AuthorsRelaysCache, fetcher: EventFetcher) {
method fromSystem (line 30) | static fromSystem(system: SystemInterface) {
method pickTopRelays (line 41) | pickTopRelays(authors: Array<string>, pickN: number, type: "write" | "...
method forRequest (line 102) | forRequest(filter: ReqFilter, pickN?: number): Array<ReqFilter> {
method forFlatRequest (line 143) | forFlatRequest(input: Array<FlatReqFilter>, pickN?: number): Array<Fla...
method forReply (line 181) | async forReply(ev: NostrEvent, pickN?: number) {
method forReplyTo (line 191) | async forReplyTo(pk: string, pickN?: number | undefined): Promise<stri...
method updateRelayLists (line 205) | async updateRelayLists(authors: Array<string>) {
FILE: packages/system/src/outbox/relay-loader.ts
class RelayMetadataLoader (line 6) | class RelayMetadataLoader extends BackgroundLoader<UsersRelays> {
method name (line 7) | override name(): string {
method onEvent (line 11) | override onEvent(e: Readonly<TaggedNostrEvent>): UsersRelays | undefin...
method getExpireCutoff (line 26) | override getExpireCutoff(): number {
method buildSub (line 30) | protected override buildSub(missing: string[]): RequestBuilder {
FILE: packages/system/src/pow-util.ts
type NostrPowEvent (line 4) | interface NostrPowEvent {
function minePow (line 14) | function minePow(e: NostrPowEvent, target: number) {
function createId (line 30) | function createId(e: NostrPowEvent) {
function countLeadingZeros (line 35) | function countLeadingZeros(hex: string) {
FILE: packages/system/src/pow-worker.ts
type PowWorkerMessage (line 5) | interface PowWorkerMessage {
FILE: packages/system/src/pow.ts
type PowMiner (line 4) | interface PowMiner {
type PowQueue (line 8) | interface PowQueue {
class PowWorker (line 14) | class PowWorker implements PowMiner {
method constructor (line 18) | constructor(workerPath: string) {
method minePow (line 37) | minePow(ev: NostrEvent, target: number) {
FILE: packages/system/src/profile-cache.ts
class ProfileLoaderService (line 9) | class ProfileLoaderService extends BackgroundLoader<CachedMetadata> {
method name (line 10) | override name(): string {
method onEvent (line 14) | override onEvent(e: Readonly<TaggedNostrEvent>): CachedMetadata | unde...
method getExpireCutoff (line 18) | override getExpireCutoff(): number {
method buildSub (line 22) | override buildSub(missing: string[]): RequestBuilder {
FILE: packages/system/src/query-manager.ts
type QueryManagerEvents (line 23) | interface QueryManagerEvents {
type PendingTrace (line 29) | interface PendingTrace {
class QueryManager (line 39) | class QueryManager extends EventEmitter<QueryManagerEvents> {
method constructor (line 67) | constructor(system: SystemInterface) {
method destroy (line 81) | destroy() {
method #setupConnectionListeners (line 93) | #setupConnectionListeners() {
method get (line 123) | get(id: string) {
method query (line 130) | query(req: RequestBuilder): Query {
method handleEvent (line 162) | handleEvent(sub: string, ev: TaggedNostrEvent) {
method fetch (line 169) | async fetch(req: RequestBuilder, cb?: (evs: Array<TaggedNostrEvent>) =...
method #send (line 207) | async #send(q: Query, filters: Array<ReqFilter>) {
method #canSendQuery (line 279) | #canSendQuery(c: ConnectionType, q: BuiltRawReqFilter, query: Query) {
method createTrace (line 309) | createTrace(query: Query, connection: ConnectionType, filters: BuiltRa...
method sendTrace (line 351) | sendTrace(query: Query, trace: QueryTrace, connection: ConnectionType,...
method #handleSync (line 396) | #handleSync(
method #fallbackSync (line 428) | #fallbackSync(
method #syncSince (line 454) | #syncSince(
method #syncRangeSync (line 474) | #syncRangeSync(
method #retryPendingTraces (line 511) | #retryPendingTraces(connection: ConnectionType) {
method #sendToRelays (line 525) | async #sendToRelays(q: Query, qSend: BuiltRawReqFilter) {
method #cleanup (line 575) | #cleanup() {
method [Symbol.iterator] (line 201) | *[Symbol.iterator]() {
FILE: packages/system/src/query-optimizer/index.ts
type FlatReqFilter (line 9) | interface FlatReqFilter {
type Optimizer (line 27) | interface Optimizer {
FILE: packages/system/src/query-optimizer/request-expander.ts
function expandFilter (line 8) | function expandFilter(f: ReqFilter): Array<FlatReqFilter> {
function resultSetId (line 49) | function resultSetId(f: ReqFilter) {
FILE: packages/system/src/query-optimizer/request-merger.ts
function canMergeFilters (line 5) | function canMergeFilters(a: FlatReqFilter | ReqFilter, b: FlatReqFilter ...
function mergeSimilar (line 12) | function mergeSimilar(filters: Array<ReqFilter>): Array<ReqFilter> {
function simpleMerge (line 38) | function simpleMerge(filters: Array<ReqFilter>) {
function filterIncludes (line 67) | function filterIncludes(bigger: ReqFilter, smaller: ReqFilter) {
function flatMerge (line 94) | function flatMerge(all: Array<FlatReqFilter>): Array<ReqFilter> {
FILE: packages/system/src/query-optimizer/request-splitter.ts
function diffFilters (line 4) | function diffFilters(prev: Array<FlatReqFilter>, next: Array<FlatReqFilt...
FILE: packages/system/src/query.ts
type QueryTraceState (line 10) | enum QueryTraceState {
type QueryTraceEvent (line 24) | interface QueryTraceEvent {
type QueryTraceEvents (line 33) | interface QueryTraceEvents {
class QueryTrace (line 40) | class QueryTrace extends EventEmitter<QueryTraceEvents> {
method constructor (line 46) | constructor(
method #setState (line 58) | #setState(state: QueryTraceState) {
method currentState (line 74) | get currentState() {
method queued (line 78) | queued() {
method sent (line 82) | sent() {
method sentSync (line 86) | sentSync() {
method syncFallback (line 90) | syncFallback() {
method eose (line 94) | eose() {
method remoteClosed (line 98) | remoteClosed() {
method close (line 102) | close() {
method drop (line 106) | drop() {
method timeout (line 110) | timeout() {
method finished (line 117) | get finished() {
type QueryEvents (line 128) | interface QueryEvents {
class Query (line 145) | class Query extends EventEmitter<QueryEvents> {
method constructor (line 213) | constructor(req: RequestBuilder) {
method addRequest (line 233) | addRequest(req: RequestBuilder) {
method isOpen (line 249) | isOpen() {
method canRemove (line 253) | canRemove() {
method filters (line 260) | get filters() {
method feed (line 264) | get feed() {
method snapshot (line 268) | get snapshot() {
method traces (line 272) | get traces() {
method leaveOpen (line 276) | get leaveOpen() {
method flush (line 283) | flush() {
method addTrace (line 291) | addTrace(trace: QueryTrace) {
method removeTrace (line 317) | removeTrace(traceId: string) {
method addEvent (line 324) | addEvent(sub: string, e: TaggedNostrEvent) {
method cancel (line 346) | cancel() {
method uncancel (line 350) | uncancel() {
method cleanup (line 354) | cleanup() {
method closeQuery (line 362) | closeQuery() {
method progress (line 374) | get progress() {
method start (line 386) | start() {
method #emitFilters (line 398) | async #emitFilters() {
FILE: packages/system/src/relays.ts
function parseRelayTag (line 5) | function parseRelayTag(tag: Array<string>) {
function parseRelayTags (line 19) | function parseRelayTags(tag: Array<Array<string>>) {
function parseRelaysFromKind (line 23) | function parseRelaysFromKind(ev: NostrEvent) {
function settingsToRelayTag (line 59) | function settingsToRelayTag(rx: FullRelaySettings) {
FILE: packages/system/src/request-builder.ts
type BuiltRawReqFilter (line 12) | interface BuiltRawReqFilter {
type RequestBuilderOptions (line 19) | interface RequestBuilderOptions {
class RequestBuilder (line 69) | class RequestBuilder {
method constructor (line 75) | constructor(id: string) {
method numFilters (line 81) | get numFilters() {
method filterBuilders (line 85) | get filterBuilders() {
method options (line 89) | get options() {
method clear (line 93) | clear() {
method add (line 101) | add(other: RequestBuilder) {
method withFilter (line 105) | withFilter() {
method withBareFilter (line 111) | withBareFilter(f: ReqFilter) {
method withOptions (line 117) | withOptions(opt: RequestBuilderOptions) {
method withRelays (line 128) | withRelays(relays: Array<string>) {
method buildRaw (line 135) | buildRaw(): Array<ReqFilter> {
class RequestFilterBuilder (line 143) | class RequestFilterBuilder {
method constructor (line 146) | constructor(f?: ReqFilter) {
method filter (line 150) | get filter() {
method relay (line 159) | relay(u: string | Array<string>) {
method ids (line 169) | ids(ids: Array<string>) {
method authors (line 174) | authors(authors?: Array<string>) {
method kinds (line 183) | kinds(kinds?: Array<EventKind>) {
method since (line 189) | since(since?: number) {
method until (line 195) | until(until?: number) {
method limit (line 201) | limit(limit?: number) {
method tag (line 207) | tag(key: "e" | "p" | "d" | "t" | "r" | "a" | "g" | string, value?: Arr...
method tags (line 216) | tags(tags: Array<ToNostrEventTag>) {
method search (line 226) | search(keyword?: string) {
method link (line 235) | link(link: NostrLink | ToNostrEventTag) {
method replyToLink (line 264) | replyToLink(links: Array<NostrLink>) {
method build (line 280) | build(model?: RequestRouter, options?: RequestBuilderOptions): Array<R...
FILE: packages/system/src/request-matcher.ts
function eventMatchesFilter (line 3) | function eventMatchesFilter(ev: NostrEvent, filter: ReqFilter) {
function isRequestSatisfied (line 22) | function isRequestSatisfied(filter: ReqFilter, results: Array<TaggedNost...
FILE: packages/system/src/request-router.ts
type RequestRouter (line 8) | interface RequestRouter {
method forAllRequest (line 56) | forAllRequest(filters: Array<ReqFilter>) {
FILE: packages/system/src/request-trim.ts
function trimFilters (line 6) | function trimFilters(filters: Array<ReqFilter>) {
FILE: packages/system/src/signer.ts
type SignerSupports (line 9) | type SignerSupports = "nip04" | "nip44" | string
type EventSigner (line 11) | interface EventSigner {
function decryptSigner (line 25) | async function decryptSigner(content: string, signer: EventSigner, other...
class PrivateKeySigner (line 31) | class PrivateKeySigner implements EventSigner {
method constructor (line 35) | constructor(privateKey: string | Uint8Array) {
method random (line 47) | static random() {
method supports (line 52) | get supports(): string[] {
method privateKey (line 56) | get privateKey() {
method init (line 60) | init(): Promise<void> {
method getPubKey (line 64) | getPubKey(): string {
method nip4Encrypt (line 68) | async nip4Encrypt(content: string, otherKey: string) {
method nip4Decrypt (line 73) | async nip4Decrypt(content: string, otherKey: string) {
method nip44Encrypt (line 78) | async nip44Encrypt(content: string, otherKey: string) {
method nip44Decrypt (line 83) | async nip44Decrypt(content: string, otherKey: string) {
method sign (line 88) | sign(ev: NostrEvent): Promise<NostrEvent> {
FILE: packages/system/src/sync/diff-sync.ts
type TagDiff (line 14) | interface TagDiff {
type DiffSyncTagsEvents (line 19) | interface DiffSyncTagsEvents {
class DiffSyncTags (line 26) | class DiffSyncTags extends EventEmitter<DiffSyncTagsEvents> {
method constructor (line 33) | constructor(
method value (line 44) | get value() {
method tags (line 51) | get tags() {
method encryptedTags (line 58) | get encryptedTags() {
method add (line 74) | add(tag: Array<string> | Array<Array<string>>, encrypted = false) {
method remove (line 85) | remove(tag: Array<string> | Array<Array<string>>, encrypted = false) {
method update (line 96) | update(tag: Array<string> | Array<Array<string>>, encrypted = false) {
method replace (line 107) | replace(tag: Array<Array<string>>, encrypted = false) {
method sync (line 115) | async sync(signer: EventSigner | undefined, system: SystemInterface) {
method persist (line 124) | async persist(signer: EventSigner, system: SystemInterface, content?: ...
method #afterSync (line 145) | async #afterSync(signer: EventSigner | undefined) {
method #nextEvent (line 153) | #nextEvent(content?: string): NotSignedNostrEvent {
method #applyChanges (line 179) | #applyChanges(tags: Array<Array<string>>, changes: Array<TagDiff>) {
FILE: packages/system/src/sync/json-in-event-sync.ts
type JsonEventSyncEvents (line 13) | interface JsonEventSyncEvents {
class JsonEventSync (line 17) | class JsonEventSync<T> extends EventEmitter<JsonEventSyncEvents> {
method constructor (line 22) | constructor(
method json (line 32) | get json(): T {
method hasPendingChanges (line 39) | get hasPendingChanges(): boolean {
method setJson (line 54) | setJson(val: T) {
method sync (line 59) | async sync(signer: EventSigner | undefined, system: SystemInterface) {
method persist (line 78) | async persist(signer: EventSigner, system: SystemInterface) {
method updateJson (line 107) | async updateJson(val: T, signer: EventSigner, system: SystemInterface) {
FILE: packages/system/src/sync/range-sync.ts
type RangeSyncEvents (line 10) | interface RangeSyncEvents {
class RangeSync (line 18) | class RangeSync extends EventEmitter<RangeSyncEvents> {
method constructor (line 24) | private constructor() {
method forSystem (line 28) | static forSystem(system: SystemInterface) {
method forFetcher (line 34) | static forFetcher(fn: SystemInterface["Fetch"]) {
method setWindowSize (line 43) | setWindowSize(n: number) {
method setStartPoint (line 54) | setStartPoint(n: number) {
method sync (line 64) | async sync(filter: ReqFilter) {
FILE: packages/system/src/sync/safe-sync.ts
class SafeSync (line 24) | class SafeSync {
method constructor (line 29) | constructor(readonly link: NostrLink) {}
method value (line 34) | get value() {
method didSync (line 38) | get didSync() {
method sync (line 45) | async sync(system: SystemInterface) {
method setBase (line 57) | setBase(ev: NostrEvent) {
method update (line 68) | async update(
method #signEvent (line 88) | async #signEvent(next: NotSignedNostrEvent, signer: EventSigner) {
method #sync (line 95) | async #sync(system: SystemInterface) {
method #checkForUpdate (line 112) | #checkForUpdate(ev: NostrEvent, mustExist: boolean) {
FILE: packages/system/src/system-base.ts
method config (line 14) | get config() {
method constructor (line 18) | constructor(props: Partial<SystemConfig>) {
method profileCache (line 39) | get profileCache(): CachedTable<CachedMetadata> {
method optimizer (line 46) | get optimizer(): Optimizer {
method userFollowsCache (line 50) | get userFollowsCache(): CachedTable<UsersFollows> {
method cacheRelay (line 54) | get cacheRelay(): CacheRelay | undefined {
method checkSigs (line 61) | get checkSigs(): boolean {
method checkSigs (line 65) | set checkSigs(v: boolean) {
FILE: packages/system/src/system.ts
type QueryLike (line 17) | type QueryLike = {
type NostrSystemEvents (line 42) | interface NostrSystemEvents {
type SystemConfig (line 50) | interface SystemConfig {
type SystemInterface (line 113) | interface SystemInterface {
type SystemSnapshot (line 222) | interface SystemSnapshot {
FILE: packages/system/src/text.ts
type FragmentType (line 17) | enum FragmentType {
type ParsedFragment (line 72) | interface ParsedFragment {
type Fragment (line 92) | type Fragment = string | ParsedFragment
function mapFragments (line 102) | function mapFragments(
function splitAndParseRegex (line 131) | function splitAndParseRegex(
function extractLinks (line 184) | function extractLinks(fragments: Fragment[]) {
function extractMentions (line 237) | function extractMentions(fragments: Fragment[]) {
function extractCashuTokens (line 241) | function extractCashuTokens(fragments: Fragment[]) {
function extractInvoices (line 250) | function extractInvoices(fragments: Fragment[]) {
function extractHashtags (line 254) | function extractHashtags(fragments: Fragment[]) {
function extractTagRefs (line 262) | function extractTagRefs(fragments: Fragment[], tags: Array<Array<string>...
function extractCustomEmoji (line 286) | function extractCustomEmoji(fragments: Fragment[], tags: Array<Array<str...
function extractMarkdownCode (line 305) | function extractMarkdownCode(fragments: Fragment[]): (string | ParsedFra...
function extractInlineCode (line 320) | function extractInlineCode(fragments: Fragment[]): (string | ParsedFragm...
function parseIMeta (line 328) | function parseIMeta(tags: Array<Array<string>>) {
function transformText (line 341) | function transformText(body: string, tags: Array<Array<string>>) {
FILE: packages/system/src/trace-timeline.ts
type TimelineEntry (line 4) | interface TimelineEntry {
type TraceTimelineSnapshot (line 10) | interface TraceTimelineSnapshot {
class TraceTimeline (line 18) | class TraceTimeline extends ExternalStore<TraceTimelineSnapshot> {
method constructor (line 26) | constructor() {
method setEnabled (line 33) | setEnabled(enabled: boolean) {
method enabled (line 42) | get enabled() {
method takeSnapshot (line 46) | takeSnapshot(): TraceTimelineSnapshot {
method addTrace (line 56) | addTrace(event: QueryTraceEvent, queryName?: string) {
method getEntries (line 100) | getEntries(): ReadonlyArray<TimelineEntry> {
method getEntriesInRange (line 107) | getEntriesInRange(startTime: number, endTime: number): Array<TimelineE...
method getEntriesForRelay (line 114) | getEntriesForRelay(relayAddress: string): Array<TimelineEntry> {
method getEntriesForQuery (line 121) | getEntriesForQuery(queryId: string): Array<TimelineEntry> {
method clear (line 128) | clear() {
method exportGoogleTrace (line 139) | exportGoogleTrace() {
FILE: packages/system/src/user-state.ts
type UserStateOptions (line 18) | interface UserStateOptions<T> {
type UserStateObject (line 27) | interface UserStateObject<TAppData> {
type UserStateChangeType (line 34) | enum UserStateChangeType {
type UserStateEvents (line 43) | interface UserStateEvents {
class UserState (line 50) | class UserState<TAppData> extends EventEmitter<UserStateEvents> {
method constructor (line 66) | constructor(
method didInit (line 103) | get didInit() {
method destroy (line 107) | destroy() {
method init (line 116) | async init(signer: EventSigner | undefined, system: SystemInterface) {
method #performInit (line 128) | async #performInit(signer: EventSigner | undefined, system: SystemInte...
method signer (line 165) | get signer() {
method version (line 169) | get version() {
method pendingChanges (line 176) | get pendingChanges() {
method profile (line 235) | get profile() {
method relays (line 242) | get relays() {
method follows (line 255) | get follows() {
method appdata (line 267) | get appdata() {
method muted (line 274) | get muted() {
method follow (line 285) | follow(link: NostrLink) {
method unfollow (line 302) | unfollow(link: NostrLink) {
method replaceFollows (line 319) | replaceFollows(links: Array<NostrLink>) {
method saveContacts (line 334) | async saveContacts() {
method saveAll (line 344) | async saveAll() {
method addRelay (line 377) | addRelay(addr: string, settings: RelaySettings) {
method #addRelay (line 382) | #addRelay(addr: string, settings: RelaySettings) {
method removeRelay (line 397) | removeRelay(addr: string) {
method updateRelay (line 411) | updateRelay(addr: string, settings: RelaySettings) {
method saveRelays (line 429) | async saveRelays() {
method setAppData (line 438) | setAppData(data: TAppData) {
method saveAppData (line 448) | async saveAppData() {
method addToList (line 462) | addToList(kind: EventKind, links: ToNostrEventTag | Array<ToNostrEvent...
method removeFromList (line 478) | removeFromList(kind: EventKind, links: ToNostrEventTag | Array<ToNostr...
method saveList (line 491) | async saveList(kind: EventKind, content?: string) {
method mute (line 500) | mute(link: NostrLink) {
method unmute (line 507) | unmute(link: NostrLink) {
method isOnList (line 511) | isOnList(kind: EventKind, link: ToNostrEventTag) {
method getList (line 520) | getList(kind: EventKind): Array<ToNostrEventTag> {
method serialize (line 525) | serialize(): UserStateObject<TAppData> {
method checkIsStandardList (line 534) | checkIsStandardList(kind: EventKind) {
method #ensureInit (line 548) | #ensureInit() {
method #relaysObject (line 554) | #relaysObject() {
FILE: packages/system/src/utils.ts
function findTag (line 5) | function findTag(e: NostrEvent, tag: string) {
function findWholeTag (line 12) | function findWholeTag(e: NostrEvent, tag: string) {
function reqFilterEq (line 19) | function reqFilterEq(a: FlatReqFilter | ReqFilter, b: FlatReqFilter | Re...
function flatFilterEq (line 36) | function flatFilterEq(a: FlatReqFilter, b: FlatReqFilter): boolean {
function splitByUrl (line 57) | function splitByUrl(str: string) {
function extensionToMime (line 79) | function extensionToMime(e: string) {
FILE: packages/system/tests/background-loader.test.ts
function sleep (line 21) | function sleep(ms: number) {
function pubkey (line 26) | function pubkey(seed: number): string {
function pubkeys (line 31) | function pubkeys(count: number, offset = 0): string[] {
type TestEntry (line 39) | type TestEntry = { pubkey: string; loaded: number; created: number }
class MemoryCache (line 41) | class MemoryCache extends EventEmitter<CacheEvents<TestEntry>> implement...
method key (line 45) | key(of: TestEntry) {
method getFromCache (line 49) | getFromCache(k?: string) {
method get (line 53) | async get(k?: string) {
method bulkGet (line 57) | async bulkGet(keys: string[]) {
method set (line 61) | async set(obj: TestEntry) {
method bulkSet (line 67) | async bulkSet(objs: TestEntry[] | Readonly<TestEntry[]>) {
method update (line 76) | async update(m: TestEntry) {
method buffer (line 83) | async buffer(keys: string[]) {
method preload (line 88) | async preload() {}
method keysOnTable (line 89) | keysOnTable() {
method clear (line 92) | async clear() {
method snapshot (line 95) | snapshot() {
method search (line 98) | async search() {
method subscribe (line 102) | subscribe(key: string, cb: () => void): () => void {
method #notifyKey (line 118) | #notifyKey(key: string) {
method seed (line 126) | seed(pk: string, loadedMs: number, createdSecs = 1) {
class TestLoader (line 136) | class TestLoader extends BackgroundLoader<TestEntry> {
method constructor (line 143) | constructor(system: SystemInterface, cache: CachedTable<TestEntry>) {
method name (line 147) | override name() {
method onEvent (line 150) | override onEvent(e: Readonly<TaggedNostrEvent>): TestEntry | undefined {
method getExpireCutoff (line 153) | override getExpireCutoff() {
method buildSub (line 157) | override buildSub(missing: string[]): RequestBuilder {
type FetchCallback (line 171) | type FetchCallback = (evs: TaggedNostrEvent[]) => Promise<void>
function makeMockSystem (line 173) | function makeMockSystem(onFetch: (req: RequestBuilder, authors: string[]...
class SpyLoader (line 285) | class SpyLoader extends TestLoader {
method buildSub (line 286) | override buildSub(missing: string[]) {
class ChunkSpyLoader (line 337) | class ChunkSpyLoader extends BackgroundLoader<TestEntry> {
method name (line 338) | override name() {
method onEvent (line 341) | override onEvent() {
method getExpireCutoff (line 344) | override getExpireCutoff() {
method buildSub (line 347) | override buildSub(missing: string[]): RequestBuilder {
method name (line 379) | override name() {
method onEvent (line 382) | override onEvent() {
method getExpireCutoff (line 385) | override getExpireCutoff() {
method buildSub (line 388) | override buildSub(missing: string[]): RequestBuilder {
class ChunkSpyLoader (line 378) | class ChunkSpyLoader extends BackgroundLoader<TestEntry> {
method name (line 338) | override name() {
method onEvent (line 341) | override onEvent() {
method getExpireCutoff (line 344) | override getExpireCutoff() {
method buildSub (line 347) | override buildSub(missing: string[]): RequestBuilder {
method name (line 379) | override name() {
method onEvent (line 382) | override onEvent() {
method getExpireCutoff (line 385) | override getExpireCutoff() {
method buildSub (line 388) | override buildSub(missing: string[]): RequestBuilder {
class IdSpyLoader (line 418) | class IdSpyLoader extends BackgroundLoader<TestEntry> {
method name (line 419) | override name() {
method onEvent (line 422) | override onEvent() {
method getExpireCutoff (line 425) | override getExpireCutoff() {
method buildSub (line 428) | override buildSub(_missing: string[]): RequestBuilder {
class InFlightSpyLoader (line 474) | class InFlightSpyLoader extends BackgroundLoader<TestEntry> {
method name (line 475) | override name() {
method onEvent (line 478) | override onEvent() {
method getExpireCutoff (line 481) | override getExpireCutoff() {
method buildSub (line 484) | override buildSub(missing: string[]): RequestBuilder {
class FreshSkipLoader (line 533) | class FreshSkipLoader extends BackgroundLoader<TestEntry> {
method name (line 534) | override name() {
method onEvent (line 537) | override onEvent() {
method getExpireCutoff (line 541) | override getExpireCutoff() {
method buildSub (line 544) | override buildSub(missing: string[]): RequestBuilder {
class ExpiredLoader (line 580) | class ExpiredLoader extends BackgroundLoader<TestEntry> {
method name (line 581) | override name() {
method onEvent (line 584) | override onEvent() {
method getExpireCutoff (line 588) | override getExpireCutoff() {
method buildSub (line 591) | override buildSub(missing: string[]): RequestBuilder {
class BlacklistSpyLoader (line 628) | class BlacklistSpyLoader extends BackgroundLoader<TestEntry> {
method name (line 629) | override name() {
method onEvent (line 632) | override onEvent() {
method getExpireCutoff (line 635) | override getExpireCutoff() {
method buildSub (line 638) | override buildSub(missing: string[]): RequestBuilder {
FILE: packages/system/tests/feed-cache-subscribe.test.ts
type TestItem (line 17) | type TestItem = { id: string; value: string; created: number; loaded: nu...
class TestFeedCache (line 19) | class TestFeedCache extends FeedCache<TestItem> {
method constructor (line 20) | constructor() {
method key (line 24) | key(of: TestItem) {
method takeSnapshot (line 28) | takeSnapshot() {
method search (line 32) | async search() {
function item (line 37) | function item(id: string, value = "v1", created = 1, loaded = Date.now()...
FILE: packages/system/tests/node.ts
function test (line 7) | async function test() {
FILE: packages/system/tests/note-collection-comprehensive.test.ts
function ev (line 5) | function ev(
function sleep (line 16) | function sleep(ms: number) {
FILE: packages/system/tests/pin-encrypted.test.ts
constant TEST_KEY (line 5) | const TEST_KEY = "000000000000000000000000000000000000000000000000000000...
constant TEST_PIN (line 6) | const TEST_PIN = "correct-pin"
constant WRONG_PIN (line 7) | const WRONG_PIN = "wrong-pin"
FILE: packages/system/tests/query-comprehensive.test.ts
function createEv (line 6) | function createEv(id: string, kind = 1, created_at = 100, pubkey = "aa",...
function sleep (line 10) | function sleep(ms: number) {
FILE: packages/system/tests/query-manager-comprehensive.test.ts
function createEv (line 18) | function createEv(
function sleep (line 29) | function sleep(ms: number) {
function makeRb (line 33) | function makeRb(id: string, groupingDelay = 0) {
class MockConnection (line 40) | class MockConnection extends EventEmitter<ConnectionTypeEvents> implemen...
method constructor (line 51) | constructor(address: string, open = true) {
method isOpen (line 57) | get isOpen() {
method isDown (line 60) | get isDown() {
method activeSubscriptions (line 63) | get activeSubscriptions() {
method maxSubscriptions (line 66) | get maxSubscriptions() {
method open (line 70) | open() {
method markDown (line 76) | markDown() {
method connect (line 80) | async connect() {}
method close (line 81) | close() {}
method publish (line 82) | async publish(ev: NostrEvent): Promise<OkResponse> {
method request (line 86) | request(req: ReqCommand, cbSent?: () => void) {
method closeRequest (line 93) | closeRequest(id: string) {
method sendRaw (line 96) | sendRaw(_obj: object) {}
class MockPool (line 99) | class MockPool extends EventEmitter<ConnectionPoolEvents> implements Con...
method add (line 102) | add(c: MockConnection) {
method getConnection (line 106) | getConnection(id: string) {
method connect (line 110) | async connect(address: string) {
method disconnect (line 113) | disconnect(_a: string) {}
method broadcast (line 114) | async broadcast(): Promise<OkResponse[]> {
method broadcastTo (line 117) | async broadcastTo(_a: string, ev: NostrEvent): Promise<OkResponse> {
method [Symbol.iterator] (line 121) | *[Symbol.iterator]() {
function makeSystem (line 126) | function makeSystem(pool: MockPool, opts?: { cacheRelay?: any }): System...
FILE: packages/system/tests/query-manager-race.test.ts
class MockConnection (line 38) | class MockConnection extends EventEmitter<ConnectionTypeEvents> implemen...
method constructor (line 54) | constructor(address: string) {
method isOpen (line 60) | get isOpen() {
method isDown (line 64) | get isDown() {
method activeSubscriptions (line 68) | get activeSubscriptions() {
method maxSubscriptions (line 72) | get maxSubscriptions() {
method connect (line 76) | async connect() {}
method close (line 77) | close() {}
method publish (line 78) | async publish(_ev: NostrEvent): Promise<OkResponse> {
method request (line 82) | request(req: ReqCommand, cbSent?: () => void) {
method closeRequest (line 92) | closeRequest(_id: string) {}
method sendRaw (line 93) | sendRaw(_obj: object) {}
method open (line 96) | open() {
class MockPool (line 112) | class MockPool extends EventEmitter<ConnectionPoolEvents> implements Con...
method add (line 115) | add(conn: MockConnection) {
method getConnection (line 119) | getConnection(id: string): ConnectionType | undefined {
method connect (line 123) | async connect(address: string, _options: RelaySettings, _ephemeral: bo...
method disconnect (line 127) | disconnect(_address: string) {}
method broadcast (line 129) | async broadcast(_ev: NostrEvent): Promise<OkResponse[]> {
method broadcastTo (line 133) | async broadcastTo(_address: string, _ev: NostrEvent): Promise<OkRespon...
method connectionOpened (line 144) | connectionOpened(conn: MockConnection) {
method [Symbol.iterator] (line 137) | *[Symbol.iterator]() {
function makeSystem (line 153) | function makeSystem(pool: MockPool): SystemInterface {
function sleep (line 213) | function sleep(ms: number) {
function makeRb (line 217) | function makeRb(id: string) {
FILE: packages/system/tests/query-system-edge-cases.test.ts
function ev (line 34) | function ev(id: string, kind: number, created_at: number, pubkey = "aa",...
function sleep (line 38) | function sleep(ms: number) {
function makeRb (line 42) | function makeRb(id: string, groupingDelay = 0) {
class MockConnection (line 53) | class MockConnection extends EventEmitter<ConnectionTypeEvents> implemen...
method constructor (line 63) | constructor(address: string, open = true) {
method isOpen (line 69) | get isOpen() {
method isDown (line 72) | get isDown() {
method activeSubscriptions (line 75) | get activeSubscriptions() {
method maxSubscriptions (line 78) | get maxSubscriptions() {
method open (line 82) | open() {
method markDown (line 88) | markDown() {
method connect (line 92) | async connect() {}
method close (line 93) | close() {}
method publish (line 94) | async publish(ev: NostrEvent): Promise<OkResponse> {
method request (line 98) | request(req: ReqCommand, cbSent?: () => void) {
method closeRequest (line 105) | closeRequest(_id: string) {}
method sendRaw (line 106) | sendRaw(_obj: object) {}
class MockPool (line 113) | class MockPool extends EventEmitter<ConnectionPoolEvents> implements Con...
method add (line 116) | add(c: MockConnection) {
method getConnection (line 120) | getConnection(id: string) {
method connect (line 124) | async connect(address: string) {
method disconnect (line 127) | disconnect(_a: string) {}
method broadcast (line 128) | async broadcast(): Promise<OkResponse[]> {
method broadcastTo (line 131) | async broadcastTo(_a: string, ev: NostrEvent): Promise<OkResponse> {
method fireConnected (line 135) | fireConnected(conn: MockConnection) {
method [Symbol.iterator] (line 139) | *[Symbol.iterator]() {
function makeSystem (line 148) | function makeSystem(pool: MockPool): SystemInterface {
FILE: packages/wallet/src/AlbyWallet.ts
type OAuthToken (line 17) | interface OAuthToken {
class AlbyWallet (line 28) | class AlbyWallet extends EventEmitter<WalletEvents> implements LNWallet {
method constructor (line 30) | constructor(token: OAuthToken) {
method isReady (line 35) | isReady() {
method canAutoLogin (line 39) | canAutoLogin() {
method canGetInvoices (line 43) | canGetInvoices() {
method canGetBalance (line 47) | canGetBalance() {
method canCreateInvoice (line 51) | canCreateInvoice() {
method canPayInvoice (line 55) | canPayInvoice() {
method getInfo (line 59) | async getInfo() {
method login (line 64) | async login() {
method close (line 68) | close() {
method getBalance (line 72) | async getBalance() {
method createInvoice (line 78) | async createInvoice(req: InvoiceRequest) {
method payInvoice (line 87) | async payInvoice(pr: string) {
method getInvoices (line 101) | async getInvoices() {
method #fetch (line 114) | async #fetch<T>(path: string, method: "GET" | "POST" = "GET", body?: o...
method #refreshToken (line 135) | async #refreshToken() {
type GetBalanceResponse (line 164) | interface GetBalanceResponse {
type CreateInvoiceResponse (line 170) | interface CreateInvoiceResponse {
type PayInvoiceResponse (line 176) | interface PayInvoiceResponse {
type GetInvoiceResponse (line 186) | interface GetInvoiceResponse {
type GetUserResponse (line 200) | interface GetUserResponse {
FILE: packages/wallet/src/LNDHub.ts
class LNDHubWallet (line 22) | class LNDHubWallet extends EventEmitter<WalletEvents> implements LNWallet {
method constructor (line 29) | constructor(url: string) {
method isReady (line 46) | isReady(): boolean {
method canAutoLogin (line 50) | canAutoLogin() {
method canGetInvoices (line 54) | canGetInvoices() {
method canGetBalance (line 58) | canGetBalance() {
method canCreateInvoice (line 62) | canCreateInvoice() {
method canPayInvoice (line 66) | canPayInvoice() {
method close (line 70) | close(): Promise<boolean> {
method getInfo (line 74) | async getInfo() {
method login (line 79) | async login() {
method getBalance (line 91) | async getBalance(): Promise<Sats> {
method createInvoice (line 98) | async createInvoice(req: InvoiceRequest) {
method payInvoice (line 115) | async payInvoice(pr: string) {
method getInvoices (line 134) | async getInvoices(): Promise<WalletInvoice[]> {
method getJson (line 154) | private async getJson<T>(method: "GET" | "POST", path: string, body?: ...
type AuthResponse (line 175) | interface AuthResponse {
type GetBalanceResponse (line 181) | interface GetBalanceResponse {
type UserInvoicesResponse (line 187) | interface UserInvoicesResponse {
type PayInvoiceResponse (line 199) | interface PayInvoiceResponse {
type ErrorResponse (line 206) | interface ErrorResponse {
FILE: packages/wallet/src/NostrWalletConnect.ts
type WalletConnectConfig (line 18) | interface WalletConnectConfig {
type QueueObj (line 24) | interface QueueObj {
type WalletConnectResponse (line 29) | interface WalletConnectResponse<T> {
type GetInfoResponse (line 46) | interface GetInfoResponse {
type ListTransactionsResponse (line 56) | interface ListTransactionsResponse {
type MakeInvoiceResponse (line 73) | interface MakeInvoiceResponse {
class NostrConnectWallet (line 80) | class NostrConnectWallet extends EventEmitter<WalletEvents> implements L...
method constructor (line 88) | constructor(cfg: string) {
method parseConfigUrl (line 94) | static parseConfigUrl(url: string) {
method canAutoLogin (line 103) | canAutoLogin(): boolean {
method isReady (line 107) | isReady(): boolean {
method canGetInvoices (line 111) | canGetInvoices() {
method canGetBalance (line 115) | canGetBalance() {
method canCreateInvoice (line 119) | canCreateInvoice() {
method canPayInvoice (line 123) | canPayInvoice() {
method getInfo (line 127) | async getInfo() {
method login (line 166) | async login() {
method close (line 188) | async close() {
method getBalance (line 193) | async getBalance() {
method createInvoice (line 203) | async createInvoice(req: InvoiceRequest) {
method payInvoice (line 223) | async payInvoice(pr: string) {
method getInvoices (line 239) | async getInvoices() {
method #onReply (line 266) | async #onReply(sub: string, e: NostrEvent) {
method #rpc (line 296) | async #rpc<T>(method: string, params: Record<string, string | number |...
FILE: packages/wallet/src/WebLN.ts
class WebLNWallet (line 20) | class WebLNWallet extends EventEmitter<WalletEvents> implements LNWallet {
method isReady (line 21) | isReady(): boolean {
method canCreateInvoice (line 25) | canCreateInvoice() {
method canPayInvoice (line 29) | canPayInvoice() {
method canGetInvoices (line 33) | canGetInvoices() {
method canGetBalance (line 37) | canGetBalance() {
method canAutoLogin (line 41) | canAutoLogin(): boolean {
method getInfo (line 45) | async getInfo(): Promise<WalletInfo> {
method login (line 61) | async login(): Promise<boolean> {
method close (line 68) | close(): Promise<boolean> {
method getBalance (line 72) | async getBalance(): Promise<Sats> {
method createInvoice (line 81) | async createInvoice(req: InvoiceRequest): Promise<WalletInvoice> {
method payInvoice (line 103) | async payInvoice(pr: string): Promise<WalletInvoice> {
method getInvoices (line 124) | getInvoices(): Promise<WalletInvoice[]> {
FILE: packages/wallet/src/index.ts
type WalletKind (line 10) | enum WalletKind {
type WalletErrorCode (line 19) | enum WalletErrorCode {
class WalletError (line 29) | class WalletError extends Error {
method constructor (line 32) | constructor(c: WalletErrorCode, msg: string) {
type WalletInfo (line 43) | interface WalletInfo {
type Login (line 57) | interface Login {
type InvoiceRequest (line 63) | interface InvoiceRequest {
type WalletInvoiceState (line 69) | enum WalletInvoiceState {
type WalletInvoice (line 76) | interface WalletInvoice {
function prToWalletInvoice (line 88) | function prToWalletInvoice(pr: string) {
type Sats (line 103) | type Sats = number
type MilliSats (line 104) | type MilliSats = number
type WalletEvents (line 106) | interface WalletEvents {
type LNWallet (line 110) | type LNWallet = EventEmitter<WalletEvents> & {
function loadWallet (line 134) | async function loadWallet(kind: WalletKind, data: string | undefined) {
FILE: packages/wallet/src/zapper.ts
type ZapTarget (line 5) | interface ZapTarget {
type ZapTargetResult (line 18) | interface ZapTargetResult {
type ZapTargetLoaded (line 27) | interface ZapTargetLoaded {
class Zapper (line 32) | class Zapper {
method constructor (line 36) | constructor(
method fromEvent (line 45) | static fromEvent(ev: NostrEvent) {
method send (line 89) | async send(wallet: LNWallet | undefined, targets: Array<ZapTarget>, am...
method load (line 152) | async load(targets: Array<ZapTarget>) {
method canZap (line 169) | canZap() {
method maxComment (line 176) | maxComment() {
method minAmount (line 187) | minAmount() {
method maxAmount (line 194) | maxAmount() {
method #getService (line 198) | async #getService(t: ZapTarget) {
FILE: packages/worker-relay/src/debug.ts
function debugLog (line 2) | function debugLog(scope: string, msg: string, ...args: Array<any>) {
function setLogging (line 7) | function setLogging(v: boolean) {
FILE: packages/worker-relay/src/forYouFeed.ts
function getForYouFeed (line 12) | async function getForYouFeed(relay: RelayHandler, pubkey: string): Promi...
function getReactionMaps (line 44) | async function getReactionMaps(relay: RelayHandler, myReactedEventIds: S...
function getMyReactedEvents (line 62) | async function getMyReactedEvents(relay: RelayHandler, pubkey: string) {
function getEventIdsReactedByOthers (line 80) | async function getEventIdsReactedByOthers(
function getFeedEvents (line 109) | async function getFeedEvents(
FILE: packages/worker-relay/src/interface.ts
type InitAargs (line 5) | interface InitAargs {
class WorkerRelayInterface (line 12) | class WorkerRelayInterface {
method constructor (line 23) | constructor(scriptPath?: string | URL | Worker) {
method init (line 46) | async init(args: InitAargs) {
method event (line 50) | async event(ev: NostrEvent) {
method query (line 54) | async query(req: ReqCommand) {
method count (line 58) | async count(req: ReqCommand) {
method delete (line 62) | async delete(req: ReqCommand) {
method summary (line 66) | async summary() {
method close (line 70) | async close(id: string) {
method dump (line 74) | async dump() {
method wipe (line 78) | async wipe() {
method forYouFeed (line 82) | async forYouFeed(pubkey: string) {
method setEventMetadata (line 86) | setEventMetadata(id: string, _meta: EventMetadata) {
method debug (line 92) | async debug(v: string) {
method configureSearchIndex (line 97) | configureSearchIndex(config: Record<number, Array<string>>) {
method #workerRpc (line 101) | async #workerRpc<T, R>(cmd: WorkerMessageCommand, args?: T) {
FILE: packages/worker-relay/src/memory-relay.ts
class InMemoryRelay (line 15) | class
Condensed preview — 743 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,787K chars).
[
{
"path": ".dockerignore",
"chars": 34,
"preview": "**/node_modules\n**/.idea\n**/target"
},
{
"path": ".drone.yml",
"chars": 3908,
"preview": "---\nkind: pipeline\ntype: kubernetes\nname: docker\nconcurrency:\n limit: 1\ntrigger:\n branch:\n - main\n event:\n - pu"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 837,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: \"\"\nassignees: \"\"\n---\n\n**Describe the bu"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 594,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"\"\nlabels: \"\"\nassignees: \"\"\n---\n\n**Is your feat"
},
{
"path": ".github/workflows/docker.yml",
"chars": 1943,
"preview": "name: Docker\n\non:\n push:\n branches:\n - main\n tags:\n - v[0-9]+.[0-9]+.[0-9]+\n - v[0-9]+.[0-9]+.[0-9"
},
{
"path": ".github/workflows/nsite.yml",
"chars": 744,
"preview": "name: Deploy nsite\non:\n workflow_dispatch: # temporarily disabled - nsite-cli upload hangs indefinitely\n # push:\n # "
},
{
"path": ".github/workflows/release.yml",
"chars": 3398,
"preview": "name: Release\non:\n push:\n tags:\n - v[0-9]+.[0-9]+.[0-9]+\n - v[0-9]+.[0-9]+.[0-9]+-*\nenv:\n DOCKER_CLI_EXPE"
},
{
"path": ".gitignore",
"chars": 77,
"preview": "node_modules/\n.idea\n.pnp.*\ndist/\n*.tgz\n*.log\n.DS_Store\n.pnp*\ndocs/\n.wrangler/"
},
{
"path": ".vscode/extensions.json",
"chars": 79,
"preview": "{\n \"recommendations\": [\n \"arcanis.vscode-zipfs\",\n \"biomejs.biome\"\n ]\n}\n"
},
{
"path": ".vscode/settings.json",
"chars": 318,
"preview": "{\n \"files.exclude\": {\n \"**/.git\": true,\n \"**/.svn\": true,\n \"**/.hg\": true,\n \"**/CVS\": true,\n \"**/.DS_Sto"
},
{
"path": "AGENTS.md",
"chars": 5256,
"preview": "# AGENTS.md - Snort Codebase Guidelines\n\nThis document provides guidelines for AI coding agents working in the Snort cod"
},
{
"path": "Dockerfile",
"chars": 434,
"preview": "FROM oven/bun:latest AS build\nWORKDIR /src\nRUN apt update \\\n && apt install -y --no-install-recommends git ca-certifi"
},
{
"path": "Dockerfile.prebuilt",
"chars": 148,
"preview": "FROM nginxinc/nginx-unprivileged:mainline-alpine\nCOPY packages/app/build /usr/share/nginx/html\nCOPY docker/nginx.conf /e"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2023 Kieran (v0l)\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 2163,
"preview": "## Snort\n\nSnort is a nostr UI built with React aiming for speed and efficiency.\n\nSnort supports the following NIP's:\n\n- "
},
{
"path": "biome.json",
"chars": 1467,
"preview": "{\n \"$schema\": \"https://biomejs.dev/schemas/2.3.9/schema.json\",\n \"vcs\": {\n \"enabled\": true,\n \"clientKind\": \"git\","
},
{
"path": "crowdin.yml",
"chars": 178,
"preview": "project_id: 568149\npreserve_hierarchy: true\nfiles:\n - source: packages/app/src/translations/en.json\n translation: pa"
},
{
"path": "docker/nginx.conf",
"chars": 870,
"preview": "server {\n listen 8080 default_server;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n ad"
},
{
"path": "functions/_middleware.ts",
"chars": 1607,
"preview": "type Env = {}\n\nconst HOST = \"snort.social\";\n\nexport const onRequest: PagesFunction<Env> = async context => {\n const u ="
},
{
"path": "functions/tsconfig.json",
"chars": 145,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"esnext\",\n \"module\": \"esnext\",\n \"lib\": [\"esnext\"],\n \"types\": [\"@cloudfla"
},
{
"path": "maintainers.yaml",
"chars": 310,
"preview": "maintainers:\n - npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk\n - npub1v0lxxxxutpvrelsksy8cdhgfux9l6a"
},
{
"path": "package.json",
"chars": 1038,
"preview": "{\n \"private\": true,\n \"workspaces\": [\n \"packages/*\"\n ],\n \"scripts\": {\n \"start\": \"bun run build && bun --cwd=pac"
},
{
"path": "packages/app/.gitignore",
"chars": 344,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "packages/app/CHANGELOG.md",
"chars": 50011,
"preview": "# v0.5.0\n\n`+196,739,-43,235`\n\n## Highlights\n\n- **Security Hardening**: Comprehensive audit fixes including Schnorr signa"
},
{
"path": "packages/app/README.md",
"chars": 294,
"preview": "# bun-react-template\n\nTo install dependencies:\n\n```bash\nbun install\n```\n\nTo start a development server:\n\n```bash\nbun dev"
},
{
"path": "packages/app/babel.config.json",
"chars": 156,
"preview": "{\n \"plugins\": [\n [\n \"formatjs\",\n {\n \"idInterpolationPattern\": \"[sha512:contenthash:base64:6]\",\n "
},
{
"path": "packages/app/bun-env.d.ts",
"chars": 330,
"preview": "// Generated by `bun init`\n\ndeclare module \"*.svg\" {\n /**\n * A path to the SVG file\n */\n const path: `${string}.sv"
},
{
"path": "packages/app/bunfig.toml",
"chars": 35,
"preview": "[serve.static]\nenv = \"BUN_PUBLIC_*\""
},
{
"path": "packages/app/config/README.md",
"chars": 69,
"preview": "Choose config with NODE_CONFIG_ENV: `NODE_CONFIG_ENV=iris bun start`\n"
},
{
"path": "packages/app/config/default.json",
"chars": 1680,
"preview": "{\n \"appName\": \"Snort\",\n \"appNameCapitalized\": \"Snort\",\n \"appTitle\": \"Snort - Nostr\",\n \"hostname\": \"snort.social\",\n "
},
{
"path": "packages/app/config/iris.json",
"chars": 1630,
"preview": "{\n \"appName\": \"iris\",\n \"appNameCapitalized\": \"Iris\",\n \"appTitle\": \"iris\",\n \"hostname\": \"iris.to\",\n \"nip05Domain\": \""
},
{
"path": "packages/app/config/meku.json",
"chars": 1205,
"preview": "{\n \"appName\": \"めく\",\n \"appNameCapitalized\": \"めく\",\n \"appTitle\": \"めく\",\n \"hostname\": \"meku.app\",\n \"nip05Domain\": \"meku."
},
{
"path": "packages/app/config/nostr.json",
"chars": 1258,
"preview": "{\n \"appName\": \"Nostr\",\n \"appNameCapitalized\": \"Nostr\",\n \"appTitle\": \"Nostr\",\n \"hostname\": \"nostr.com\",\n \"nip05Domai"
},
{
"path": "packages/app/config/phoenix.json",
"chars": 1690,
"preview": "{\n \"appName\": \"Phoenix\",\n \"appNameCapitalized\": \"Phoenix\",\n \"appTitle\": \"Phoenix - Nostr\",\n \"hostname\": \"phoenix.soc"
},
{
"path": "packages/app/config/soloco.json",
"chars": 1182,
"preview": "{\n \"appName\": \"Soloco\",\n \"appNameCapitalized\": \"Soloco\",\n \"appTitle\": \"Soloco\",\n \"hostname\": \"soloco.nl\",\n \"nip05Do"
},
{
"path": "packages/app/custom.d.ts",
"chars": 2248,
"preview": "/// <reference types=\"@webbtc/webln-types\" />\n/// <reference types=\"vite/client\" />\n\ndeclare module \"*.jpg\" {\n const va"
},
{
"path": "packages/app/index.html",
"chars": 2232,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta\n name=\"viewport\"\n content=\"wi"
},
{
"path": "packages/app/package.json",
"chars": 3592,
"preview": "{\n \"name\": \"@snort/app\",\n \"version\": \"0.5.2\",\n \"type\": \"module\",\n \"dependencies\": {\n \"@cashu/cashu-ts\": \"^2.7.2\","
},
{
"path": "packages/app/public/iris/.well-known/assetlinks.json",
"chars": 319,
"preview": "[\n {\n \"relation\": [\"delegate_permission/common.handle_all_urls\"],\n \"target\": {\n \"namespace\": \"android_app\",\n"
},
{
"path": "packages/app/public/iris/_headers",
"chars": 702,
"preview": "/*\n Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https:/"
},
{
"path": "packages/app/public/iris/manifest.json",
"chars": 834,
"preview": "{\n \"short_name\": \"Iris\",\n \"name\": \"Iris\",\n \"description\": \"Fast nostr web ui\",\n \"id\": \"/\",\n \"icons\": [\n {\n "
},
{
"path": "packages/app/public/iris/robots.txt",
"chars": 67,
"preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
},
{
"path": "packages/app/public/nostr/_headers",
"chars": 633,
"preview": "/*\n Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https:/"
},
{
"path": "packages/app/public/phoenix/.well-known/apple-app-site-association",
"chars": 194,
"preview": "{\n \"applinks\": {\n \"details\": [\n {\n \"appIDs\": [\n \"snort.social.app\"\n ]\n }\n ]\n },"
},
{
"path": "packages/app/public/phoenix/.well-known/assetlinks.json",
"chars": 431,
"preview": "[\n {\n \"relation\": [\"delegate_permission/common.handle_all_urls\"],\n \"target\": {\n \"namespace\": \"android_app\",\n"
},
{
"path": "packages/app/public/phoenix/_headers",
"chars": 702,
"preview": "/*\n Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https:/"
},
{
"path": "packages/app/public/phoenix/manifest.json",
"chars": 681,
"preview": "{\n \"short_name\": \"Phoenix\",\n \"name\": \"phoenix.social - Nostr interface\",\n \"description\": \"Fast nostr web ui\",\n \"id\":"
},
{
"path": "packages/app/public/phoenix/robots.txt",
"chars": 125,
"preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\nSitemap: https://api.snort.social/api/v1/sitemap/inde"
},
{
"path": "packages/app/public/snort/.well-known/apple-app-site-association",
"chars": 194,
"preview": "{\n \"applinks\": {\n \"details\": [\n {\n \"appIDs\": [\n \"snort.social.app\"\n ]\n }\n ]\n },"
},
{
"path": "packages/app/public/snort/.well-known/assetlinks.json",
"chars": 431,
"preview": "[\n {\n \"relation\": [\"delegate_permission/common.handle_all_urls\"],\n \"target\": {\n \"namespace\": \"android_app\",\n"
},
{
"path": "packages/app/public/snort/_headers",
"chars": 702,
"preview": "/*\n Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https:/"
},
{
"path": "packages/app/public/snort/manifest.json",
"chars": 776,
"preview": "{\n \"short_name\": \"Snort\",\n \"name\": \"snort.social - Nostr interface\",\n \"description\": \"Fast nostr web ui\",\n \"id\": \"/\""
},
{
"path": "packages/app/public/snort/robots.txt",
"chars": 125,
"preview": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\nSitemap: https://api.snort.social/api/v1/sitemap/inde"
},
{
"path": "packages/app/src/Agent/system-prompt.ts",
"chars": 3259,
"preview": "import { unixNow } from \"@snort/shared\"\n\nexport const SnortSystemPrompt = `You are an AI assistant integrated with Snort"
},
{
"path": "packages/app/src/Cache/CommunityLeadersStore.tsx",
"chars": 356,
"preview": "import { ExternalStore } from \"@snort/shared\"\n\nclass CommunityLeadersStore extends ExternalStore<Array<string>> {\n #lea"
},
{
"path": "packages/app/src/Cache/GiftWrapCache.ts",
"chars": 4268,
"preview": "import { EventKind, type EventPublisher, type NostrEvent, type TaggedNostrEvent } from \"@snort/system\"\nimport type { Cac"
},
{
"path": "packages/app/src/Cache/ProfileWorkerCache.ts",
"chars": 796,
"preview": "import { type CachedMetadata, type CacheRelay, EventKind, mapEventToProfile, type NostrEvent } from \"@snort/system\"\nimpo"
},
{
"path": "packages/app/src/Cache/RefreshFeedCache.ts",
"chars": 889,
"preview": "import { FeedCache } from \"@snort/shared\"\nimport type { EventPublisher, RequestBuilder, TaggedNostrEvent } from \"@snort/"
},
{
"path": "packages/app/src/Cache/RelaysWorkerCache.ts",
"chars": 971,
"preview": "import { unixNowMs } from \"@snort/shared\"\nimport { type CacheRelay, EventKind, type NostrEvent, type UsersRelays, parseR"
},
{
"path": "packages/app/src/Cache/UserFollowsWorker.ts",
"chars": 949,
"preview": "import { unixNowMs } from \"@snort/shared\"\nimport { type CacheRelay, EventKind, type NostrEvent, type UsersFollows } from"
},
{
"path": "packages/app/src/Cache/index.ts",
"chars": 2634,
"preview": "import {\n type CacheRelay,\n Connection,\n ConnectionCacheRelay,\n UserFollowsCache,\n UserProfileCache,\n UserRelaysCa"
},
{
"path": "packages/app/src/Cache/worker-cached.ts",
"chars": 5698,
"preview": "import { type CachedTable, type CacheEvents, removeUndefined, unixNowMs } from \"@snort/shared\"\nimport type { CachedBase,"
},
{
"path": "packages/app/src/Components/AskSnort/AskSnortInput.tsx",
"chars": 1918,
"preview": "import { useState } from \"react\"\nimport { useNavigate } from \"react-router-dom\"\nimport { FormattedMessage, useIntl } fro"
},
{
"path": "packages/app/src/Components/Button/AsyncButton.tsx",
"chars": 1340,
"preview": "import classNames from \"classnames\"\nimport React, { type ForwardedRef } from \"react\"\n\nimport Spinner from \"@/Components/"
},
{
"path": "packages/app/src/Components/Button/AsyncIcon.tsx",
"chars": 785,
"preview": "import Icon from \"@/Components/Icons/Icon\"\nimport Spinner from \"@/Components/Icons/Spinner\"\nimport useLoading from \"@/Ho"
},
{
"path": "packages/app/src/Components/Button/BackButton.tsx",
"chars": 544,
"preview": "import { FormattedMessage } from \"react-intl\"\n\nimport Icon from \"@/Components/Icons/Icon\"\nimport type { ReactNode } from"
},
{
"path": "packages/app/src/Components/Button/CloseButton.tsx",
"chars": 490,
"preview": "import classNames from \"classnames\"\n\nimport Icon from \"@/Components/Icons/Icon\"\n\nexport default function CloseButton({ o"
},
{
"path": "packages/app/src/Components/Button/IconButton.tsx",
"chars": 641,
"preview": "import classNames from \"classnames\"\nimport type { ReactNode } from \"react\"\n\nimport Icon, { type IconProps } from \"@/Comp"
},
{
"path": "packages/app/src/Components/Button/LogoutButton.tsx",
"chars": 610,
"preview": "import { FormattedMessage } from \"react-intl\"\nimport { useNavigate } from \"react-router-dom\"\n\nimport useLogin from \"@/Ho"
},
{
"path": "packages/app/src/Components/Button/NavLink.tsx",
"chars": 497,
"preview": "import { NavLink as RouterNavLink, type NavLinkProps, useLocation } from \"react-router-dom\"\n\nexport default function Nav"
},
{
"path": "packages/app/src/Components/Collapsed.tsx",
"chars": 1474,
"preview": "import classNames from \"classnames\"\nimport { type ReactNode, useState } from \"react\"\n\nimport Icon from \"@/Components/Ico"
},
{
"path": "packages/app/src/Components/CommunityLeaders/Award.tsx",
"chars": 2317,
"preview": "export default function AwardIcon({ size }: { size?: number }) {\n return (\n <svg width={size} height={size} viewBox="
},
{
"path": "packages/app/src/Components/CommunityLeaders/LeaderBadge.tsx",
"chars": 1774,
"preview": "import { useState } from \"react\"\nimport { FormattedMessage } from \"react-intl\"\nimport { Link } from \"react-router-dom\"\n\n"
},
{
"path": "packages/app/src/Components/Copy/Copy.tsx",
"chars": 1121,
"preview": "import classNames from \"classnames\"\n\nimport Icon from \"@/Components/Icons/Icon\"\nimport { useCopy } from \"@/Hooks/useCopy"
},
{
"path": "packages/app/src/Components/DvmSelector.tsx",
"chars": 3114,
"preview": "import { FormattedMessage } from \"react-intl\"\nimport type { EventKind } from \"@snort/system\"\n\nimport Modal from \"@/Compo"
},
{
"path": "packages/app/src/Components/Embed/AppleMusicEmbed.tsx",
"chars": 686,
"preview": "const AppleMusicEmbed = ({ link }: { link: string }) => {\n const convertedUrl = link.replace(\"music.apple.com\", \"embed."
},
{
"path": "packages/app/src/Components/Embed/BlossomBlob.tsx",
"chars": 7008,
"preview": "import useBlossomServers from \"@/Hooks/useBlossomServers\"\nimport { appendDedupe, dedupe, isHex, NostrPrefix, removeUndef"
},
{
"path": "packages/app/src/Components/Embed/CashuNuts.tsx",
"chars": 2507,
"preview": "import { useUserProfile } from \"@snort/system-react\"\nimport { useEffect, useState } from \"react\"\nimport { FormattedMessa"
},
{
"path": "packages/app/src/Components/Embed/GenericPlayer.tsx",
"chars": 990,
"preview": "import { useState } from \"react\"\n\nimport Icon from \"../Icons/Icon\"\nimport { ProxyImg } from \"../ProxyImg\"\n\nexport defaul"
},
{
"path": "packages/app/src/Components/Embed/Hashtag.tsx",
"chars": 302,
"preview": "import { Link } from \"react-router-dom\"\n\nconst Hashtag = ({ tag }: { tag: string }) => {\n return (\n <span className="
},
{
"path": "packages/app/src/Components/Embed/HyperText.tsx",
"chars": 1918,
"preview": "import type { ReactNode } from \"react\"\n\nimport AppleMusicEmbed from \"@/Components/Embed/AppleMusicEmbed\"\nimport LinkPrev"
},
{
"path": "packages/app/src/Components/Embed/Invoice.tsx",
"chars": 2943,
"preview": "import { decodeInvoice } from \"@snort/shared\"\nimport { useState } from \"react\"\nimport { useMemo } from \"react\"\nimport { "
},
{
"path": "packages/app/src/Components/Embed/LinkPreview.tsx",
"chars": 3840,
"preview": "import { useEffect, useState } from \"react\"\nimport { LRUCache } from \"typescript-lru-cache\"\n\nimport { MediaElement } fro"
},
{
"path": "packages/app/src/Components/Embed/MagnetLink.tsx",
"chars": 636,
"preview": "import { FormattedMessage } from \"react-intl\"\n\nimport type { Magnet } from \"@/Utils\"\nimport Icon from \"../Icons/Icon\"\n\ni"
},
{
"path": "packages/app/src/Components/Embed/MediaElement.tsx",
"chars": 3905,
"preview": "import type { Nip94Tags } from \"@snort/system\"\nimport classNames from \"classnames\"\nimport type React from \"react\"\nimport"
},
{
"path": "packages/app/src/Components/Embed/Mention.tsx",
"chars": 1118,
"preview": "import { NostrPrefix } from \"@snort/shared\"\nimport type { NostrLink } from \"@snort/system\"\nimport { useUserProfile } fro"
},
{
"path": "packages/app/src/Components/Embed/MixCloudEmbed.tsx",
"chars": 659,
"preview": "import usePreferences from \"@/Hooks/usePreferences\"\nimport { MixCloudRegex } from \"@/Utils/Const\"\n\nconst MixCloudEmbed ="
},
{
"path": "packages/app/src/Components/Embed/NostrLink.tsx",
"chars": 1379,
"preview": "import { tryParseNostrLink } from \"@snort/system\"\nimport { Link } from \"react-router-dom\"\n\nimport Mention from \"@/Compon"
},
{
"path": "packages/app/src/Components/Embed/NostrNestsEmbed.tsx",
"chars": 194,
"preview": "const NostrNestsEmbed = ({ link }: { link: string }) => (\n <iframe src={link} allow=\"microphone\" width=\"480\" height=\"68"
},
{
"path": "packages/app/src/Components/Embed/PubkeyList.tsx",
"chars": 3076,
"preview": "import { hexToBech32, LNURL } from \"@snort/shared\"\nimport type { NostrEvent } from \"@snort/system\"\nimport { WalletInvoic"
},
{
"path": "packages/app/src/Components/Embed/SoundCloudEmded.tsx",
"chars": 267,
"preview": "const SoundCloudEmbed = ({ link }: { link: string }) => {\n return (\n <iframe\n width=\"100%\"\n height=\"166\"\n "
},
{
"path": "packages/app/src/Components/Embed/SpotifyEmbed.tsx",
"chars": 455,
"preview": "const SpotifyEmbed = ({ link }: { link: string }) => {\n const convertedUrl = link.replace(/\\/(track|album|playlist|epis"
},
{
"path": "packages/app/src/Components/Embed/TidalEmbed.tsx",
"chars": 1966,
"preview": "import { useEffect, useState } from \"react\"\n\nimport { TidalRegex } from \"@/Utils/Const\"\n\n// Re-use dom parser across ins"
},
{
"path": "packages/app/src/Components/Embed/TwitchEmbed.tsx",
"chars": 381,
"preview": "const TwitchEmbed = ({ link }: { link: string }) => {\n const channel = link.split(\"/\").slice(-1)\n\n const args = `?chan"
},
{
"path": "packages/app/src/Components/Embed/UrlStatusCheck.tsx",
"chars": 989,
"preview": "import { useEffect, useState } from \"react\"\nimport Icon from \"../Icons/Icon\"\nimport Spinner from \"../Icons/Spinner\"\n\nint"
},
{
"path": "packages/app/src/Components/Embed/WavlakeEmbed.tsx",
"chars": 314,
"preview": "const WavlakeEmbed = ({ link }: { link: string }) => {\n const convertedUrl = link.replace(/(?:player\\.|www\\.)?wavlake\\."
},
{
"path": "packages/app/src/Components/Embed/YoutubeEmbed.tsx",
"chars": 539,
"preview": "import { YoutubeUrlRegex } from \"@/Utils/Const\"\n\nexport default function YoutubeEmbed({ link }: { link: string }) {\n co"
},
{
"path": "packages/app/src/Components/Embed/ZapstrEmbed.css",
"chars": 216,
"preview": ".zapstr {\n}\n\n.zapstr > img {\n margin: 0 10px 0 0;\n}\n\n.zapstr audio {\n margin: 0;\n height: 2em;\n}\n\n.zapstr .pfp .avata"
},
{
"path": "packages/app/src/Components/Embed/ZapstrEmbed.tsx",
"chars": 1402,
"preview": "import \"./ZapstrEmbed.css\"\n\nimport { type NostrEvent, NostrLink } from \"@snort/system\"\nimport { FormattedMessage } from "
},
{
"path": "packages/app/src/Components/ErrorBoundary.tsx",
"chars": 1116,
"preview": "import React from \"react\"\n\nimport { trackEvent } from \"@/Utils\"\n\ninterface ErrorBoundaryState {\n hasError: boolean\n er"
},
{
"path": "packages/app/src/Components/ErrorOrOffline.tsx",
"chars": 635,
"preview": "import { OfflineError } from \"@snort/shared\"\nimport classNames from \"classnames\"\n\nimport Icon from \"@/Components/Icons/I"
},
{
"path": "packages/app/src/Components/Event/Application.tsx",
"chars": 1742,
"preview": "import { mapEventToProfile, type TaggedNostrEvent } from \"@snort/system\"\nimport { FormattedMessage } from \"react-intl\"\n\n"
},
{
"path": "packages/app/src/Components/Event/Create/NoteCreator.tsx",
"chars": 25283,
"preview": "/* eslint-disable max-lines */\nimport { fetchNip05Pubkey, NostrPrefix, unixNow } from \"@snort/shared\"\nimport {\n type Ev"
},
{
"path": "packages/app/src/Components/Event/Create/NoteCreatorButton.tsx",
"chars": 2436,
"preview": "import classNames from \"classnames\"\nimport { useMemo, useRef } from \"react\"\nimport { FormattedMessage } from \"react-intl"
},
{
"path": "packages/app/src/Components/Event/Create/OkResponseRow.tsx",
"chars": 2116,
"preview": "import { sanitizeRelayUrl, unwrap } from \"@snort/shared\"\nimport type { OkResponse } from \"@snort/system\"\nimport { useSta"
},
{
"path": "packages/app/src/Components/Event/Create/util.ts",
"chars": 757,
"preview": "import { removeUndefined } from \"@snort/shared\"\nimport type { NostrEvent, OkResponse, SystemInterface } from \"@snort/sys"
},
{
"path": "packages/app/src/Components/Event/DVMJobFeedback.tsx",
"chars": 2222,
"preview": "import { findTag, type TaggedNostrEvent } from \"@snort/system\"\nimport { FormattedMessage } from \"react-intl\"\n\nimport Ico"
},
{
"path": "packages/app/src/Components/Event/EventComponent.tsx",
"chars": 3825,
"preview": "import { EventKind, parseIMeta, type TaggedNostrEvent } from \"@snort/system\"\nimport type { ReactNode } from \"react\"\n\nimp"
},
{
"path": "packages/app/src/Components/Event/FileUpload.tsx",
"chars": 502,
"preview": "import Progress from \"@/Components/Progress/Progress\"\nimport type { UploadProgress } from \"@/Utils/Upload\"\n\nexport defau"
},
{
"path": "packages/app/src/Components/Event/HiddenNote.tsx",
"chars": 770,
"preview": "import { useState } from \"react\"\nimport { FormattedMessage } from \"react-intl\"\n\nimport usePreferences from \"@/Hooks/useP"
},
{
"path": "packages/app/src/Components/Event/LoadMore.tsx",
"chars": 1084,
"preview": "import { useEffect } from \"react\"\nimport { useInView } from \"react-intersection-observer\"\nimport { FormattedMessage } fr"
},
{
"path": "packages/app/src/Components/Event/LongFormText.tsx",
"chars": 5321,
"preview": "import type { TaggedNostrEvent } from \"@snort/system\"\nimport classNames from \"classnames\"\nimport { useCallback, useMemo,"
},
{
"path": "packages/app/src/Components/Event/Markdown.tsx",
"chars": 5215,
"preview": "import { transformText } from \"@snort/system\"\nimport { marked, type Token, type Tokens } from \"marked\"\nimport markedFoot"
},
{
"path": "packages/app/src/Components/Event/NostrFileHeader.tsx",
"chars": 1446,
"preview": "import type { NostrEvent, NostrLink } from \"@snort/system\"\nimport { useEventFeed } from \"@snort/system-react\"\nimport { F"
},
{
"path": "packages/app/src/Components/Event/Note/ClientFingerprinting.tsx",
"chars": 7941,
"preview": "import type { NostrEvent } from \"@snort/system\"\nimport type { ReactNode } from \"react\"\nimport { FormattedMessage } from "
},
{
"path": "packages/app/src/Components/Event/Note/ClientTag.tsx",
"chars": 4770,
"preview": "import Icon from \"@/Components/Icons/Icon\"\nimport { EventKind, type NostrEvent, NostrLink, type TaggedNostrEvent } from "
},
{
"path": "packages/app/src/Components/Event/Note/Note.tsx",
"chars": 4466,
"preview": "import { EventKind, NostrLink, type TaggedNostrEvent } from \"@snort/system\"\nimport { WorkerRelayInterface } from \"@snort"
},
{
"path": "packages/app/src/Components/Event/Note/NoteAppHandler.tsx",
"chars": 2855,
"preview": "import { NostrLink, type TaggedNostrEvent } from \"@snort/system\"\nimport { FormattedMessage } from \"react-intl\"\n\nimport I"
},
{
"path": "packages/app/src/Components/Event/Note/NoteContent.tsx",
"chars": 1653,
"preview": "import { EventExt, EventKind, type TaggedNostrEvent } from \"@snort/system\"\nimport classNames from \"classnames\"\n\nimport t"
},
{
"path": "packages/app/src/Components/Event/Note/NoteContext.tsx",
"chars": 4297,
"preview": "import { NostrLink, type TaggedNostrEvent } from \"@snort/system\"\nimport { useEventReactions, useReactions } from \"@snort"
},
{
"path": "packages/app/src/Components/Event/Note/NoteContextMenu.tsx",
"chars": 7183,
"preview": "import { EventKind, type NostrEvent, NostrLink } from \"@snort/system\"\nimport * as DropdownMenu from \"@radix-ui/react-dro"
},
{
"path": "packages/app/src/Components/Event/Note/NoteFooter/AsyncFooterIcon.tsx",
"chars": 632,
"preview": "import classNames from \"classnames\"\n\nimport { AsyncIcon, type AsyncIconProps } from \"@/Components/Button/AsyncIcon\"\nimpo"
},
{
"path": "packages/app/src/Components/Event/Note/NoteFooter/FooterZapButton.tsx",
"chars": 5239,
"preview": "import { barrierQueue } from \"@snort/shared\"\nimport { NostrLink, type ParsedZap, type TaggedNostrEvent } from \"@snort/sy"
},
{
"path": "packages/app/src/Components/Event/Note/NoteFooter/LikeButton.tsx",
"chars": 1583,
"preview": "import { normalizeReaction } from \"@snort/shared\"\nimport type { TaggedNostrEvent } from \"@snort/system\"\nimport className"
},
{
"path": "packages/app/src/Components/Event/Note/NoteFooter/NoteFooter.tsx",
"chars": 2417,
"preview": "import { EventKind, RequestFilterBuilder } from \"@snort/system\"\nimport { SnortContext } from \"@snort/system-react\"\nimpor"
},
{
"path": "packages/app/src/Components/Event/Note/NoteFooter/PowIcon.tsx",
"chars": 686,
"preview": "import { countLeadingZeros, type TaggedNostrEvent } from \"@snort/system\"\nimport { useIntl } from \"react-intl\"\n\nimport { "
},
{
"path": "packages/app/src/Components/Event/Note/NoteFooter/ReplyButton.tsx",
"chars": 1399,
"preview": "import type { TaggedNostrEvent } from \"@snort/system\"\nimport classNames from \"classnames\"\nimport { useIntl } from \"react"
},
{
"path": "packages/app/src/Components/Event/Note/NoteFooter/RepostButton.tsx",
"chars": 3297,
"preview": "import type { TaggedNostrEvent } from \"@snort/system\"\nimport * as DropdownMenu from \"@radix-ui/react-dropdown-menu\"\nimpo"
},
{
"path": "packages/app/src/Components/Event/Note/NoteFooter/ZapperQueue.tsx",
"chars": 153,
"preview": "import { processWorkQueue, type WorkQueueItem } from \"@snort/shared\"\n\nexport const ZapperQueue: Array<WorkQueueItem> = ["
},
{
"path": "packages/app/src/Components/Event/Note/NoteHeader.tsx",
"chars": 2573,
"preview": "import { EventKind, NostrLink } from \"@snort/system\"\nimport type React from \"react\"\nimport { FormattedMessage, useIntl }"
},
{
"path": "packages/app/src/Components/Event/Note/NoteQuote.tsx",
"chars": 2505,
"preview": "import { dedupe, NostrPrefix, sanitizeRelayUrl } from \"@snort/shared\"\nimport { NostrLink } from \"@snort/system\"\nimport {"
},
{
"path": "packages/app/src/Components/Event/Note/NoteText.tsx",
"chars": 3240,
"preview": "import { memo, useState } from \"react\"\nimport { FormattedMessage } from \"react-intl\"\nimport { Link } from \"react-router-"
},
{
"path": "packages/app/src/Components/Event/Note/NoteTime.tsx",
"chars": 1873,
"preview": "import type React from \"react\"\nimport { type ReactNode, useCallback, useMemo, useState } from \"react\"\nimport { Formatted"
},
{
"path": "packages/app/src/Components/Event/Note/ReactionsModal.tsx",
"chars": 3766,
"preview": "import type { TaggedNostrEvent } from \"@snort/system\"\nimport { Fragment, useMemo, useState } from \"react\"\nimport { Forma"
},
{
"path": "packages/app/src/Components/Event/Note/ReplyTag.tsx",
"chars": 1329,
"preview": "import { EventExt, type TaggedNostrEvent } from \"@snort/system\"\nimport { FormattedMessage } from \"react-intl\"\nimport { L"
},
{
"path": "packages/app/src/Components/Event/Note/TranslationInfo.tsx",
"chars": 773,
"preview": "import { FormattedMessage } from \"react-intl\"\n\nimport { useNoteContext } from \"@/Components/Event/Note/NoteContext\"\n\nexp"
},
{
"path": "packages/app/src/Components/Event/Note/types.tsx",
"chars": 116,
"preview": "export interface NoteTranslation {\n text: string\n fromLanguage: string\n confidence: number\n skipped?: boolean\n}\n"
},
{
"path": "packages/app/src/Components/Event/NoteReaction.tsx",
"chars": 2676,
"preview": "import { NostrPrefix } from \"@snort/shared\"\nimport { EventKind, NostrLink, type TaggedNostrEvent } from \"@snort/system\"\n"
},
{
"path": "packages/app/src/Components/Event/Poll.tsx",
"chars": 5929,
"preview": "import { LNURL } from \"@snort/shared\"\nimport { NostrLink, type ParsedZap, type TaggedNostrEvent } from \"@snort/system\"\ni"
},
{
"path": "packages/app/src/Components/Event/Reveal.tsx",
"chars": 460,
"preview": "import { useState } from \"react\"\n\nimport { WarningNotice } from \"@/Components/WarningNotice/WarningNotice\"\n\ninterface Re"
},
{
"path": "packages/app/src/Components/Event/RevealMedia.tsx",
"chars": 1804,
"preview": "import { FormattedMessage } from \"react-intl\"\nimport { Link } from \"react-router-dom\"\n\nimport { MediaElement, type Media"
},
{
"path": "packages/app/src/Components/Event/Thread/Subthread.tsx",
"chars": 2181,
"preview": "import { EventExt, type TaggedNostrEvent } from \"@snort/system\"\n\nimport Note from \"@/Components/Event/EventComponent\"\nim"
},
{
"path": "packages/app/src/Components/Event/Thread/Thread.tsx",
"chars": 4831,
"preview": "import { EventExt, type TaggedNostrEvent } from \"@snort/system\"\nimport { type ReactNode, useCallback, use, useMemo, useS"
},
{
"path": "packages/app/src/Components/Event/Thread/ThreadRoute.tsx",
"chars": 596,
"preview": "import { NostrLink, parseNostrLink } from \"@snort/system\"\nimport { useParams } from \"react-router-dom\"\n\nimport { ThreadE"
},
{
"path": "packages/app/src/Components/Event/Thread/util.ts",
"chars": 453,
"preview": "import { removeUndefined } from \"@snort/shared\"\nimport { EventExt, type TaggedNostrEvent } from \"@snort/system\"\n\nexport "
},
{
"path": "packages/app/src/Components/Event/Zap.tsx",
"chars": 1108,
"preview": "import type { ParsedZap } from \"@snort/system\"\nimport { FormattedMessage } from \"react-intl\"\n\nimport Text from \"@/Compon"
},
{
"path": "packages/app/src/Components/Event/ZapButton.tsx",
"chars": 1146,
"preview": "import type { NostrLink } from \"@snort/system\"\nimport { useUserProfile } from \"@snort/system-react\"\nimport type { ZapTar"
},
{
"path": "packages/app/src/Components/Event/ZapGoal.tsx",
"chars": 1527,
"preview": "import { type NostrEvent, NostrLink } from \"@snort/system\"\nimport { Zapper } from \"@snort/wallet\"\nimport { useState } fr"
},
{
"path": "packages/app/src/Components/Event/ZapsSummary.tsx",
"chars": 1096,
"preview": "import type { ParsedZap } from \"@snort/system\"\nimport type React from \"react\"\nimport { useMemo } from \"react\"\n\nimport { "
},
{
"path": "packages/app/src/Components/Feed/ImageGridItem.tsx",
"chars": 2048,
"preview": "import { EventKind, NostrLink, type TaggedNostrEvent } from \"@snort/system\"\nimport { memo, type MouseEvent, type ReactNo"
},
{
"path": "packages/app/src/Components/Feed/LoadMore.tsx",
"chars": 872,
"preview": "import { useEffect, useState } from \"react\"\nimport { useInView } from \"react-intersection-observer\"\nimport { FormattedMe"
},
{
"path": "packages/app/src/Components/Feed/RootTabItems.tsx",
"chars": 2621,
"preview": "import type { ReactNode } from \"react\"\nimport { FormattedMessage } from \"react-intl\"\n\nimport Icon from \"@/Components/Ico"
},
{
"path": "packages/app/src/Components/Feed/RootTabs.tsx",
"chars": 3472,
"preview": "import { unwrap } from \"@snort/shared\"\nimport { EventKind } from \"@snort/system\"\nimport * as DropdownMenu from \"@radix-u"
},
{
"path": "packages/app/src/Components/Feed/Timeline.tsx",
"chars": 2606,
"preview": "import { unixNow } from \"@snort/shared\"\nimport type { TaggedNostrEvent } from \"@snort/system\"\nimport { useCallback, useM"
},
{
"path": "packages/app/src/Components/Feed/TimelineChunk.tsx",
"chars": 1346,
"preview": "import { type NostrEvent, RequestBuilder } from \"@snort/system\"\nimport { useRequestBuilder } from \"@snort/system-react\"\n"
},
{
"path": "packages/app/src/Components/Feed/TimelineFollows.tsx",
"chars": 2139,
"preview": "import { unixNow } from \"@snort/shared\"\nimport { EventKind, type NostrEvent, type RequestBuilder } from \"@snort/system\"\n"
},
{
"path": "packages/app/src/Components/Feed/TimelineFragment.tsx",
"chars": 1089,
"preview": "import type { TaggedNostrEvent } from \"@snort/system\"\nimport type { ReactNode } from \"react\"\n\nimport Note from \"@/Compon"
},
{
"path": "packages/app/src/Components/Feed/TimelineRenderer.tsx",
"chars": 3520,
"preview": "import type { TaggedNostrEvent } from \"@snort/system\"\nimport { type ReactNode, useEffect, useRef } from \"react\"\nimport {"
},
{
"path": "packages/app/src/Components/Feed/UsersFeed.tsx",
"chars": 1177,
"preview": "import type { TaggedNostrEvent } from \"@snort/system\"\nimport { useCallback, useMemo } from \"react\"\n\nimport PageSpinner f"
},
{
"path": "packages/app/src/Components/Icons/Alby.tsx",
"chars": 5134,
"preview": "export default function AlbyIcon(props: { size?: number }) {\n return (\n <svg width={props.size ?? 400} height={props"
},
{
"path": "packages/app/src/Components/Icons/BlueWallet.tsx",
"chars": 14985,
"preview": "export const BlueWallet = (props: { width?: number; height?: number }) => {\n return (\n <svg viewBox=\"0 0 58 58\" vers"
},
{
"path": "packages/app/src/Components/Icons/Cashu.tsx",
"chars": 8737,
"preview": "export default function CashuIcon(props: { size?: number }) {\n return (\n <svg width={props.size ?? 135} height={prop"
},
{
"path": "packages/app/src/Components/Icons/ECash.tsx",
"chars": 2533,
"preview": "export default function ECashIcon(props: { width?: number; height?: number }) {\n return (\n <svg viewBox=\"0 0 30 39\" "
},
{
"path": "packages/app/src/Components/Icons/Icon.tsx",
"chars": 487,
"preview": "import { forwardRef, type HTMLProps } from \"react\"\n\nimport IconsSvg from \"@/Components/Icons/icons.svg\"\n\nexport type Ico"
},
{
"path": "packages/app/src/Components/Icons/NWC.tsx",
"chars": 3455,
"preview": "export default function NWCIcon(props: { width?: number; height?: number }) {\n return (\n <svg viewBox=\"0 0 256 256\" "
},
{
"path": "packages/app/src/Components/Icons/Nostrich.tsx",
"chars": 3384,
"preview": "export default function NostrIcon(props: { width?: number; height?: number }) {\n return (\n <svg xmlns=\"http://www.w3"
},
{
"path": "packages/app/src/Components/Icons/Spinner.tsx",
"chars": 539,
"preview": "const Spinner = (props: { width?: number; height?: number; className?: string }) => (\n <svg\n width={props.width ?? 2"
},
{
"path": "packages/app/src/Components/Icons/Toggle.tsx",
"chars": 2004,
"preview": "import type { IconProps } from \"./Icon\"\n\nexport function ToggleSwitch(props: Omit<IconProps, \"name\">) {\n const size = p"
},
{
"path": "packages/app/src/Components/IntlProvider/IntlProvider.tsx",
"chars": 3453,
"preview": "import { type ReactNode, useEffect, useState } from \"react\"\nimport { type MessageFormatElement, IntlProvider as ReactInt"
},
{
"path": "packages/app/src/Components/IntlProvider/IntlProviderUtils.tsx",
"chars": 694,
"preview": "export const DefaultLocale = \"en-US\"\n\nexport const getLocale = () => {\n return (navigator.languages?.[0]) ?? navigator."
},
{
"path": "packages/app/src/Components/IntlProvider/langStore.tsx",
"chars": 300,
"preview": "import { ExternalStore } from \"@snort/shared\"\n\nclass LangStore extends ExternalStore<string | null> {\n setLang(s: strin"
},
{
"path": "packages/app/src/Components/IntlProvider/useLocale.tsx",
"chars": 629,
"preview": "import { useSyncExternalStore } from \"react\"\n\nimport { getLocale } from \"@/Components/IntlProvider/IntlProviderUtils\"\nim"
},
{
"path": "packages/app/src/Components/Invite.tsx",
"chars": 1534,
"preview": "import { useUserProfile } from \"@snort/system-react\"\nimport Lottie from \"lottie-react\"\nimport { useEffect, useState } fr"
},
{
"path": "packages/app/src/Components/LiveStream/LiveEvent.tsx",
"chars": 3246,
"preview": "import { NostrLink, type TaggedNostrEvent } from \"@snort/system\"\nimport { lazy, Suspense, useState } from \"react\"\nimport"
},
{
"path": "packages/app/src/Components/LiveStream/LiveStreams.tsx",
"chars": 4311,
"preview": "import { type NostrEvent, NostrLink } from \"@snort/system\"\nimport { useUserProfile } from \"@snort/system-react\"\nimport c"
},
{
"path": "packages/app/src/Components/LiveStream/VU.tsx",
"chars": 2263,
"preview": "import { useEffect, useRef } from \"react\"\n\nexport default function VuBar({\n track,\n full,\n width,\n height,\n classNa"
},
{
"path": "packages/app/src/Components/LiveStream/livekit.tsx",
"chars": 10160,
"preview": "/* eslint-disable max-lines */\nimport {\n LiveKitRoom as LiveKitRoomContext,\n RoomAudioRenderer,\n useEnsureRoom,\n use"
},
{
"path": "packages/app/src/Components/LiveStream/nests-participants.tsx",
"chars": 925,
"preview": "import { dedupe, unixNow } from \"@snort/shared\"\nimport { type EventKind, NostrLink, RequestBuilder, type TaggedNostrEven"
},
{
"path": "packages/app/src/Components/Modal/Modal.tsx",
"chars": 2517,
"preview": "import type React from \"react\"\nimport { type ReactNode, useEffect, useLayoutEffect, useRef } from \"react\"\nimport { creat"
},
{
"path": "packages/app/src/Components/Nip5Service.tsx",
"chars": 10815,
"preview": "import { mapEventToProfile, type UserMetadata } from \"@snort/system\"\nimport { useUserProfile } from \"@snort/system-react"
},
{
"path": "packages/app/src/Components/Offline.tsx",
"chars": 678,
"preview": "import classNames from \"classnames\"\nimport { FormattedMessage } from \"react-intl\"\n\nimport Icon from \"@/Components/Icons/"
},
{
"path": "packages/app/src/Components/PageSpinner.tsx",
"chars": 214,
"preview": "import Spinner from \"@/Components/Icons/Spinner\"\n\nexport default function PageSpinner() {\n return (\n <div className="
},
{
"path": "packages/app/src/Components/PinPrompt/PinPrompt.tsx",
"chars": 4730,
"preview": "import { unwrap } from \"@snort/shared\"\nimport { EventPublisher, InvalidPinError, PinEncrypted } from \"@snort/system\"\nimp"
},
{
"path": "packages/app/src/Components/Progress/Progress.tsx",
"chars": 580,
"preview": "import type { ReactNode } from \"react\"\nimport { FormattedNumber } from \"react-intl\"\n\nexport default function Progress({ "
},
{
"path": "packages/app/src/Components/ProxyImg.tsx",
"chars": 2361,
"preview": "import { forwardRef, type HTMLProps, memo, type ReactNode, useEffect, useState } from \"react\"\nimport { FormattedMessage "
},
{
"path": "packages/app/src/Components/QrCode.tsx",
"chars": 1308,
"preview": "import QRCodeStyling from \"qr-code-styling\"\nimport { useEffect, useRef } from \"react\"\n\nexport interface QrCodeProps {\n "
},
{
"path": "packages/app/src/Components/ReBroadcaster.tsx",
"chars": 2702,
"preview": "import type { OkResponse, TaggedNostrEvent } from \"@snort/system\"\nimport { SnortContext } from \"@snort/system-react\"\nimp"
},
{
"path": "packages/app/src/Components/Relay/Relay.tsx",
"chars": 1376,
"preview": "import { Link } from \"react-router-dom\"\n\nimport useRelayState from \"@/Feed/RelayState\"\nimport useLogin from \"@/Hooks/use"
},
{
"path": "packages/app/src/Components/Relay/RelaysMetadata.tsx",
"chars": 591,
"preview": "import { useState } from \"react\"\n\nimport Nostrich from \"@/assets/img/nostrich.webp\"\n\nexport const RelayFavicon = ({ url,"
},
{
"path": "packages/app/src/Components/Relay/name.tsx",
"chars": 534,
"preview": "import useRelayState from \"@/Feed/RelayState\"\nimport { getRelayName } from \"@/Utils\"\nimport { Nip11, type RelayInfoDocum"
},
{
"path": "packages/app/src/Components/Relay/paid.tsx",
"chars": 609,
"preview": "import type { RelayInfoDocument } from \"@snort/system\"\nimport classNames from \"classnames\"\nimport { FormattedMessage } f"
},
{
"path": "packages/app/src/Components/Relay/permissions.tsx",
"chars": 1027,
"preview": "import type { ConnectionType } from \"@snort/system/dist/connection-pool\"\nimport { FormattedMessage } from \"react-intl\"\n\n"
},
{
"path": "packages/app/src/Components/Relay/software.tsx",
"chars": 297,
"preview": "import { Link } from \"react-router-dom\"\n\nexport default function RelaySoftware({ software }: { software: string }) {\n i"
},
{
"path": "packages/app/src/Components/Relay/status-label.tsx",
"chars": 593,
"preview": "import type { ConnectionType } from \"@snort/system/dist/connection-pool\"\nimport classNames from \"classnames\"\nimport { Fo"
},
{
"path": "packages/app/src/Components/Relay/uptime-label.tsx",
"chars": 847,
"preview": "import classNames from \"classnames\"\nimport { FormattedMessage } from \"react-intl\"\n\nexport default function UptimeLabel({"
},
{
"path": "packages/app/src/Components/Relay/uptime.tsx",
"chars": 1121,
"preview": "import { sanitizeRelayUrl, unixNow } from \"@snort/shared\"\nimport { type EventKind, RequestBuilder } from \"@snort/system\""
},
{
"path": "packages/app/src/Components/Review.tsx",
"chars": 532,
"preview": "import { type EventKind, type NostrLink, RequestBuilder } from \"@snort/system\"\nimport { useRequestBuilder } from \"@snort"
},
{
"path": "packages/app/src/Components/RightWidgets/articles.tsx",
"chars": 2189,
"preview": "import { NostrLink } from \"@snort/system\"\nimport { useState } from \"react\"\nimport { FormattedMessage } from \"react-intl\""
},
{
"path": "packages/app/src/Components/RightWidgets/base.tsx",
"chars": 814,
"preview": "import type { ReactNode } from \"react\"\n\nimport Icon from \"../Icons/Icon\"\n\nexport interface BaseWidgetProps {\n title?: R"
},
{
"path": "packages/app/src/Components/RightWidgets/index.tsx",
"chars": 162,
"preview": "export enum RightColumnWidget {\n TaskList,\n TrendingNotes,\n TrendingPeople,\n TrendingHashtags,\n LatestArticls,\n Li"
}
]
// ... and 543 more files (download for full content)
About this extraction
This page contains the full source code of the v0l/snort GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 743 files (4.8 MB), approximately 1.3M tokens, and a symbol index with 2439 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.