diff --git a/apps/self-hosted/config.template.json b/apps/self-hosted/config.template.json index 0585e60a11..3f83320980 100644 --- a/apps/self-hosted/config.template.json +++ b/apps/self-hosted/config.template.json @@ -46,7 +46,12 @@ } }, "features": { - "postsFilters": ["blog", "posts", "comments", "replies"], + "postsFilters": [ + "blog", + "posts", + "comments", + "replies" + ], "likes": { "enabled": true }, @@ -60,9 +65,28 @@ }, "auth": { "enabled": true, - "methods": ["keychain", "hivesigner", "hiveauth"] + "methods": [ + "keychain", + "hivesigner", + "hiveauth" + ] + }, + "tipping": { + "general": { + "enabled": false, + "buttonLabel": "Tip" + }, + "post": { + "enabled": false, + "buttonLabel": "Tip" + }, + "amounts": [ + 1, + 5, + 10 + ] } } } } -} +} \ No newline at end of file diff --git a/apps/self-hosted/package.json b/apps/self-hosted/package.json index 37cfd235cf..a4d27a4bd7 100644 --- a/apps/self-hosted/package.json +++ b/apps/self-hosted/package.json @@ -11,6 +11,8 @@ "preview": "rsbuild preview" }, "dependencies": { + "@floating-ui/dom": "^1.7.4", + "@floating-ui/react-dom": "^2.1.6", "@ecency/render-helper": "workspace:*", "@ecency/sdk": "workspace:*", "@ecency/ui": "workspace:*", @@ -38,6 +40,7 @@ "hivesigner": "^3.3.5", "marked": "^12.0.0", "motion": "^12.23.22", + "qrcode": "^1.5.4", "qrcode.react": "^4.2.0", "react": "^19.1.1", "react-dom": "^19.1.1", @@ -50,6 +53,7 @@ "devDependencies": { "@biomejs/biome": "2.2.3", "@rsbuild/core": "^1.5.6", + "@rsbuild/plugin-node-polyfill": "^1.4.4", "@rsbuild/plugin-react": "^1.4.0", "@tailwindcss/postcss": "^4.1.13", "@tanstack/react-query-devtools": "^5.90.2", @@ -58,6 +62,7 @@ "@types/dompurify": "^3.0.5", "@types/node": "^24.6.0", "@types/turndown": "^5.0.0", + "@types/qrcode": "^1.5.5", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@types/speakingurl": "^13.0.6", diff --git a/apps/self-hosted/rsbuild.config.ts b/apps/self-hosted/rsbuild.config.ts index a489052624..0847996c0b 100644 --- a/apps/self-hosted/rsbuild.config.ts +++ b/apps/self-hosted/rsbuild.config.ts @@ -1,16 +1,25 @@ import path from 'node:path'; +import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; import { defineConfig } from '@rsbuild/core'; +import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'; import { pluginReact } from '@rsbuild/plugin-react'; import { tanstackRouter } from '@tanstack/router-plugin/rspack'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const require = createRequire(import.meta.url); +const bip39Resolved = require.resolve('bip39', { + paths: [__dirname, path.join(__dirname, 'node_modules/@ecency/wallets')], +}); export default defineConfig({ - plugins: [pluginReact()], + plugins: [pluginReact(), pluginNodePolyfill()], resolve: { alias: { '@': './src', + // bip39 has only named exports; wallets uses default import — shim provides default + 'bip39': path.resolve(__dirname, 'src/shim-bip39.ts'), + 'bip39-original': bip39Resolved, }, }, tools: { @@ -22,6 +31,8 @@ export default defineConfig({ react: path.resolve(__dirname, 'node_modules/react'), 'react-dom': path.resolve(__dirname, 'node_modules/react-dom'), '@ecency/ui': path.resolve(__dirname, '../../packages/ui/dist/index.js'), + 'bip39': path.resolve(__dirname, 'src/shim-bip39.ts'), + 'bip39-original': bip39Resolved, }, }, plugins: [ diff --git a/apps/self-hosted/src/core/configuration-loader.ts b/apps/self-hosted/src/core/configuration-loader.ts index e5edb7f55a..b16072c58b 100644 --- a/apps/self-hosted/src/core/configuration-loader.ts +++ b/apps/self-hosted/src/core/configuration-loader.ts @@ -68,6 +68,12 @@ export interface InstanceConfig { post: { text2Speech: { enabled: boolean }; }; + tipping?: { + enabled?: boolean; + general?: { enabled: boolean; buttonLabel?: string }; + post?: { enabled: boolean; buttonLabel?: string }; + amounts?: number[]; + }; auth: { enabled: boolean; methods: string[]; diff --git a/apps/self-hosted/src/core/i18n.ts b/apps/self-hosted/src/core/i18n.ts index 08addb98f4..7795832e37 100644 --- a/apps/self-hosted/src/core/i18n.ts +++ b/apps/self-hosted/src/core/i18n.ts @@ -1,4 +1,4 @@ -import { InstanceConfigManager } from './configuration-loader'; +import { InstanceConfigManager } from "./configuration-loader"; // Translation keys used throughout the app type TranslationKey = @@ -60,71 +60,100 @@ type TranslationKey = | 'reblog_to_followers' | 'error_loading' | 'retry' - | 'community_not_found'; + | 'community_not_found' + | "tip_amount" + | "tip_custom" + | "tip_currency" + | "tip_private_key" + | "tip_wallet_address" + | "tip_no_wallet_address" + | "tip_send" + | "tip_sending" + | "tip_login_to_send" + | "tip_asset_not_supported" + | "tip_transaction_failed" + | "tip_qr_no_address" + | "tip_qr_failed" + | "cancel"; type Translations = Record; const translations: Record = { en: { - loading: 'Loading...', - loadingPost: 'Loading post...', - loadingMore: 'Loading more posts...', - postNotFound: 'Post not found.', - noPosts: 'No posts found.', - followers: 'Followers', - following: 'Following', - hiveInfo: 'Hive Info', - reputation: 'Reputation', - joined: 'Joined', - posts: 'Posts', - location: 'Location', - website: 'Website', - likes: 'likes', - comments: 'comments', - reblogs: 'reblogs', - replies: 'Replies', - blog: 'Blog', - newest: 'Newest', - trending: 'Trending', - authorReputation: 'Author Reputation', - votes: 'Votes', - discussion: 'Discussion', - readTime: 'read', - minRead: 'min read', - login: 'Login', - logout: 'Logout', - login_to_comment: 'Login to leave a comment', - login_to_vote: 'Login to vote', - login_to_reblog: 'Login to reblog', - write_comment: 'Write a comment...', - posting: 'Posting...', - post_comment: 'Post Comment', - create_post: 'Create Post', - subscribers: 'Subscribers', - authors: 'Authors', - community_info: 'Community Info', - created: 'Created', - language: 'Language', - pending_posts: 'Pending Posts', - team: 'Team', - search: 'Search', - searching: 'Searching...', - search_error: 'Search failed. Please try again.', - no_results: 'No results found.', - results_for: 'results for', - enter_search_query: 'Enter a search term to find posts.', - listen: 'Listen', - pause: 'Pause', - resume: 'Resume', - stop: 'Stop', - reblogging: 'Reblogging...', - reblog_confirm: 'Are you sure you want to reblog this post to your followers?', + loading: "Loading...", + loadingPost: "Loading post...", + loadingMore: "Loading more posts...", + postNotFound: "Post not found.", + noPosts: "No posts found.", + followers: "Followers", + following: "Following", + hiveInfo: "Hive Info", + reputation: "Reputation", + joined: "Joined", + posts: "Posts", + location: "Location", + website: "Website", + likes: "likes", + comments: "comments", + reblogs: "reblogs", + replies: "Replies", + blog: "Blog", + newest: "Newest", + trending: "Trending", + authorReputation: "Author Reputation", + votes: "Votes", + discussion: "Discussion", + readTime: "read", + minRead: "min read", + login: "Login", + logout: "Logout", + login_to_comment: "Login to leave a comment", + login_to_vote: "Login to vote", + login_to_reblog: "Login to reblog", + write_comment: "Write a comment...", + posting: "Posting...", + post_comment: "Post Comment", + create_post: "Create Post", + subscribers: "Subscribers", + authors: "Authors", + community_info: "Community Info", + created: "Created", + language: "Language", + pending_posts: "Pending Posts", + team: "Team", + search: "Search", + searching: "Searching...", + search_error: "Search failed. Please try again.", + no_results: "No results found.", + results_for: "results for", + enter_search_query: "Enter a search term to find posts.", + listen: "Listen", + pause: "Pause", + resume: "Resume", + stop: "Stop", + reblogging: "Reblogging...", + reblog_confirm: + "Are you sure you want to reblog this post to your followers?", cant_reblog_own: "You can't reblog your own post", already_reblogged: 'Already reblogged', reblog_to_followers: 'Reblog to your followers', error_loading: 'Something went wrong. Please try again.', retry: 'Retry', community_not_found: 'Community not found.', + tip_amount: "Amount", + tip_custom: "Custom", + tip_currency: "Currency", + tip_private_key: "Active key", + tip_wallet_address: "Wallet address", + tip_no_wallet_address: "Recipient has not set up this wallet address.", + tip_send: "Tip", + tip_sending: "Sending...", + tip_login_to_send: "Login to send a tip", + tip_asset_not_supported: "This asset is not supported for tipping yet", + tip_transaction_failed: "Transaction failed", + tip_qr_no_address: "No address", + tip_qr_failed: "Failed to generate QR", + cancel: "Cancel", }, es: { loading: 'Cargando...', @@ -249,65 +278,80 @@ const translations: Record = { community_not_found: 'Community nicht gefunden.', }, fr: { - loading: 'Chargement...', + loading: "Chargement...", loadingPost: "Chargement de l'article...", loadingMore: "Chargement d'autres articles...", - postNotFound: 'Article non trouvé.', - noPosts: 'Aucun article trouvé.', - followers: 'Abonnés', - following: 'Abonnements', - hiveInfo: 'Info Hive', - reputation: 'Réputation', - joined: 'Inscrit', - posts: 'Articles', - location: 'Lieu', - website: 'Site web', + postNotFound: "Article non trouvé.", + noPosts: "Aucun article trouvé.", + followers: "Abonnés", + following: "Abonnements", + hiveInfo: "Info Hive", + reputation: "Réputation", + joined: "Inscrit", + posts: "Articles", + location: "Lieu", + website: "Site web", likes: "j'aime", - comments: 'commentaires', - reblogs: 'repartages', - replies: 'Réponses', - blog: 'Blog', - newest: 'Plus récent', - trending: 'Tendances', + comments: "commentaires", + reblogs: "repartages", + replies: "Réponses", + blog: "Blog", + newest: "Plus récent", + trending: "Tendances", authorReputation: "Réputation de l'auteur", - votes: 'Votes', - discussion: 'Discussion', - readTime: 'lecture', - minRead: 'min de lecture', - login: 'Connexion', - logout: 'Déconnexion', - login_to_comment: 'Connectez-vous pour commenter', - login_to_vote: 'Connectez-vous pour voter', - login_to_reblog: 'Connectez-vous pour repartager', - write_comment: 'Écrire un commentaire...', - posting: 'Publication...', - post_comment: 'Publier le commentaire', - create_post: 'Créer un article', - subscribers: 'Abonnés', - authors: 'Auteurs', - community_info: 'Info Communauté', - created: 'Créé', - language: 'Langue', - pending_posts: 'Articles en attente', - team: 'Équipe', - search: 'Rechercher', - searching: 'Recherche...', - search_error: 'La recherche a échoué. Veuillez réessayer.', - no_results: 'Aucun résultat trouvé.', - results_for: 'résultats pour', - enter_search_query: 'Entrez un terme pour rechercher des articles.', - listen: 'Écouter', - pause: 'Pause', - resume: 'Reprendre', - stop: 'Arrêter', - reblogging: 'Repartage...', - reblog_confirm: 'Êtes-vous sûr de vouloir repartager cet article à vos abonnés?', - cant_reblog_own: 'Vous ne pouvez pas repartager votre propre article', - already_reblogged: 'Déjà repartagé', - reblog_to_followers: 'Repartager à vos abonnés', + votes: "Votes", + discussion: "Discussion", + readTime: "lecture", + minRead: "min de lecture", + login: "Connexion", + logout: "Déconnexion", + login_to_comment: "Connectez-vous pour commenter", + login_to_vote: "Connectez-vous pour voter", + login_to_reblog: "Connectez-vous pour repartager", + write_comment: "Écrire un commentaire...", + posting: "Publication...", + post_comment: "Publier le commentaire", + create_post: "Créer un article", + subscribers: "Abonnés", + authors: "Auteurs", + community_info: "Info Communauté", + created: "Créé", + language: "Langue", + pending_posts: "Articles en attente", + team: "Équipe", + search: "Rechercher", + searching: "Recherche...", + search_error: "La recherche a échoué. Veuillez réessayer.", + no_results: "Aucun résultat trouvé.", + results_for: "résultats pour", + enter_search_query: "Entrez un terme pour rechercher des articles.", + listen: "Écouter", + pause: "Pause", + resume: "Reprendre", + stop: "Arrêter", + reblogging: "Repartage...", + reblog_confirm: + "Êtes-vous sûr de vouloir repartager cet article à vos abonnés?", + cant_reblog_own: "Vous ne pouvez pas repartager votre propre article", + already_reblogged: "Déjà repartagé", + reblog_to_followers: "Repartager à vos abonnés", error_loading: "Une erreur s'est produite. Veuillez réessayer.", retry: 'Réessayer', community_not_found: 'Communauté introuvable.', + tip_amount: "Montant", + tip_custom: "Personnalisé", + tip_currency: "Devise", + tip_private_key: "Clé active", + tip_wallet_address: "Adresse du portefeuille", + tip_no_wallet_address: "Le destinataire n'a pas configuré cette adresse.", + tip_send: "Pourboire", + tip_sending: "Envoi...", + tip_login_to_send: "Connectez-vous pour envoyer un pourboire", + tip_asset_not_supported: "Cet actif n'est pas encore pris en charge pour les pourboires", + tip_transaction_failed: "Échec de la transaction", + tip_qr_no_address: "Aucune adresse", + tip_qr_failed: "Échec de la génération du QR", + cancel: "Annuler", }, ko: { loading: '로딩 중...', diff --git a/apps/self-hosted/src/features/auth/auth-actions.ts b/apps/self-hosted/src/features/auth/auth-actions.ts index 9d0291f01e..11f508034d 100644 --- a/apps/self-hosted/src/features/auth/auth-actions.ts +++ b/apps/self-hosted/src/features/auth/auth-actions.ts @@ -94,20 +94,28 @@ export function logout(): void { clearHiveAuthSession(); } +export type BroadcastAuthorityType = "Active" | "Posting" | "Owner" | "Memo"; + /** * Broadcast operations using the current user's auth method. * Throws if not authenticated or session/keychain is missing. + * @param authorityType - For keychain: which key to use (e.g. "Active" for transfers). */ -export async function broadcast(operations: Operation[]): Promise { +export async function broadcast( + operations: Operation[], + options?: { authorityType?: BroadcastAuthorityType } +): Promise { const { user, session } = authenticationStore.getState(); if (!user) { throw new Error("Not authenticated"); } + const authorityType = options?.authorityType ?? "Posting"; + switch (user.loginType) { case "keychain": - return keychainBroadcast(user.username, operations); + return keychainBroadcast(user.username, operations, authorityType); case "hivesigner": if (!user.accessToken) { diff --git a/apps/self-hosted/src/features/blog/components/blog-post-footer.tsx b/apps/self-hosted/src/features/blog/components/blog-post-footer.tsx index fccb7851d3..73a45f5a53 100644 --- a/apps/self-hosted/src/features/blog/components/blog-post-footer.tsx +++ b/apps/self-hosted/src/features/blog/components/blog-post-footer.tsx @@ -5,6 +5,7 @@ import { UilComment } from '@tooni/iconscout-unicons-react'; import { useMemo } from 'react'; import { InstanceConfigManager, t } from '@/core'; import { VoteButton, ReblogButton } from '@/features/auth'; +import { TipButton } from '@/features/tipping'; interface Props { entry: Entry; @@ -21,6 +22,10 @@ export function BlogPostFooter({ entry }: Props) { ({ configuration }) => configuration.instanceConfiguration.features.comments?.enabled ?? true, ); + const showTippingPost = InstanceConfigManager.getConfigValue( + ({ configuration }) => + configuration.instanceConfiguration.features.tipping?.post?.enabled ?? false, + ); const commentsCount = entryData.children || 0; const reblogsCount = entryData.reblogs || 0; @@ -67,6 +72,14 @@ export function BlogPostFooter({ entry }: Props) { permlink={entryData.permlink} reblogCount={reblogsCount} /> + {showTippingPost && ( + + )} ); diff --git a/apps/self-hosted/src/features/blog/layout/blog-sidebar.tsx b/apps/self-hosted/src/features/blog/layout/blog-sidebar.tsx index 601cbdc3e0..5d4aaa5602 100644 --- a/apps/self-hosted/src/features/blog/layout/blog-sidebar.tsx +++ b/apps/self-hosted/src/features/blog/layout/blog-sidebar.tsx @@ -1,5 +1,6 @@ import { formatMonthYear, InstanceConfigManager, t } from "@/core"; import { UserAvatar } from "@/features/shared/user-avatar"; +import { TipButton } from "@/features/tipping"; import { getAccountFullQueryOptions } from "@ecency/sdk"; import { useQuery } from "@tanstack/react-query"; import { useMemo } from "react"; @@ -24,6 +25,11 @@ function BlogSidebarContent({ username }: { username: string }) { enabled: !!username, }); + const showTippingGeneral = InstanceConfigManager.getConfigValue( + ({ configuration }) => + configuration.instanceConfiguration.features.tipping?.general?.enabled ?? false + ); + const joinDate = useMemo(() => { if (!data?.created) return null; return formatMonthYear(data.created); @@ -97,6 +103,15 @@ function BlogSidebarContent({ username }: { username: string }) { )} )} + {showTippingGeneral && ( +
+ +
+ )} {data?.profile?.location && (
{t("location")}:{" "} diff --git a/apps/self-hosted/src/features/floating-menu/config-fields.ts b/apps/self-hosted/src/features/floating-menu/config-fields.ts index 3060d093b1..72172755da 100644 --- a/apps/self-hosted/src/features/floating-menu/config-fields.ts +++ b/apps/self-hosted/src/features/floating-menu/config-fields.ts @@ -200,6 +200,50 @@ export const configFieldsMap: Record = { }, }, }, + tipping: { + label: 'Tipping', + type: 'section', + description: 'Tip button in posts and sidebar', + fields: { + general: { + label: 'Sidebar (General)', + type: 'section', + fields: { + enabled: { + label: 'Enabled', + type: 'boolean', + description: 'Show Tip button in sidebar', + }, + buttonLabel: { + label: 'Button Label', + type: 'string', + description: 'Custom label for Tip button (e.g. Tip)', + }, + }, + }, + post: { + label: 'Post', + type: 'section', + fields: { + enabled: { + label: 'Enabled', + type: 'boolean', + description: 'Show Tip button in post footer', + }, + buttonLabel: { + label: 'Button Label', + type: 'string', + description: 'Custom label for Tip button (e.g. Tip)', + }, + }, + }, + amounts: { + label: 'Preset Amounts', + type: 'array', + description: 'Preset amounts in USD for tip buttons (e.g. 1, 5, 10)', + }, + }, + }, auth: { label: 'Authentication', type: 'section', diff --git a/apps/self-hosted/src/features/tipping/components/tip-button.tsx b/apps/self-hosted/src/features/tipping/components/tip-button.tsx new file mode 100644 index 0000000000..b9f972b4ac --- /dev/null +++ b/apps/self-hosted/src/features/tipping/components/tip-button.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { autoUpdate, offset } from "@floating-ui/dom"; +import { flip, shift, useFloating } from "@floating-ui/react-dom"; +import { useState } from "react"; +import { UilDollarSign } from "@tooni/iconscout-unicons-react"; +import { useTippingConfig } from "../hooks/use-tipping-config"; +import { TippingPopover } from "./tipping-popover"; +import type { TippingVariant } from "../types"; + +interface TipButtonProps { + recipientUsername: string; + variant: TippingVariant; + memo?: string; + className?: string; +} + +export function TipButton({ + recipientUsername, + variant, + memo = "", + className, +}: TipButtonProps) { + const { enabled, buttonLabel, presetAmounts } = useTippingConfig(variant); + const [open, setOpen] = useState(false); + + const { refs, floatingStyles } = useFloating({ + placement: "bottom-start", + middleware: [offset(8), flip(), shift({ padding: 8 })], + whileElementsMounted: autoUpdate, + }); + + if (!enabled) return undefined; + + return ( + <> + + {open && ( + setOpen(false)} + refs={refs} + floatingStyles={floatingStyles} + /> + )} + + ); +} diff --git a/apps/self-hosted/src/features/tipping/components/tipping-currency-card.tsx b/apps/self-hosted/src/features/tipping/components/tipping-currency-card.tsx new file mode 100644 index 0000000000..730cd346d4 --- /dev/null +++ b/apps/self-hosted/src/features/tipping/components/tipping-currency-card.tsx @@ -0,0 +1,36 @@ +import { useInstanceConfig } from "@/features/blog/hooks/use-instance-config"; +import { getAccountWalletAssetInfoQueryOptions } from "@ecency/wallets"; +import { useQuery } from "@tanstack/react-query"; +import clsx from "clsx"; + +interface Props { + asset: string; + selectedAsset: string | undefined; + onAssetSelect: (value: string) => void; +} + +export function TippingCurrencyCard({ + asset, + selectedAsset, + onAssetSelect, +}: Props) { + const { username } = useInstanceConfig(); + const { data } = useQuery( + getAccountWalletAssetInfoQueryOptions(username, asset), + ); + return ( + + ); +} diff --git a/apps/self-hosted/src/features/tipping/components/tipping-currency-cards.tsx b/apps/self-hosted/src/features/tipping/components/tipping-currency-cards.tsx new file mode 100644 index 0000000000..c5781c6955 --- /dev/null +++ b/apps/self-hosted/src/features/tipping/components/tipping-currency-cards.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { useInstanceConfig } from "@/features/blog/hooks/use-instance-config"; +import { getAccountWalletListQueryOptions } from "@ecency/wallets"; +import { useQuery } from "@tanstack/react-query"; +import { TippingCurrencyCard } from "./tipping-currency-card"; +import { isExternalWalletAsset } from "../types"; + +interface TippingCurrencyCardsProps { + selectedAsset: string | undefined; + onAssetSelect: (asset: string) => void; +} + +export function TippingCurrencyCards({ + selectedAsset, + onAssetSelect, +}: TippingCurrencyCardsProps) { + const { username } = useInstanceConfig(); + const { data: walletList } = useQuery({ + ...getAccountWalletListQueryOptions(username, "usd"), + select: (data) => + data.filter( + (asset) => + ["HIVE", "HP", "HBD", "POINTS"].includes(asset) || + isExternalWalletAsset(asset), + ), + }); + + return ( +
+ {walletList?.map((asset) => ( + onAssetSelect(asset)} + /> + ))} +
+ ); +} diff --git a/apps/self-hosted/src/features/tipping/components/tipping-popover.tsx b/apps/self-hosted/src/features/tipping/components/tipping-popover.tsx new file mode 100644 index 0000000000..1b7d5edf12 --- /dev/null +++ b/apps/self-hosted/src/features/tipping/components/tipping-popover.tsx @@ -0,0 +1,157 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; +import { createPortal } from "react-dom"; +import { useAuth } from "@/features/auth/hooks"; +import { executeTip } from "../utils/tip-transaction"; +import { TippingStepAmount } from "./tipping-step-amount"; +import { TippingStepCurrency } from "./tipping-step-currency"; +import { isExternalWalletAsset, type TippingAsset } from "../types"; + +interface TippingPopoverRefs { + setReference: (element: HTMLElement | null) => void; + setFloating: (element: HTMLElement | null) => void; + reference: React.RefObject; + floating: React.RefObject; +} + +interface TippingPopoverProps { + to: string; + memo: string; + presetAmounts: number[]; + onClose: () => void; + refs: TippingPopoverRefs; + floatingStyles: React.CSSProperties; +} + +type Step = "amount" | "currency"; + +export function TippingPopover({ + to, + memo, + presetAmounts, + onClose, + refs, + floatingStyles, +}: TippingPopoverProps) { + const { user } = useAuth(); + const [step, setStep] = useState("amount"); + const [selectedPreset, setSelectedPreset] = useState< + number | "custom" | undefined + >(undefined); + const [amount, setAmount] = useState(""); + const [selectedAsset, setSelectedAsset] = useState( + undefined, + ); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(undefined); + + const amountNum = parseFloat(amount); + const hasValidAmount = Number.isFinite(amountNum) && amountNum > 0; + const isExternalAsset = + selectedAsset !== undefined && isExternalWalletAsset(selectedAsset); + const canSubmit = + hasValidAmount && + selectedAsset !== undefined && + !isExternalAsset && + !!user?.username && + !loading; + + const handleClickOutside = useCallback( + (e: MouseEvent) => { + const refRef = refs.reference.current; + const refFloating = refs.floating.current; + const referenceEl = + refRef != null && typeof refRef === "object" && "contains" in refRef + ? (refRef as HTMLElement) + : null; + const floatingEl = + refFloating != null && + typeof refFloating === "object" && + "contains" in refFloating + ? (refFloating as HTMLElement) + : null; + if ( + floatingEl?.contains(e.target as Node) || + referenceEl?.contains(e.target as Node) + ) { + return; + } + onClose(); + }, + [refs.reference, refs.floating, onClose], + ); + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [handleClickOutside]); + + const handlePreset = (value: number | "custom") => { + setSelectedPreset(value); + setAmount(value === "custom" ? "" : String(value)); + setStep("currency"); + setError(undefined); + }; + + const handleSubmit = useCallback(async () => { + if (!canSubmit || !user?.username) return; + if ( + selectedAsset !== "HIVE" && + selectedAsset !== "HBD" && + selectedAsset !== "POINTS" + ) { + setError("This asset is not supported for tipping yet"); + return; + } + setLoading(true); + setError(undefined); + try { + await executeTip({ + from: user.username, + to, + amount, + asset: selectedAsset as TippingAsset, + memo, + }); + onClose(); + } catch (err) { + setError(err instanceof Error ? err.message : "Transaction failed"); + } finally { + setLoading(false); + } + }, [canSubmit, user?.username, to, amount, selectedAsset, memo, onClose]); + + const content = ( +
+ {step === "amount" && ( + + )} + {step === "currency" && ( + + )} +
+ ); + + return createPortal(content, document.body); +} diff --git a/apps/self-hosted/src/features/tipping/components/tipping-step-amount.tsx b/apps/self-hosted/src/features/tipping/components/tipping-step-amount.tsx new file mode 100644 index 0000000000..0b0cd078ce --- /dev/null +++ b/apps/self-hosted/src/features/tipping/components/tipping-step-amount.tsx @@ -0,0 +1,41 @@ +import { t } from "@/core"; + +interface TippingStepAmountProps { + presetAmounts: number[]; + onSelect: (value: number | "custom") => void; +} + +const buttonClassName = + "px-3 py-2 rounded-md border border-theme bg-theme-primary text-theme-primary hover:bg-theme-tertiary text-sm"; + +export function TippingStepAmount({ + presetAmounts, + onSelect, +}: TippingStepAmountProps) { + return ( + <> +
+ {t("tip_amount")} +
+
+ {presetAmounts.map((val) => ( + + ))} + +
+ + ); +} diff --git a/apps/self-hosted/src/features/tipping/components/tipping-step-currency.tsx b/apps/self-hosted/src/features/tipping/components/tipping-step-currency.tsx new file mode 100644 index 0000000000..fd56075687 --- /dev/null +++ b/apps/self-hosted/src/features/tipping/components/tipping-step-currency.tsx @@ -0,0 +1,124 @@ +import { t } from "@/core"; +import { getAccountFullQueryOptions } from "@ecency/sdk"; +import { useQuery } from "@tanstack/react-query"; +import { isExternalWalletAsset } from "../types"; +import { TippingCurrencyCards } from "./tipping-currency-cards"; +import { TippingWalletQr } from "./tipping-wallet-qr"; +import { useAuth } from "@/features/auth"; + +interface TippingStepCurrencyProps { + to: string; + amount: string; + onAmountChange: (value: string) => void; + selectedAsset: string | undefined; + onAssetSelect: (asset: string) => void; + error: string | undefined; + canSubmit: boolean; + loading: boolean; + onCancel: () => void; + onSubmit: () => void; +} + +const inputClassName = + "w-full mb-3 px-3 py-2 rounded-md border border-theme bg-theme-primary text-theme-primary text-sm"; + +function useRecipientWalletAddress(to: string, asset: string | undefined) { + const enabled = !!to && !!asset && isExternalWalletAsset(asset); + const { data: account } = useQuery({ + ...getAccountFullQueryOptions(to), + enabled, + }); + if (!enabled || !account?.profile?.tokens) return undefined; + const token = account.profile.tokens.find( + (token) => token.symbol?.toUpperCase() === asset?.toUpperCase(), + ); + const address = token?.meta?.address; + return typeof address === "string" && address.trim() ? address : undefined; +} + +export function TippingStepCurrency({ + to, + amount, + onAmountChange, + selectedAsset, + onAssetSelect, + error, + canSubmit, + loading, + onCancel, + onSubmit, +}: TippingStepCurrencyProps) { + const { user } = useAuth(); + + const isExternal = + selectedAsset !== undefined && isExternalWalletAsset(selectedAsset); + const recipientAddress = useRecipientWalletAddress(to, selectedAsset); + + return ( + <> +
+ {t("tip_amount")} +
+ onAmountChange(e.target.value)} + className={inputClassName} + /> +
+ {t("tip_currency")} +
+ + {isExternal && ( + <> +
+ {t("tip_wallet_address")} +
+ {recipientAddress ? ( +
+ + + {recipientAddress} + +
+ ) : ( +
+ {t("tip_no_wallet_address")} +
+ )} + + )} + {error && ( +
+ {error} +
+ )} +
+ + {!isExternal && ( + + )} +
+ + ); +} diff --git a/apps/self-hosted/src/features/tipping/components/tipping-wallet-qr.tsx b/apps/self-hosted/src/features/tipping/components/tipping-wallet-qr.tsx new file mode 100644 index 0000000000..3b36aba2dc --- /dev/null +++ b/apps/self-hosted/src/features/tipping/components/tipping-wallet-qr.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { useEffect, useState } from "react"; +import QRCode from "qrcode"; +import { t } from "@/core"; + +interface TippingWalletQrProps { + address: string; + size?: number; + className?: string; +} + +export function TippingWalletQr({ + address, + size = 180, + className, +}: TippingWalletQrProps) { + const [dataUrl, setDataUrl] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + if (!address.trim()) { + setDataUrl(null); + setError(t("tip_qr_no_address")); + return; + } + setError(null); + QRCode.toDataURL(address, { width: size, margin: 2 }) + .then(setDataUrl) + .catch((err: unknown) => + setError(err instanceof Error ? err.message : t("tip_qr_failed")), + ); + }, [address, size]); + + if (error) { + return ( +
+ {error} +
+ ); + } + if (!dataUrl) { + return ( +
+ +
+ ); + } + return ( + Wallet address QR code + ); +} diff --git a/apps/self-hosted/src/features/tipping/hooks/use-tipping-config.ts b/apps/self-hosted/src/features/tipping/hooks/use-tipping-config.ts new file mode 100644 index 0000000000..45e928f18b --- /dev/null +++ b/apps/self-hosted/src/features/tipping/hooks/use-tipping-config.ts @@ -0,0 +1,23 @@ +import { InstanceConfigManager } from '@/core'; +import { useMemo } from 'react'; +import type { TippingVariant } from '../types'; + +const DEFAULT_PRESETS = [1, 5, 10]; +const DEFAULT_BUTTON_LABEL = 'Tip'; + +export function useTippingConfig(variant: TippingVariant) { + return useMemo(() => { + const config = InstanceConfigManager.getConfig(); + const tipping = config.configuration.instanceConfiguration.features?.tipping; + if (!tipping) { + return { enabled: false, buttonLabel: DEFAULT_BUTTON_LABEL, presetAmounts: DEFAULT_PRESETS }; + } + const sub = variant === 'post' ? tipping.post : tipping.general; + const enabled = Boolean(sub?.enabled); + const buttonLabel = sub?.buttonLabel ?? DEFAULT_BUTTON_LABEL; + const presetAmounts = Array.isArray(tipping.amounts) && tipping.amounts.length > 0 + ? tipping.amounts + : DEFAULT_PRESETS; + return { enabled, buttonLabel, presetAmounts }; + }, [variant]); +} diff --git a/apps/self-hosted/src/features/tipping/index.ts b/apps/self-hosted/src/features/tipping/index.ts new file mode 100644 index 0000000000..472380550d --- /dev/null +++ b/apps/self-hosted/src/features/tipping/index.ts @@ -0,0 +1,3 @@ +export { TipButton } from './components/tip-button'; +export { useTippingConfig } from './hooks/use-tipping-config'; +export type { TippingVariant, TippingAsset, TippingConfig } from './types'; diff --git a/apps/self-hosted/src/features/tipping/types.ts b/apps/self-hosted/src/features/tipping/types.ts new file mode 100644 index 0000000000..f20db149a9 --- /dev/null +++ b/apps/self-hosted/src/features/tipping/types.ts @@ -0,0 +1,41 @@ +export type TippingVariant = "post" | "general"; + +export type TippingAsset = "HIVE" | "HBD" | "POINTS"; + +const TIPABLE_ASSETS: TippingAsset[] = ["HIVE", "HBD", "POINTS"]; + +export function isTipableAsset(asset: string): asset is TippingAsset { + return TIPABLE_ASSETS.includes(asset as TippingAsset); +} + +/** Assets that require active key input for tipping */ +export const ASSETS_REQUIRING_KEY = ["POINTS", "HIVE", "HBD", "HP"] as const; + +/** External wallet symbols (show QR with address, no Tip button) */ +export const EXTERNAL_WALLET_SYMBOLS = [ + "APT", + "BNB", + "BTC", + "ETH", + "SOL", + "TON", + "TRX", +] as const; + +export function isAssetRequiringKey(asset: string): boolean { + return ASSETS_REQUIRING_KEY.includes( + asset as (typeof ASSETS_REQUIRING_KEY)[number], + ); +} + +export function isExternalWalletAsset(asset: string): boolean { + return EXTERNAL_WALLET_SYMBOLS.includes( + asset as (typeof EXTERNAL_WALLET_SYMBOLS)[number], + ); +} + +export interface TippingConfig { + enabled: boolean; + buttonLabel: string; + presetAmounts: number[]; +} diff --git a/apps/self-hosted/src/features/tipping/utils/tip-transaction.ts b/apps/self-hosted/src/features/tipping/utils/tip-transaction.ts new file mode 100644 index 0000000000..3306c83474 --- /dev/null +++ b/apps/self-hosted/src/features/tipping/utils/tip-transaction.ts @@ -0,0 +1,87 @@ +import { broadcast } from "@/features/auth"; +import { getQueryClient } from "@ecency/sdk"; +import { getAccountWalletAssetInfoQueryOptions } from "@ecency/wallets"; +import type { Operation } from "@hiveio/dhive"; +import type { TippingAsset } from "../types"; + +const ASSETS_WITH_USD_PRICE: TippingAsset[] = ["HIVE", "HBD"]; +export interface ExecuteTipParams { + from: string; + to: string; + amount: string; + asset: TippingAsset; + memo: string; +} + +/** + * Execute tip: transfer HIVE, HBD, or POINTS. + * Uses current auth (keychain or hivesigner) to broadcast. + * Fetches token price in USD via getTokenPriceQueryOptions for conversion. + */ +export async function executeTip(params: ExecuteTipParams): Promise { + const { from, to, amount, asset, memo } = params; + + let realAmount = 0; + const num = parseFloat(amount); + if (!Number.isFinite(num) || num <= 0) { + throw new Error("Invalid amount"); + } + const formatted = num.toFixed(3); + + // Ensure USD price is loaded for supported assets (conversion relative to USD) + const queryClient = getQueryClient(); + + const info = await queryClient.ensureQueryData( + getAccountWalletAssetInfoQueryOptions(to, asset), + ); + realAmount = num / (info?.price ?? 0); + + let operations: Operation[]; + + if (asset === "HIVE") { + operations = [ + [ + "transfer", + { + from, + to, + amount: `${realAmount} HIVE`, + memo, + }, + ], + ]; + } else if (asset === "HBD") { + operations = [ + [ + "transfer", + { + from, + to, + amount: `${realAmount} HBD`, + memo, + }, + ], + ]; + } else if (asset === "POINTS") { + operations = [ + [ + "custom_json", + { + id: "ecency_point_transfer", + json: JSON.stringify({ + sender: from, + receiver: to, + amount: `${realAmount} POINT`, + memo, + }), + required_auths: [from], + required_posting_auths: [], + }, + ], + ]; + } else { + throw new Error(`Unsupported asset: ${asset}`); + } + + await broadcast(operations, { authorityType: "Active" }); +} diff --git a/apps/self-hosted/src/shim-bip39.ts b/apps/self-hosted/src/shim-bip39.ts new file mode 100644 index 0000000000..e1a8491d98 --- /dev/null +++ b/apps/self-hosted/src/shim-bip39.ts @@ -0,0 +1,13 @@ +/** + * ESM shim for bip39: the package has only named exports, but @ecency/wallets + * uses both default and named imports. Re-export namespace as default and re-export names. + */ +import * as bip39 from 'bip39-original'; + +export default bip39; +export const generateMnemonic = bip39.generateMnemonic; +export const mnemonicToSeedSync = bip39.mnemonicToSeedSync; +export const mnemonicToSeed = bip39.mnemonicToSeed; +export const entropyToMnemonic = bip39.entropyToMnemonic; +export const mnemonicToEntropy = bip39.mnemonicToEntropy; +export const validateMnemonic = bip39.validateMnemonic; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3ec4fe681..d5ae0c496b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,12 @@ importers: '@ecency/wallets': specifier: workspace:* version: link:../../packages/wallets + '@floating-ui/dom': + specifier: ^1.7.4 + version: 1.7.4 + '@floating-ui/react-dom': + specifier: ^2.1.6 + version: 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@hiveio/dhive': specifier: ^1.3.1-beta version: 1.3.2 @@ -124,6 +130,9 @@ importers: motion: specifier: ^12.23.22 version: 12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + qrcode: + specifier: ^1.5.4 + version: 1.5.4 qrcode.react: specifier: ^4.2.0 version: 4.2.0(react@19.2.3) @@ -155,6 +164,9 @@ importers: '@rsbuild/core': specifier: ^1.5.6 version: 1.7.2 + '@rsbuild/plugin-node-polyfill': + specifier: ^1.4.4 + version: 1.4.4(@rsbuild/core@1.7.2) '@rsbuild/plugin-react': specifier: ^1.4.0 version: 1.4.3(@rsbuild/core@1.7.2) @@ -176,6 +188,9 @@ importers: '@types/node': specifier: ^24.6.0 version: 24.10.8 + '@types/qrcode': + specifier: ^1.5.5 + version: 1.5.5 '@types/react': specifier: ^19.1.16 version: 19.2.8 @@ -3413,6 +3428,14 @@ packages: engines: {node: '>=18.12.0'} hasBin: true + '@rsbuild/plugin-node-polyfill@1.4.4': + resolution: {integrity: sha512-V9Wh8FOprWBOaPvTK6ptj9A+SAkG1X1645sVf6HoIrJRNgtlrBECuadybMLFRBdihr6hiizz417d5RXy6X1hLQ==} + peerDependencies: + '@rsbuild/core': ^1.0.0 || ^2.0.0-0 + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@rsbuild/plugin-react@1.4.3': resolution: {integrity: sha512-Uf9FkKk2TqYDbsypXHgjdivPmEoYFvBTHJT5fPmjCf0Sc3lR2BHtlc0WMdZTCKUJ5jTxREygMs9LbnehX98L8g==} peerDependencies: @@ -4645,6 +4668,10 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -4838,6 +4865,9 @@ packages: asmcrypto.js@2.3.2: resolution: {integrity: sha512-3FgFARf7RupsZETQ1nHnhLUUvpcttcCq1iZCaVAbJZbCZ5VNRrNyvpDyHTOb0KC3llFcsyOT/a99NZcCbeiEsA==} + asn1.js@4.10.1: + resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==} + assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} @@ -5064,6 +5094,23 @@ packages: browserify-aes@1.2.0: resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} + browserify-cipher@1.0.1: + resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==} + + browserify-des@1.0.2: + resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} + + browserify-rsa@4.1.1: + resolution: {integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==} + engines: {node: '>= 0.10'} + + browserify-sign@4.2.5: + resolution: {integrity: sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==} + engines: {node: '>= 0.10'} + + browserify-zlib@0.2.0: + resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} + browserslist@4.26.3: resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -5097,6 +5144,9 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} + builtin-status-codes@3.0.0: + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5278,6 +5328,12 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + console-browserify@1.2.0: + resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} + + constants-browserify@1.0.0: + resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -5314,6 +5370,9 @@ packages: engines: {node: '>=0.8'} hasBin: true + create-ecdh@4.0.4: + resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} + create-hash@1.2.0: resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} @@ -5333,6 +5392,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crypto-browserify@3.12.1: + resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==} + engines: {node: '>= 0.10'} + crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} @@ -5503,6 +5566,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + des.js@1.1.0: + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + detect-file@1.0.0: resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} engines: {node: '>=0.10.0'} @@ -5537,6 +5603,9 @@ packages: resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} engines: {node: '>=0.3.1'} + diffie-hellman@5.0.3: + resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} + dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -5567,6 +5636,10 @@ packages: dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + domain-browser@5.7.0: + resolution: {integrity: sha512-edTFu0M/7wO1pXY6GDxVNVW086uqwWYIHP98txhcPyV995X21JIH2DtYp33sQJOupYoXKe9RwTw2Ya2vWaquTQ==} + engines: {node: '>=4'} + domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} @@ -5904,6 +5977,10 @@ packages: resolution: {integrity: sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==} engines: {node: '>=6.5.0', npm: '>=3'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} @@ -6209,15 +6286,17 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@9.3.5: resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} engines: {node: '>=16 || 14 >=14.17'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-modules@1.0.0: resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} @@ -6284,6 +6363,10 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hash-base@3.0.5: + resolution: {integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==} + engines: {node: '>= 0.10'} + hash-base@3.1.2: resolution: {integrity: sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==} engines: {node: '>= 0.8'} @@ -6359,6 +6442,9 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + https-browserify@1.0.0: + resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} + https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -7114,6 +7200,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + miller-rabin@4.0.1: + resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} + hasBin: true + mime-db@1.25.0: resolution: {integrity: sha512-5k547tI4Cy+Lddr/hdjNbBEWBwSl8EBc5aSdKvedav8DReADgWJzcYiktaRIw3GtGC1jjwldXtTzvqJZmtvC7w==} engines: {node: '>= 0.6'} @@ -7407,6 +7497,9 @@ packages: orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + os-browserify@0.3.0: + resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -7452,10 +7545,17 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-asn1@5.1.9: + resolution: {integrity: sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==} + engines: {node: '>= 0.10'} + parse-passwd@1.0.0: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} @@ -7463,6 +7563,9 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -7771,6 +7874,9 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + public-encrypt@4.0.3: + resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -7802,6 +7908,10 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + querystring-es3@0.2.1: + resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} + engines: {node: '>=0.4.x'} + querystring@0.2.1: resolution: {integrity: sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==} engines: {node: '>=0.4.x'} @@ -7819,6 +7929,9 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + randomfill@1.0.4: + resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -8008,6 +8121,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -8561,6 +8678,12 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + + stream-http@3.2.0: + resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==} + streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} @@ -8800,6 +8923,10 @@ packages: resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} engines: {node: '>=10'} + timers-browserify@2.0.12: + resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} + engines: {node: '>=0.6.0'} + tiny-case@1.0.3: resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} @@ -8949,6 +9076,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tty-browserify@0.0.1: + resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -9304,6 +9434,9 @@ packages: jsdom: optional: true + vm-browserify@1.1.2: + resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} + w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -11806,6 +11939,12 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + '@floating-ui/react-dom@2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + '@floating-ui/utils@0.2.10': {} '@hiveio/dhive@1.3.2': @@ -12986,6 +13125,34 @@ snapshots: core-js: 3.47.0 jiti: 2.6.1 + '@rsbuild/plugin-node-polyfill@1.4.4(@rsbuild/core@1.7.2)': + dependencies: + assert: 2.1.0 + browserify-zlib: 0.2.0 + buffer: 5.7.1 + console-browserify: 1.2.0 + constants-browserify: 1.0.0 + crypto-browserify: 3.12.1 + domain-browser: 5.7.0 + events: 3.3.0 + https-browserify: 1.0.0 + os-browserify: 0.3.0 + path-browserify: 1.0.1 + process: 0.11.10 + punycode: 2.3.1 + querystring-es3: 0.2.1 + readable-stream: 4.7.0 + stream-browserify: 3.0.0 + stream-http: 3.2.0 + string_decoder: 1.3.0 + timers-browserify: 2.0.12 + tty-browserify: 0.0.1 + url: 0.11.4 + util: 0.12.5 + vm-browserify: 1.1.2 + optionalDependencies: + '@rsbuild/core': 1.7.2 + '@rsbuild/plugin-react@1.4.3(@rsbuild/core@1.7.2)': dependencies: '@rsbuild/core': 1.7.2 @@ -13935,7 +14102,7 @@ snapshots: '@types/bn.js@4.11.6': dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 '@types/bs58@4.0.4': dependencies: @@ -13948,7 +14115,7 @@ snapshots: '@types/connect@3.4.36': dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 '@types/crypto-js@4.2.2': {} @@ -13981,7 +14148,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 22.18.8 + '@types/node': 24.10.8 '@types/google.maps@3.58.1': {} @@ -14027,7 +14194,7 @@ snapshots: '@types/mysql@2.15.26': dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 '@types/node@12.20.55': {} @@ -14051,7 +14218,7 @@ snapshots: '@types/pbkdf2@3.1.2': dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 '@types/pg-pool@2.0.6': dependencies: @@ -14059,7 +14226,7 @@ snapshots: '@types/pg@8.6.1': dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -14067,7 +14234,7 @@ snapshots: '@types/qrcode@1.5.5': dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 '@types/react-beautiful-dnd@13.1.8': dependencies: @@ -14114,13 +14281,13 @@ snapshots: '@types/resolve@1.17.1': dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 '@types/rss@0.0.32': {} '@types/secp256k1@4.0.7': dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 '@types/shimmer@1.2.0': {} @@ -14130,7 +14297,7 @@ snapshots: '@types/tedious@4.0.14': dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 '@types/trusted-types@2.0.7': {} @@ -14582,6 +14749,10 @@ snapshots: '@xtuc/long@4.2.2': {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -14787,6 +14958,12 @@ snapshots: asmcrypto.js@2.3.2: {} + asn1.js@4.10.1: + dependencies: + bn.js: 4.12.2 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + assert-plus@1.0.0: {} assert@2.1.0: @@ -15057,6 +15234,41 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 + browserify-cipher@1.0.1: + dependencies: + browserify-aes: 1.2.0 + browserify-des: 1.0.2 + evp_bytestokey: 1.0.3 + + browserify-des@1.0.2: + dependencies: + cipher-base: 1.0.7 + des.js: 1.1.0 + inherits: 2.0.4 + safe-buffer: 5.2.1 + + browserify-rsa@4.1.1: + dependencies: + bn.js: 5.2.2 + randombytes: 2.1.0 + safe-buffer: 5.2.1 + + browserify-sign@4.2.5: + dependencies: + bn.js: 5.2.2 + browserify-rsa: 4.1.1 + create-hash: 1.2.0 + create-hmac: 1.1.7 + elliptic: 6.6.1 + inherits: 2.0.4 + parse-asn1: 5.1.9 + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + + browserify-zlib@0.2.0: + dependencies: + pako: 1.0.11 + browserslist@4.26.3: dependencies: baseline-browser-mapping: 2.9.11 @@ -15095,6 +15307,8 @@ snapshots: builtin-modules@3.3.0: {} + builtin-status-codes@3.0.0: {} + bundle-require@5.1.0(esbuild@0.25.10): dependencies: esbuild: 0.25.10 @@ -15274,6 +15488,10 @@ snapshots: consola@3.4.2: {} + console-browserify@1.2.0: {} + + constants-browserify@1.0.0: {} + convert-source-map@2.0.0: {} cookie-es@2.0.0: {} @@ -15300,6 +15518,11 @@ snapshots: crc-32@1.2.2: {} + create-ecdh@4.0.4: + dependencies: + bn.js: 4.12.2 + elliptic: 6.6.1 + create-hash@1.2.0: dependencies: cipher-base: 1.0.7 @@ -15333,6 +15556,21 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crypto-browserify@3.12.1: + dependencies: + browserify-cipher: 1.0.1 + browserify-sign: 4.2.5 + create-ecdh: 4.0.4 + create-hash: 1.2.0 + create-hmac: 1.1.7 + diffie-hellman: 5.0.3 + hash-base: 3.0.5 + inherits: 2.0.4 + pbkdf2: 3.1.5 + public-encrypt: 4.0.3 + randombytes: 2.1.0 + randomfill: 1.0.4 + crypto-js@4.2.0: {} crypto-random-string@2.0.0: {} @@ -15489,6 +15727,11 @@ snapshots: dequal@2.0.3: {} + des.js@1.1.0: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + detect-file@1.0.0: {} detect-indent@6.1.0: {} @@ -15511,6 +15754,12 @@ snapshots: diff@8.0.3: {} + diffie-hellman@5.0.3: + dependencies: + bn.js: 4.12.2 + miller-rabin: 4.0.1 + randombytes: 2.1.0 + dijkstrajs@1.0.3: {} dir-glob@3.0.1: @@ -15542,6 +15791,8 @@ snapshots: domhandler: 5.0.3 entities: 4.5.0 + domain-browser@5.7.0: {} + domelementtype@2.3.0: {} domhandler@5.0.3: @@ -16190,6 +16441,8 @@ snapshots: is-hex-prefixed: 1.0.0 strip-hex-prefix: 1.0.0 + event-target-shim@5.0.1: {} + events-universal@1.0.1: dependencies: bare-events: 2.7.0 @@ -16601,6 +16854,11 @@ snapshots: dependencies: has-symbols: 1.1.0 + hash-base@3.0.5: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + hash-base@3.1.2: dependencies: inherits: 2.0.4 @@ -16702,6 +16960,8 @@ snapshots: transitivePeerDependencies: - supports-color + https-browserify@1.0.0: {} + https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -17012,13 +17272,13 @@ snapshots: jest-worker@26.6.2: dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 merge-stream: 2.0.0 supports-color: 7.2.0 jest-worker@27.5.1: dependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.8 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -17447,6 +17707,11 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + miller-rabin@4.0.1: + dependencies: + bn.js: 4.12.2 + brorand: 1.1.0 + mime-db@1.25.0: {} mime-db@1.52.0: {} @@ -17744,6 +18009,8 @@ snapshots: orderedmap@2.1.1: {} + os-browserify@0.3.0: {} + outdent@0.5.0: {} own-keys@1.0.1: @@ -17786,16 +18053,28 @@ snapshots: dependencies: quansync: 0.2.11 + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-asn1@5.1.9: + dependencies: + asn1.js: 4.10.1 + browserify-aes: 1.2.0 + evp_bytestokey: 1.0.3 + pbkdf2: 3.1.5 + safe-buffer: 5.2.1 + parse-passwd@1.0.0: {} parse5@7.3.0: dependencies: entities: 6.0.1 + path-browserify@1.0.1: {} + path-exists@3.0.0: {} path-exists@4.0.0: {} @@ -18119,7 +18398,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.18.8 + '@types/node': 24.10.8 long: 5.2.4 protobufjs@7.5.4: @@ -18134,11 +18413,20 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.18.8 + '@types/node': 24.10.8 long: 5.3.2 proxy-from-env@1.1.0: {} + public-encrypt@4.0.3: + dependencies: + bn.js: 4.12.2 + browserify-rsa: 4.1.1 + create-hash: 1.2.0 + parse-asn1: 5.1.9 + randombytes: 2.1.0 + safe-buffer: 5.2.1 + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -18166,6 +18454,8 @@ snapshots: quansync@0.2.11: {} + querystring-es3@0.2.1: {} + querystring@0.2.1: {} querystringify@2.2.0: {} @@ -18178,6 +18468,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + randomfill@1.0.4: + dependencies: + randombytes: 2.1.0 + safe-buffer: 5.2.1 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -18443,6 +18738,14 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -19105,6 +19408,18 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + + stream-http@3.2.0: + dependencies: + builtin-status-codes: 3.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + xtend: 4.0.2 + streamx@2.23.0: dependencies: events-universal: 1.0.1 @@ -19417,6 +19732,10 @@ snapshots: throttle-debounce@3.0.1: {} + timers-browserify@2.0.12: + dependencies: + setimmediate: 1.0.5 + tiny-case@1.0.3: {} tiny-invariant@1.2.0: {} @@ -19617,6 +19936,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tty-browserify@0.0.1: {} + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -20164,6 +20485,8 @@ snapshots: - supports-color - terser + vm-browserify@1.1.2: {} + w3c-keyname@2.2.8: {} w3c-xmlserializer@5.0.0: