From 3b4a2ba2d30875312a784727da95e1be010a4ca9 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 21 Mar 2026 23:12:09 +0800 Subject: [PATCH 01/10] feat: add allow insecure connections toggle in system settings Block ws:// relay connections and http:// resource loading by default to prevent browser mixed content warnings. When blocked, resources show clickable URL links instead of error placeholders so users can open them manually. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/AudioPlayer/index.tsx | 6 ++++-- src/components/Emoji/index.tsx | 4 +++- src/components/Image/index.tsx | 17 ++++++++++++++++- src/components/VideoPlayer/index.tsx | 4 +++- src/components/WebPreview/index.tsx | 6 ++++++ src/constants.ts | 1 + src/hooks/useFetchWebMetadata.tsx | 6 +++++- src/i18n/locales/ar.ts | 5 ++++- src/i18n/locales/de.ts | 5 ++++- src/i18n/locales/en.ts | 5 ++++- src/i18n/locales/es.ts | 5 ++++- src/i18n/locales/fa.ts | 5 ++++- src/i18n/locales/fr.ts | 5 ++++- src/i18n/locales/hi.ts | 5 ++++- src/i18n/locales/hu.ts | 5 ++++- src/i18n/locales/it.ts | 5 ++++- src/i18n/locales/ja.ts | 5 ++++- src/i18n/locales/ko.ts | 5 ++++- src/i18n/locales/pl.ts | 5 ++++- src/i18n/locales/pt-BR.ts | 5 ++++- src/i18n/locales/pt-PT.ts | 5 ++++- src/i18n/locales/ru.ts | 5 ++++- src/i18n/locales/th.ts | 5 ++++- src/i18n/locales/zh-TW.ts | 5 ++++- src/i18n/locales/zh.ts | 5 ++++- src/lib/smart-pool.ts | 5 +++++ src/lib/url.ts | 9 +++++++++ .../secondary/SystemSettingsPage/index.tsx | 19 +++++++++++++++++++ src/services/local-storage.service.ts | 13 +++++++++++++ 29 files changed, 156 insertions(+), 24 deletions(-) diff --git a/src/components/AudioPlayer/index.tsx b/src/components/AudioPlayer/index.tsx index 5233489..a6dba1f 100644 --- a/src/components/AudioPlayer/index.tsx +++ b/src/components/AudioPlayer/index.tsx @@ -1,6 +1,8 @@ import { Button } from '@/components/ui/button' import { Slider } from '@/components/ui/slider' +import { isInsecureUrl } from '@/lib/url' import { cn } from '@/lib/utils' +import storage from '@/services/local-storage.service' import mediaManager from '@/services/media-manager.service' import { Minimize2, Pause, Play, X } from 'lucide-react' import { useEffect, useRef, useState } from 'react' @@ -26,7 +28,7 @@ export default function AudioPlayer({ const [currentTime, setCurrentTime] = useState(0) const [duration, setDuration] = useState(0) const [error, setError] = useState(false) - const seekTimeoutRef = useRef() + const seekTimeoutRef = useRef>() const isSeeking = useRef(false) const containerRef = useRef(null) @@ -121,7 +123,7 @@ export default function AudioPlayer({ }, 300) } - if (error) { + if (error || (!storage.getAllowInsecureConnection() && isInsecureUrl(src))) { return } diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx index 2a2603d..3febf31 100644 --- a/src/components/Emoji/index.tsx +++ b/src/components/Emoji/index.tsx @@ -1,4 +1,6 @@ +import { isInsecureUrl } from '@/lib/url' import { cn } from '@/lib/utils' +import storage from '@/services/local-storage.service' import { TEmoji } from '@/types' import { Heart } from 'lucide-react' import { HTMLAttributes, useState } from 'react' @@ -23,7 +25,7 @@ export default function Emoji({ ) } - if (hasError) { + if (hasError || (!storage.getAllowInsecureConnection() && isInsecureUrl(emoji.url))) { return ( {`:${emoji.shortcode}:`} ) diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index 2fa599e..229a38e 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -1,11 +1,14 @@ import { Skeleton } from '@/components/ui/skeleton' +import { isInsecureUrl } from '@/lib/url' import { cn } from '@/lib/utils' import blossomService from '@/services/blossom.service' +import storage from '@/services/local-storage.service' import { TImetaInfo } from '@/types' import { decode } from 'blurhash' import { ImageOff } from 'lucide-react' import { HTMLAttributes, useEffect, useMemo, useRef, useState } from 'react' import { thumbHashToDataURL } from 'thumbhash' +import ExternalLink from '../ExternalLink' export default function Image({ image: { url, blurHash, thumbHash, pubkey, dim }, @@ -29,14 +32,22 @@ export default function Image({ const [isLoading, setIsLoading] = useState(true) const [displaySkeleton, setDisplaySkeleton] = useState(true) const [hasError, setHasError] = useState(false) + const [isBlocked, setIsBlocked] = useState(false) const [imageUrl, setImageUrl] = useState() - const timeoutRef = useRef(null) + const timeoutRef = useRef | null>(null) useEffect(() => { setIsLoading(true) setHasError(false) + setIsBlocked(false) setDisplaySkeleton(true) + if (!storage.getAllowInsecureConnection() && isInsecureUrl(url)) { + setIsBlocked(true) + setIsLoading(false) + return + } + if (pubkey) { blossomService.getValidUrl(url, pubkey).then((validUrl) => { setImageUrl(validUrl) @@ -53,6 +64,10 @@ export default function Image({ } }, [url]) + if (isBlocked) { + return + } + if (hideIfError && hasError) return null const handleError = async () => { diff --git a/src/components/VideoPlayer/index.tsx b/src/components/VideoPlayer/index.tsx index e5ecff9..7209466 100644 --- a/src/components/VideoPlayer/index.tsx +++ b/src/components/VideoPlayer/index.tsx @@ -1,6 +1,8 @@ +import { isInsecureUrl } from '@/lib/url' import { cn, isInViewport } from '@/lib/utils' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useUserPreferences } from '@/providers/UserPreferencesProvider' +import storage from '@/services/local-storage.service' import mediaManager from '@/services/media-manager.service' import { useEffect, useRef, useState } from 'react' import ExternalLink from '../ExternalLink' @@ -69,7 +71,7 @@ export default function VideoPlayer({ src, className }: { src: string; className } }, [muteMedia]) - if (error) { + if (error || (!storage.getAllowInsecureConnection() && isInsecureUrl(src))) { return } diff --git a/src/components/WebPreview/index.tsx b/src/components/WebPreview/index.tsx index 8441b75..8243ecf 100644 --- a/src/components/WebPreview/index.tsx +++ b/src/components/WebPreview/index.tsx @@ -1,7 +1,9 @@ import { useFetchWebMetadata } from '@/hooks/useFetchWebMetadata' +import { isInsecureUrl } from '@/lib/url' import { cn } from '@/lib/utils' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' +import storage from '@/services/local-storage.service' import { useMemo } from 'react' import Image from '../Image' import ExternalLink from '../ExternalLink' @@ -27,6 +29,10 @@ export default function WebPreview({ } }, [url]) + if (!storage.getAllowInsecureConnection() && isInsecureUrl(url)) { + return null + } + if (!autoLoadMedia && !mustLoad) { return null } diff --git a/src/constants.ts b/src/constants.ts index e75aa3a..288399f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -40,6 +40,7 @@ export const StorageKey = { ENABLE_SINGLE_COLUMN_LAYOUT: 'enableSingleColumnLayout', FAVICON_URL_TEMPLATE: 'faviconUrlTemplate', FILTER_OUT_ONION_RELAYS: 'filterOutOnionRelays', + ALLOW_INSECURE_CONNECTION: 'allowInsecureConnection', QUICK_REACTION: 'quickReaction', QUICK_REACTION_EMOJI: 'quickReactionEmoji', NSFW_DISPLAY_POLICY: 'nsfwDisplayPolicy', diff --git a/src/hooks/useFetchWebMetadata.tsx b/src/hooks/useFetchWebMetadata.tsx index 56a8455..affcffc 100644 --- a/src/hooks/useFetchWebMetadata.tsx +++ b/src/hooks/useFetchWebMetadata.tsx @@ -1,6 +1,8 @@ +import { isInsecureUrl } from '@/lib/url' +import storage from '@/services/local-storage.service' +import webService from '@/services/web.service' import { TWebMetadata } from '@/types' import { useEffect, useState } from 'react' -import webService from '@/services/web.service' export function useFetchWebMetadata(url: string) { const [metadata, setMetadata] = useState({}) @@ -10,6 +12,8 @@ export function useFetchWebMetadata(url: string) { } useEffect(() => { + if (!storage.getAllowInsecureConnection() && isInsecureUrl(url)) return + webService.fetchWebMetadata(url).then((metadata) => setMetadata(metadata)) }, [url]) diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index 6eeccba..440f77a 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -674,6 +674,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'الريلايات المستخدمة للبحث عن الملاحظات (NIP-50)', 'Protected event (NIP-70)': 'حدث محمي (NIP-70)', 'Protected': 'محمي', - 'Protected event hint': 'الأحداث المحمية (NIP-70) لا يمكن نشرها إلا من قبل المؤلف. سترفض الخوادم هذه الأحداث من أطراف ثالثة، مما يمنع الآخرين من إعادة بث محتواك. ملاحظة: لا تدعم جميع الخوادم الأحداث المحمية.' + 'Protected event hint': 'الأحداث المحمية (NIP-70) لا يمكن نشرها إلا من قبل المؤلف. سترفض الخوادم هذه الأحداث من أطراف ثالثة، مما يمنع الآخرين من إعادة بث محتواك. ملاحظة: لا تدعم جميع الخوادم الأحداث المحمية.', + 'Allow insecure connections': 'السماح بالاتصالات غير الآمنة', + 'Allow insecure connections description': + 'السماح بتحميل موارد http:// والاتصال بمرحلات ws://. قد يؤدي إلى تحذيرات المحتوى المختلط في المتصفح.' } } diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index ed217ee..c38730c 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -698,6 +698,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'Relays für die Notizsuche (NIP-50)', 'Protected event (NIP-70)': 'Geschütztes Ereignis (NIP-70)', 'Protected': 'Geschützt', - 'Protected event hint': 'Geschützte Ereignisse (NIP-70) können nur vom Autor veröffentlicht werden. Relays lehnen diese Ereignisse von Dritten ab und verhindern so, dass andere Ihre Inhalte weiterverbreiten. Hinweis: Nicht alle Relays unterstützen geschützte Ereignisse.' + 'Protected event hint': 'Geschützte Ereignisse (NIP-70) können nur vom Autor veröffentlicht werden. Relays lehnen diese Ereignisse von Dritten ab und verhindern so, dass andere Ihre Inhalte weiterverbreiten. Hinweis: Nicht alle Relays unterstützen geschützte Ereignisse.', + 'Allow insecure connections': 'Unsichere Verbindungen zulassen', + 'Allow insecure connections description': + 'Laden von http://-Ressourcen und Verbindung zu ws://-Relays erlauben. Kann Browser-Warnungen zu gemischten Inhalten auslösen.' } } diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 8207787..6a5bd9d 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -681,6 +681,9 @@ export default { 'Protected event (NIP-70)': 'Protected event (NIP-70)', 'Protected': 'Protected', 'Protected event hint': - 'Protected events (NIP-70) can only be published by the author. Relays will reject these events from third parties, preventing others from rebroadcasting your content. Note: not all relays support protected events.' + 'Protected events (NIP-70) can only be published by the author. Relays will reject these events from third parties, preventing others from rebroadcasting your content. Note: not all relays support protected events.', + 'Allow insecure connections': 'Allow insecure connections', + 'Allow insecure connections description': + 'Allow loading http:// resources and connecting to ws:// relays. May trigger browser mixed content warnings.' } } diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index dade224..b1469d8 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -691,6 +691,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'Relés utilizados para buscar notas (NIP-50)', 'Protected event (NIP-70)': 'Evento protegido (NIP-70)', 'Protected': 'Protegido', - 'Protected event hint': 'Los eventos protegidos (NIP-70) solo pueden ser publicados por el autor. Los relés rechazarán estos eventos de terceros, evitando que otros redistribuyan tu contenido. Nota: no todos los relés admiten eventos protegidos.' + 'Protected event hint': 'Los eventos protegidos (NIP-70) solo pueden ser publicados por el autor. Los relés rechazarán estos eventos de terceros, evitando que otros redistribuyan tu contenido. Nota: no todos los relés admiten eventos protegidos.', + 'Allow insecure connections': 'Permitir conexiones inseguras', + 'Allow insecure connections description': + 'Permitir cargar recursos http:// y conectar a relays ws://. Puede activar advertencias de contenido mixto del navegador.' } } diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index aecb1b8..b91746a 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -686,6 +686,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'رله‌هایی که برای جستجوی یادداشت‌ها استفاده می‌شوند (NIP-50)', 'Protected event (NIP-70)': 'رویداد محافظت‌شده (NIP-70)', 'Protected': 'محافظت‌شده', - 'Protected event hint': 'رویدادهای محافظت‌شده (NIP-70) فقط توسط نویسنده قابل انتشار هستند. رله‌ها این رویدادها را از اشخاص ثالث رد می‌کنند و از بازنشر محتوای شما توسط دیگران جلوگیری می‌کنند. توجه: همه رله‌ها از رویدادهای محافظت‌شده پشتیبانی نمی‌کنند.' + 'Protected event hint': 'رویدادهای محافظت‌شده (NIP-70) فقط توسط نویسنده قابل انتشار هستند. رله‌ها این رویدادها را از اشخاص ثالث رد می‌کنند و از بازنشر محتوای شما توسط دیگران جلوگیری می‌کنند. توجه: همه رله‌ها از رویدادهای محافظت‌شده پشتیبانی نمی‌کنند.', + 'Allow insecure connections': 'اجازه اتصالات ناامن', + 'Allow insecure connections description': + 'اجازه بارگذاری منابع http:// و اتصال به رله‌های ws://. ممکن است هشدارهای محتوای مختلط مرورگر را فعال کند.' } } diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index 7c4bd55..02d6068 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -695,6 +695,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'Relais utilisés pour rechercher des notes (NIP-50)', 'Protected event (NIP-70)': 'Événement protégé (NIP-70)', 'Protected': 'Protégé', - 'Protected event hint': 'Les événements protégés (NIP-70) ne peuvent être publiés que par l\'auteur. Les relais rejetteront ces événements provenant de tiers, empêchant les autres de rediffuser votre contenu. Remarque : tous les relais ne prennent pas en charge les événements protégés.' + 'Protected event hint': 'Les événements protégés (NIP-70) ne peuvent être publiés que par l\'auteur. Les relais rejetteront ces événements provenant de tiers, empêchant les autres de rediffuser votre contenu. Remarque : tous les relais ne prennent pas en charge les événements protégés.', + 'Allow insecure connections': 'Autoriser les connexions non sécurisées', + 'Allow insecure connections description': + 'Autoriser le chargement des ressources http:// et la connexion aux relais ws://. Peut déclencher des avertissements de contenu mixte du navigateur.' } } diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 8790470..e21cc5e 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -686,6 +686,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'नोट्स खोजने के लिए उपयोग किए जाने वाले रिले (NIP-50)', 'Protected event (NIP-70)': 'संरक्षित इवेंट (NIP-70)', 'Protected': 'संरक्षित', - 'Protected event hint': 'संरक्षित इवेंट (NIP-70) केवल लेखक द्वारा प्रकाशित किए जा सकते हैं। रिले तीसरे पक्ष से इन इवेंट को अस्वीकार कर देंगे, जिससे दूसरों को आपकी सामग्री को पुनः प्रसारित करने से रोका जा सके। नोट: सभी रिले संरक्षित इवेंट का समर्थन नहीं करते।' + 'Protected event hint': 'संरक्षित इवेंट (NIP-70) केवल लेखक द्वारा प्रकाशित किए जा सकते हैं। रिले तीसरे पक्ष से इन इवेंट को अस्वीकार कर देंगे, जिससे दूसरों को आपकी सामग्री को पुनः प्रसारित करने से रोका जा सके। नोट: सभी रिले संरक्षित इवेंट का समर्थन नहीं करते।', + 'Allow insecure connections': 'असुरक्षित कनेक्शन की अनुमति दें', + 'Allow insecure connections description': + 'http:// संसाधन लोड करने और ws:// रिले से कनेक्ट करने की अनुमति दें। ब्राउज़र मिश्रित सामग्री चेतावनियाँ ट्रिगर हो सकती हैं।' } } diff --git a/src/i18n/locales/hu.ts b/src/i18n/locales/hu.ts index 368fe07..90c3715 100644 --- a/src/i18n/locales/hu.ts +++ b/src/i18n/locales/hu.ts @@ -680,6 +680,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'Jegyzetek kereséséhez használt csomópontok (NIP-50)', 'Protected event (NIP-70)': 'Védett esemény (NIP-70)', 'Protected': 'Védett', - 'Protected event hint': 'A védett eseményeket (NIP-70) csak a szerző teheti közzé. A csomópontok elutasítják ezeket az eseményeket harmadik felektől, megakadályozva, hogy mások újraközvetítsék a tartalmadat. Megjegyzés: nem minden csomópont támogatja a védett eseményeket.' + 'Protected event hint': 'A védett eseményeket (NIP-70) csak a szerző teheti közzé. A csomópontok elutasítják ezeket az eseményeket harmadik felektől, megakadályozva, hogy mások újraközvetítsék a tartalmadat. Megjegyzés: nem minden csomópont támogatja a védett eseményeket.', + 'Allow insecure connections': 'Nem biztonságos kapcsolatok engedélyezése', + 'Allow insecure connections description': + 'http:// erőforrások betöltésének és ws:// relékhez való csatlakozás engedélyezése. Böngésző vegyes tartalom figyelmeztetéseket válthat ki.' } } diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index 862eee6..5076627 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -691,6 +691,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'Relay utilizzati per cercare le note (NIP-50)', 'Protected event (NIP-70)': 'Evento protetto (NIP-70)', 'Protected': 'Protetto', - 'Protected event hint': 'Gli eventi protetti (NIP-70) possono essere pubblicati solo dall\'autore. I relay rifiuteranno questi eventi da terze parti, impedendo ad altri di ridiffondere i tuoi contenuti. Nota: non tutti i relay supportano gli eventi protetti.' + 'Protected event hint': 'Gli eventi protetti (NIP-70) possono essere pubblicati solo dall\'autore. I relay rifiuteranno questi eventi da terze parti, impedendo ad altri di ridiffondere i tuoi contenuti. Nota: non tutti i relay supportano gli eventi protetti.', + 'Allow insecure connections': 'Consenti connessioni non sicure', + 'Allow insecure connections description': + 'Consenti il caricamento di risorse http:// e la connessione a relay ws://. Potrebbe attivare avvisi di contenuto misto del browser.' } } diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index a2b914e..8891e7e 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -686,6 +686,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'ノート検索に使用するリレー (NIP-50)', 'Protected event (NIP-70)': '保護されたイベント (NIP-70)', 'Protected': '保護', - 'Protected event hint': '保護されたイベント(NIP-70)は作成者のみが公開できます。リレーは第三者からのこれらのイベントを拒否し、他者によるコンテンツの再配信を防ぎます。 注意:すべてのリレーが保護されたイベントに対応しているわけではありません。' + 'Protected event hint': '保護されたイベント(NIP-70)は作成者のみが公開できます。リレーは第三者からのこれらのイベントを拒否し、他者によるコンテンツの再配信を防ぎます。 注意:すべてのリレーが保護されたイベントに対応しているわけではありません。', + 'Allow insecure connections': '安全でない接続を許可', + 'Allow insecure connections description': + 'http:// リソースの読み込みと ws:// リレーへの接続を許可します。ブラウザの混合コンテンツ警告が表示される場合があります。' } } diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index 042bbe8..7817702 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -680,6 +680,9 @@ export default { 'Relays used for searching notes (NIP-50)': '노트 검색에 사용되는 릴레이 (NIP-50)', 'Protected event (NIP-70)': '보호된 이벤트 (NIP-70)', 'Protected': '보호됨', - 'Protected event hint': '보호된 이벤트(NIP-70)는 작성자만 게시할 수 있습니다. 릴레이는 제3자의 이벤트를 거부하여 다른 사람이 콘텐츠를 재배포하는 것을 방지합니다. 참고: 모든 릴레이가 보호된 이벤트를 지원하는 것은 아닙니다.' + 'Protected event hint': '보호된 이벤트(NIP-70)는 작성자만 게시할 수 있습니다. 릴레이는 제3자의 이벤트를 거부하여 다른 사람이 콘텐츠를 재배포하는 것을 방지합니다. 참고: 모든 릴레이가 보호된 이벤트를 지원하는 것은 아닙니다.', + 'Allow insecure connections': '안전하지 않은 연결 허용', + 'Allow insecure connections description': + 'http:// 리소스 로드 및 ws:// 릴레이 연결을 허용합니다. 브라우저 혼합 콘텐츠 경고가 발생할 수 있습니다.' } } diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index 55907f0..69abc36 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -692,6 +692,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'Przekaźniki używane do wyszukiwania notatek (NIP-50)', 'Protected event (NIP-70)': 'Chronione zdarzenie (NIP-70)', 'Protected': 'Chronione', - 'Protected event hint': 'Chronione zdarzenia (NIP-70) mogą być publikowane tylko przez autora. Przekaźniki odrzucą te zdarzenia od osób trzecich, uniemożliwiając innym retransmisję Twoich treści. Uwaga: nie wszystkie przekaźniki obsługują chronione zdarzenia.' + 'Protected event hint': 'Chronione zdarzenia (NIP-70) mogą być publikowane tylko przez autora. Przekaźniki odrzucą te zdarzenia od osób trzecich, uniemożliwiając innym retransmisję Twoich treści. Uwaga: nie wszystkie przekaźniki obsługują chronione zdarzenia.', + 'Allow insecure connections': 'Zezwól na niezabezpieczone połączenia', + 'Allow insecure connections description': + 'Zezwól na ładowanie zasobów http:// i łączenie z przekaźnikami ws://. Może wywołać ostrzeżenia przeglądarki o mieszanej zawartości.' } } diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index 4eae112..d3ae8bc 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -689,6 +689,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'Relays usados para buscar notas (NIP-50)', 'Protected event (NIP-70)': 'Evento protegido (NIP-70)', 'Protected': 'Protegido', - 'Protected event hint': 'Eventos protegidos (NIP-70) só podem ser publicados pelo autor. Os relays rejeitarão esses eventos de terceiros, impedindo que outros retransmitam seu conteúdo. Nota: nem todos os relays suportam eventos protegidos.' + 'Protected event hint': 'Eventos protegidos (NIP-70) só podem ser publicados pelo autor. Os relays rejeitarão esses eventos de terceiros, impedindo que outros retransmitam seu conteúdo. Nota: nem todos os relays suportam eventos protegidos.', + 'Allow insecure connections': 'Permitir conexões inseguras', + 'Allow insecure connections description': + 'Permitir carregar recursos http:// e conectar a relays ws://. Pode acionar avisos de conteúdo misto do navegador.' } } diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index 49c0db6..ad68de8 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -692,6 +692,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'Relés usados para pesquisar notas (NIP-50)', 'Protected event (NIP-70)': 'Evento protegido (NIP-70)', 'Protected': 'Protegido', - 'Protected event hint': 'Eventos protegidos (NIP-70) só podem ser publicados pelo autor. Os relés rejeitarão estes eventos de terceiros, impedindo que outros retransmitam o seu conteúdo. Nota: nem todos os relés suportam eventos protegidos.' + 'Protected event hint': 'Eventos protegidos (NIP-70) só podem ser publicados pelo autor. Os relés rejeitarão estes eventos de terceiros, impedindo que outros retransmitam o seu conteúdo. Nota: nem todos os relés suportam eventos protegidos.', + 'Allow insecure connections': 'Permitir ligações inseguras', + 'Allow insecure connections description': + 'Permitir carregar recursos http:// e ligar a relays ws://. Pode acionar avisos de conteúdo misto do navegador.' } } diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 9d9a786..1db8a51 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -691,6 +691,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'Ретрансляторы для поиска заметок (NIP-50)', 'Protected event (NIP-70)': 'Защищённое событие (NIP-70)', 'Protected': 'Защищённый', - 'Protected event hint': 'Защищённые события (NIP-70) могут быть опубликованы только автором. Ретрансляторы отклонят эти события от третьих лиц, предотвращая повторную трансляцию вашего контента. Примечание: не все ретрансляторы поддерживают защищённые события.' + 'Protected event hint': 'Защищённые события (NIP-70) могут быть опубликованы только автором. Ретрансляторы отклонят эти события от третьих лиц, предотвращая повторную трансляцию вашего контента. Примечание: не все ретрансляторы поддерживают защищённые события.', + 'Allow insecure connections': 'Разрешить небезопасные соединения', + 'Allow insecure connections description': + 'Разрешить загрузку ресурсов http:// и подключение к реле ws://. Может вызвать предупреждения браузера о смешанном содержимом.' } } diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index f238e8d..b11bf56 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -676,6 +676,9 @@ export default { 'Relays used for searching notes (NIP-50)': 'รีเลย์ที่ใช้สำหรับค้นหาโน้ต (NIP-50)', 'Protected event (NIP-70)': 'เหตุการณ์ที่ได้รับการป้องกัน (NIP-70)', 'Protected': 'ป้องกัน', - 'Protected event hint': 'เหตุการณ์ที่ได้รับการป้องกัน (NIP-70) สามารถเผยแพร่ได้โดยผู้เขียนเท่านั้น รีเลย์จะปฏิเสธเหตุการณ์เหล่านี้จากบุคคลที่สาม ป้องกันไม่ให้ผู้อื่นเผยแพร่เนื้อหาของคุณซ้ำ หมายเหตุ: รีเลย์บางแห่งไม่รองรับเหตุการณ์ที่ได้รับการป้องกัน' + 'Protected event hint': 'เหตุการณ์ที่ได้รับการป้องกัน (NIP-70) สามารถเผยแพร่ได้โดยผู้เขียนเท่านั้น รีเลย์จะปฏิเสธเหตุการณ์เหล่านี้จากบุคคลที่สาม ป้องกันไม่ให้ผู้อื่นเผยแพร่เนื้อหาของคุณซ้ำ หมายเหตุ: รีเลย์บางแห่งไม่รองรับเหตุการณ์ที่ได้รับการป้องกัน', + 'Allow insecure connections': 'อนุญาตการเชื่อมต่อที่ไม่ปลอดภัย', + 'Allow insecure connections description': + 'อนุญาตให้โหลดทรัพยากร http:// และเชื่อมต่อกับรีเลย์ ws:// อาจทำให้เบราว์เซอร์แสดงคำเตือนเนื้อหาแบบผสม' } } diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index f48ce3f..54b565f 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -658,6 +658,9 @@ export default { 'Relays used for searching notes (NIP-50)': '用於搜尋筆記的伺服器 (NIP-50)', 'Protected event (NIP-70)': '受保護的事件 (NIP-70)', 'Protected': '受保護', - 'Protected event hint': '受保護的事件(NIP-70)只能由作者發布。伺服器將拒絕來自第三方的這些事件,防止他人轉播你的內容。 注意:並非所有伺服器都支持受保護的事件。' + 'Protected event hint': '受保護的事件(NIP-70)只能由作者發布。伺服器將拒絕來自第三方的這些事件,防止他人轉播你的內容。 注意:並非所有伺服器都支持受保護的事件。', + 'Allow insecure connections': '允許不安全的連線', + 'Allow insecure connections description': + '允許載入 http:// 資源和連線 ws:// relay。可能會觸發瀏覽器混合內容警告。' } } diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 6fa7fc6..f491c29 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -663,6 +663,9 @@ export default { 'Relays used for searching notes (NIP-50)': '用于搜索笔记的服务器 (NIP-50)', 'Protected event (NIP-70)': '受保护的事件 (NIP-70)', 'Protected': '受保护', - 'Protected event hint': '受保护的事件(NIP-70)只能由作者发布。服务器将拒绝来自第三方的这些事件,防止他人转播你的内容。 注意:并非所有服务器都支持受保护的事件。' + 'Protected event hint': '受保护的事件(NIP-70)只能由作者发布。服务器将拒绝来自第三方的这些事件,防止他人转播你的内容。 注意:并非所有服务器都支持受保护的事件。', + 'Allow insecure connections': '允许不安全的连接', + 'Allow insecure connections description': + '允许加载 http:// 资源和连接 ws:// relay。可能会触发浏览器混合内容警告。' } } diff --git a/src/lib/smart-pool.ts b/src/lib/smart-pool.ts index 2ff421b..1a1a25a 100644 --- a/src/lib/smart-pool.ts +++ b/src/lib/smart-pool.ts @@ -1,5 +1,7 @@ +import storage from '@/services/local-storage.service' import { SimplePool } from 'nostr-tools' import { AbstractRelay } from 'nostr-tools/abstract-relay' +import { isInsecureUrl } from './url' const DEFAULT_CONNECTION_TIMEOUT = 10 * 1000 // 10 seconds const CLEANUP_THRESHOLD = 15 // number of relays to trigger cleanup @@ -17,6 +19,9 @@ export class SmartPool extends SimplePool { } ensureRelay(url: string): Promise { + if (!storage.getAllowInsecureConnection() && isInsecureUrl(url)) { + return Promise.reject(new Error(`Insecure relay connection blocked: ${url}`)) + } // If relay is new and we have many relays, trigger cleanup if (!this.relayIdleTracker.has(url) && this.relayIdleTracker.size > CLEANUP_THRESHOLD) { this.cleanIdleRelays() diff --git a/src/lib/url.ts b/src/lib/url.ts index d8ea4e8..7fd119e 100644 --- a/src/lib/url.ts +++ b/src/lib/url.ts @@ -7,6 +7,15 @@ export function isWebsocketUrl(url: string): boolean { } } +export function isInsecureUrl(url: string): boolean { + try { + const protocol = new URL(url).protocol + return protocol === 'ws:' || protocol === 'http:' + } catch { + return false + } +} + export function isOnionUrl(url: string): boolean { try { const hostname = new URL(url).hostname diff --git a/src/pages/secondary/SystemSettingsPage/index.tsx b/src/pages/secondary/SystemSettingsPage/index.tsx index fcd7478..adca4ac 100644 --- a/src/pages/secondary/SystemSettingsPage/index.tsx +++ b/src/pages/secondary/SystemSettingsPage/index.tsx @@ -16,6 +16,9 @@ const SystemSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { const [filterOutOnionRelays, setFilterOutOnionRelays] = useState( storage.getFilterOutOnionRelays() ) + const [allowInsecureConnection, setAllowInsecureConnection] = useState( + storage.getAllowInsecureConnection() + ) return ( @@ -45,6 +48,22 @@ const SystemSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { }} /> +
+ + { + storage.setAllowInsecureConnection(checked) + setAllowInsecureConnection(checked) + }} + /> +
diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index bd9c788..1188841 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -63,6 +63,7 @@ class LocalStorageService { private enableSingleColumnLayout: boolean = true private faviconUrlTemplate: string = DEFAULT_FAVICON_URL_TEMPLATE private filterOutOnionRelays: boolean = !isTorBrowser() + private allowInsecureConnection: boolean = false private quickReaction: boolean = false private quickReactionEmoji: string | TEmoji = '+' private nsfwDisplayPolicy: TNsfwDisplayPolicy = NSFW_DISPLAY_POLICY.HIDE_CONTENT @@ -253,6 +254,9 @@ class LocalStorageService { this.filterOutOnionRelays = filterOutOnionRelaysStr !== 'false' } + this.allowInsecureConnection = + window.localStorage.getItem(StorageKey.ALLOW_INSECURE_CONNECTION) === 'true' + this.quickReaction = window.localStorage.getItem(StorageKey.QUICK_REACTION) === 'true' const quickReactionEmojiStr = window.localStorage.getItem(StorageKey.QUICK_REACTION_EMOJI) ?? '+' @@ -650,6 +654,15 @@ class LocalStorageService { window.localStorage.setItem(StorageKey.FILTER_OUT_ONION_RELAYS, filterOut.toString()) } + getAllowInsecureConnection() { + return this.allowInsecureConnection + } + + setAllowInsecureConnection(allow: boolean) { + this.allowInsecureConnection = allow + window.localStorage.setItem(StorageKey.ALLOW_INSECURE_CONNECTION, allow.toString()) + } + getQuickReaction() { return this.quickReaction } From c7eef9fa482894352835f529eb2854e95cafa745 Mon Sep 17 00:00:00 2001 From: Max Blake <157151994+maxblake2015@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:25:27 +0000 Subject: [PATCH 02/10] feat: update pl.ts (#763) A small stylistic amendment --- src/i18n/locales/pl.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index 69abc36..7a95064 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -163,7 +163,7 @@ export default { 'Send only to r': 'Wyślij tylko do {{r}}', 'Send only to these relays': 'Wyślij tylko do tych transmiterów', Explore: 'Transmitery', - 'Search relays': 'Przekaźniki wyszukiwania', + 'Search relays': 'Transmitery wyszukiwania', relayInfoBadgeAuth: '✔️', relayInfoBadgeSearch: 'Wyszukiwarka', relayInfoBadgePayment: 'Płatności', @@ -671,30 +671,30 @@ export default { 'Auto-load profile pictures': 'Automatyczne ładowanie zdjęć profilowych', 'Disable live feed': 'Wyłącz kanał na żywo', 'Enable live feed': 'Włącz kanał na żywo', - 'Default relays': 'Domyślne przekaźniki', + 'Default relays': 'Domyślne transmitery', 'Reset to default': 'Przywróć domyślne', 'Default relays description': - 'Używane do odpytywania konfiguracji przekaźników innych użytkowników i jako rozwiązanie awaryjne, gdy użytkownicy nie mają skonfigurowanych przekaźników.', + 'Używane do odpytywania konfiguracji transmiterów innych użytkowników i jako rozwiązanie awaryjne, gdy użytkownicy nie mają skonfigurowanych transmiterów.', 'Default relays warning': - 'Ostrzeżenie: Nie modyfikuj tych ustawień pochopnie, może to wpłynąć na podstawowe doświadczenie.', - 'Invalid relay URL': 'Nieprawidłowy adres URL przekaźnika', + 'Ostrzeżenie: Nie modyfikuj tych ustawień pochopnie, może to wpłynąć na komfort użytkowania.', + 'Invalid relay URL': 'Nieprawidłowy adres URL transmitera', 'Muted words': 'Wyciszone słowa', 'Add muted word': 'Dodaj wyciszone słowo', 'Zap Details': 'Szczegóły zapu', 'Default trust score filter threshold ({{n}}%)': 'Domyślny próg filtra wyniku zaufania ({{n}}%)', - 'No notes found': 'Nie znaleziono notatek', + 'No notes found': 'Nie znaleziono wpisów', 'Try again later or check your connection': 'Spróbuj ponownie później lub sprawdź połączenie', 'Hide indirect': 'Ukryj pośrednie', - 'Copy note content': 'Kopiuj treść notatki', + 'Copy note content': 'Kopiuj treść wpisu', 'Video loop': 'Zapętlanie wideo', 'Automatically replay videos when they end': 'Automatycznie powtarzaj filmy po zakończeniu', - 'Relays used for searching notes (NIP-50)': 'Przekaźniki używane do wyszukiwania notatek (NIP-50)', + 'Relays used for searching notes (NIP-50)': 'Transmitery używane do wyszukiwania wpisów (NIP-50)', 'Protected event (NIP-70)': 'Chronione zdarzenie (NIP-70)', 'Protected': 'Chronione', - 'Protected event hint': 'Chronione zdarzenia (NIP-70) mogą być publikowane tylko przez autora. Przekaźniki odrzucą te zdarzenia od osób trzecich, uniemożliwiając innym retransmisję Twoich treści. Uwaga: nie wszystkie przekaźniki obsługują chronione zdarzenia.', + 'Protected event hint': 'Chronione zdarzenia (NIP-70) mogą być publikowane tylko przez autora. Transmitery odrzucą publikację tych zdarzeń przez osoby trzecie, uniemożliwiając innym retransmisję Twoich treści. Uwaga: nie wszystkie transmitery obsługują ochronę zdarzeń.', 'Allow insecure connections': 'Zezwól na niezabezpieczone połączenia', 'Allow insecure connections description': - 'Zezwól na ładowanie zasobów http:// i łączenie z przekaźnikami ws://. Może wywołać ostrzeżenia przeglądarki o mieszanej zawartości.' + 'Zezwól na ładowanie zasobów http:// i łączenie z transmiterami ws://. Może to wywołać ostrzeżenia przeglądarki o mieszanej zawartości.' } } From e80474099edfa5c2e26668f246c86ac05a04e9f7 Mon Sep 17 00:00:00 2001 From: codytseng Date: Wed, 25 Mar 2026 11:02:00 +0800 Subject: [PATCH 03/10] fix: improve markdown detection and fix line spacing in embedded notes Count total occurrences of medium signals in containsMarkdown instead of only distinct types, so repeated patterns like multiple list items correctly trigger markdown rendering. Add whitespace-normal to MarkdownContent to prevent inherited whitespace-pre-wrap from parent note content causing inconsistent line spacing in embedded notes. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/MarkdownContent/index.tsx | 2 +- src/lib/markdown.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/MarkdownContent/index.tsx b/src/components/MarkdownContent/index.tsx index ebbe57e..01a5a98 100644 --- a/src/components/MarkdownContent/index.tsx +++ b/src/components/MarkdownContent/index.tsx @@ -122,7 +122,7 @@ export default function MarkdownContent({ ) return ( -
+
{ diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts index c8f7036..841a945 100644 --- a/src/lib/markdown.ts +++ b/src/lib/markdown.ts @@ -34,10 +34,10 @@ export function containsMarkdown(content: string): boolean { let matchCount = 0 for (const pattern of mediumPatterns) { - if (pattern.test(cleaned)) { - matchCount++ - if (matchCount >= 2) return true - } + const globalPattern = new RegExp(pattern.source, pattern.flags.includes('m') ? 'gm' : 'g') + const occurrences = (cleaned.match(globalPattern) || []).length + matchCount += occurrences + if (matchCount >= 2) return true } return false From e4a61740c50cb5c12b178d095de162fd1f0cadca Mon Sep 17 00:00:00 2001 From: codytseng Date: Fri, 27 Mar 2026 10:15:42 +0800 Subject: [PATCH 04/10] =?UTF-8?q?fix:=20=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UserAggregationList/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/UserAggregationList/index.tsx b/src/components/UserAggregationList/index.tsx index 45b7527..3b98312 100644 --- a/src/components/UserAggregationList/index.tsx +++ b/src/components/UserAggregationList/index.tsx @@ -306,7 +306,8 @@ const UserAggregationList = forwardRef< hideContentMentioningMutedUsers, isMentioningMutedUsers, meetsMinTrustScore, - trustScoreThreshold + trustScoreThreshold, + since ] ) From 5596e5eb7baced4f0f6907cbd598415056dc0aab Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 28 Mar 2026 15:18:50 +0800 Subject: [PATCH 05/10] fix: HTTP avatar image rendering issue --- src/components/AudioPlayer/index.tsx | 5 +++-- src/components/Emoji/index.tsx | 5 +++-- src/components/Image/index.tsx | 16 +++++----------- src/components/UserAvatar/index.tsx | 2 +- src/components/VideoPlayer/index.tsx | 5 ++--- src/components/WebPreview/index.tsx | 7 ++++--- src/hooks/useFetchWebMetadata.tsx | 7 ++++--- src/pages/secondary/SystemSettingsPage/index.tsx | 10 +++------- src/providers/UserPreferencesProvider.tsx | 16 +++++++++++++++- 9 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/components/AudioPlayer/index.tsx b/src/components/AudioPlayer/index.tsx index a6dba1f..2d6c6e0 100644 --- a/src/components/AudioPlayer/index.tsx +++ b/src/components/AudioPlayer/index.tsx @@ -2,7 +2,7 @@ import { Button } from '@/components/ui/button' import { Slider } from '@/components/ui/slider' import { isInsecureUrl } from '@/lib/url' import { cn } from '@/lib/utils' -import storage from '@/services/local-storage.service' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' import mediaManager from '@/services/media-manager.service' import { Minimize2, Pause, Play, X } from 'lucide-react' import { useEffect, useRef, useState } from 'react' @@ -24,6 +24,7 @@ export default function AudioPlayer({ className }: AudioPlayerProps) { const audioRef = useRef(null) + const { allowInsecureConnection } = useUserPreferences() const [isPlaying, setIsPlaying] = useState(false) const [currentTime, setCurrentTime] = useState(0) const [duration, setDuration] = useState(0) @@ -123,7 +124,7 @@ export default function AudioPlayer({ }, 300) } - if (error || (!storage.getAllowInsecureConnection() && isInsecureUrl(src))) { + if (error || (!allowInsecureConnection && isInsecureUrl(src))) { return } diff --git a/src/components/Emoji/index.tsx b/src/components/Emoji/index.tsx index 3febf31..e55e257 100644 --- a/src/components/Emoji/index.tsx +++ b/src/components/Emoji/index.tsx @@ -1,6 +1,6 @@ import { isInsecureUrl } from '@/lib/url' import { cn } from '@/lib/utils' -import storage from '@/services/local-storage.service' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' import { TEmoji } from '@/types' import { Heart } from 'lucide-react' import { HTMLAttributes, useState } from 'react' @@ -15,6 +15,7 @@ export default function Emoji({ img?: string } }) { + const { allowInsecureConnection } = useUserPreferences() const [hasError, setHasError] = useState(false) if (typeof emoji === 'string') { @@ -25,7 +26,7 @@ export default function Emoji({ ) } - if (hasError || (!storage.getAllowInsecureConnection() && isInsecureUrl(emoji.url))) { + if (hasError || (!allowInsecureConnection && isInsecureUrl(emoji.url))) { return ( {`:${emoji.shortcode}:`} ) diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index 229a38e..eeb9578 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -1,14 +1,13 @@ import { Skeleton } from '@/components/ui/skeleton' import { isInsecureUrl } from '@/lib/url' import { cn } from '@/lib/utils' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' import blossomService from '@/services/blossom.service' -import storage from '@/services/local-storage.service' import { TImetaInfo } from '@/types' import { decode } from 'blurhash' import { ImageOff } from 'lucide-react' import { HTMLAttributes, useEffect, useMemo, useRef, useState } from 'react' import { thumbHashToDataURL } from 'thumbhash' -import ExternalLink from '../ExternalLink' export default function Image({ image: { url, blurHash, thumbHash, pubkey, dim }, @@ -29,21 +28,20 @@ export default function Image({ hideIfError?: boolean errorPlaceholder?: React.ReactNode }) { + const { allowInsecureConnection } = useUserPreferences() const [isLoading, setIsLoading] = useState(true) const [displaySkeleton, setDisplaySkeleton] = useState(true) const [hasError, setHasError] = useState(false) - const [isBlocked, setIsBlocked] = useState(false) const [imageUrl, setImageUrl] = useState() const timeoutRef = useRef | null>(null) useEffect(() => { setIsLoading(true) setHasError(false) - setIsBlocked(false) setDisplaySkeleton(true) - if (!storage.getAllowInsecureConnection() && isInsecureUrl(url)) { - setIsBlocked(true) + if (!allowInsecureConnection && isInsecureUrl(url)) { + setHasError(true) setIsLoading(false) return } @@ -62,11 +60,7 @@ export default function Image({ } else { setImageUrl(url) } - }, [url]) - - if (isBlocked) { - return - } + }, [url, allowInsecureConnection]) if (hideIfError && hasError) return null diff --git a/src/components/UserAvatar/index.tsx b/src/components/UserAvatar/index.tsx index 3978aad..1b2c297 100644 --- a/src/components/UserAvatar/index.tsx +++ b/src/components/UserAvatar/index.tsx @@ -1,11 +1,11 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card' import { Skeleton } from '@/components/ui/skeleton' -import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useFetchProfile } from '@/hooks' import { toProfile } from '@/lib/link' import { generateImageByPubkey } from '@/lib/pubkey' import { cn, isTouchDevice } from '@/lib/utils' import { SecondaryPageLink } from '@/PageManager' +import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useMemo } from 'react' import Image from '../Image' import ProfileCard from '../ProfileCard' diff --git a/src/components/VideoPlayer/index.tsx b/src/components/VideoPlayer/index.tsx index 7209466..ba757c8 100644 --- a/src/components/VideoPlayer/index.tsx +++ b/src/components/VideoPlayer/index.tsx @@ -2,14 +2,13 @@ import { isInsecureUrl } from '@/lib/url' import { cn, isInViewport } from '@/lib/utils' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useUserPreferences } from '@/providers/UserPreferencesProvider' -import storage from '@/services/local-storage.service' import mediaManager from '@/services/media-manager.service' import { useEffect, useRef, useState } from 'react' import ExternalLink from '../ExternalLink' export default function VideoPlayer({ src, className }: { src: string; className?: string }) { const { autoplay, videoLoop } = useContentPolicy() - const { muteMedia, updateMuteMedia } = useUserPreferences() + const { muteMedia, updateMuteMedia, allowInsecureConnection } = useUserPreferences() const [error, setError] = useState(false) const videoRef = useRef(null) const containerRef = useRef(null) @@ -71,7 +70,7 @@ export default function VideoPlayer({ src, className }: { src: string; className } }, [muteMedia]) - if (error || (!storage.getAllowInsecureConnection() && isInsecureUrl(src))) { + if (error || (!allowInsecureConnection && isInsecureUrl(src))) { return } diff --git a/src/components/WebPreview/index.tsx b/src/components/WebPreview/index.tsx index 8243ecf..944b000 100644 --- a/src/components/WebPreview/index.tsx +++ b/src/components/WebPreview/index.tsx @@ -3,10 +3,10 @@ import { isInsecureUrl } from '@/lib/url' import { cn } from '@/lib/utils' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' -import storage from '@/services/local-storage.service' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' import { useMemo } from 'react' -import Image from '../Image' import ExternalLink from '../ExternalLink' +import Image from '../Image' export default function WebPreview({ url, @@ -18,6 +18,7 @@ export default function WebPreview({ mustLoad?: boolean }) { const { autoLoadMedia } = useContentPolicy() + const { allowInsecureConnection } = useUserPreferences() const { isSmallScreen } = useScreenSize() const { title, description, image } = useFetchWebMetadata(url) @@ -29,7 +30,7 @@ export default function WebPreview({ } }, [url]) - if (!storage.getAllowInsecureConnection() && isInsecureUrl(url)) { + if (!allowInsecureConnection && isInsecureUrl(url)) { return null } diff --git a/src/hooks/useFetchWebMetadata.tsx b/src/hooks/useFetchWebMetadata.tsx index affcffc..62bf8b3 100644 --- a/src/hooks/useFetchWebMetadata.tsx +++ b/src/hooks/useFetchWebMetadata.tsx @@ -1,10 +1,11 @@ import { isInsecureUrl } from '@/lib/url' -import storage from '@/services/local-storage.service' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' import webService from '@/services/web.service' import { TWebMetadata } from '@/types' import { useEffect, useState } from 'react' export function useFetchWebMetadata(url: string) { + const { allowInsecureConnection } = useUserPreferences() const [metadata, setMetadata] = useState({}) const proxyServer = import.meta.env.VITE_PROXY_SERVER if (proxyServer) { @@ -12,10 +13,10 @@ export function useFetchWebMetadata(url: string) { } useEffect(() => { - if (!storage.getAllowInsecureConnection() && isInsecureUrl(url)) return + if (!allowInsecureConnection && isInsecureUrl(url)) return webService.fetchWebMetadata(url).then((metadata) => setMetadata(metadata)) - }, [url]) + }, [url, allowInsecureConnection]) return metadata } diff --git a/src/pages/secondary/SystemSettingsPage/index.tsx b/src/pages/secondary/SystemSettingsPage/index.tsx index adca4ac..554e802 100644 --- a/src/pages/secondary/SystemSettingsPage/index.tsx +++ b/src/pages/secondary/SystemSettingsPage/index.tsx @@ -6,6 +6,7 @@ import { Switch } from '@/components/ui/switch' import { DEFAULT_FAVICON_URL_TEMPLATE } from '@/constants' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { useContentPolicy } from '@/providers/ContentPolicyProvider' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' import storage from '@/services/local-storage.service' import { forwardRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -13,12 +14,10 @@ import { useTranslation } from 'react-i18next' const SystemSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { const { t } = useTranslation() const { faviconUrlTemplate, setFaviconUrlTemplate } = useContentPolicy() + const { allowInsecureConnection, updateAllowInsecureConnection } = useUserPreferences() const [filterOutOnionRelays, setFilterOutOnionRelays] = useState( storage.getFilterOutOnionRelays() ) - const [allowInsecureConnection, setAllowInsecureConnection] = useState( - storage.getAllowInsecureConnection() - ) return ( @@ -58,10 +57,7 @@ const SystemSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { { - storage.setAllowInsecureConnection(checked) - setAllowInsecureConnection(checked) - }} + onCheckedChange={updateAllowInsecureConnection} />
diff --git a/src/providers/UserPreferencesProvider.tsx b/src/providers/UserPreferencesProvider.tsx index 9302aef..f3a4e2a 100644 --- a/src/providers/UserPreferencesProvider.tsx +++ b/src/providers/UserPreferencesProvider.tsx @@ -21,6 +21,9 @@ type TUserPreferencesContext = { quickReactionEmoji: string | TEmoji updateQuickReactionEmoji: (emoji: string | TEmoji) => void + + allowInsecureConnection: boolean + updateAllowInsecureConnection: (allow: boolean) => void } const UserPreferencesContext = createContext(undefined) @@ -46,6 +49,10 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod const [quickReaction, setQuickReaction] = useState(storage.getQuickReaction()) const [quickReactionEmoji, setQuickReactionEmoji] = useState(storage.getQuickReactionEmoji()) + const [allowInsecureConnection, setAllowInsecureConnection] = useState( + storage.getAllowInsecureConnection() + ) + useEffect(() => { if (!isSmallScreen && enableSingleColumnLayout) { document.documentElement.style.setProperty('overflow-y', 'scroll') @@ -79,6 +86,11 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod storage.setQuickReactionEmoji(emoji) } + const updateAllowInsecureConnection = (allow: boolean) => { + setAllowInsecureConnection(allow) + storage.setAllowInsecureConnection(allow) + } + return ( {children} From 234010c385775c5b2726e9a8c9950e5576657803 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 4 Apr 2026 14:56:04 +0800 Subject: [PATCH 06/10] feat: add support for displaying kind 7 and kind 17 reaction events Reactions now render with a large emoji (matching emoji-only note sizing) and a "reacted to" preview pill linking to the target event, following the same pattern as comment parent previews. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ContentPreview/ReactionPreview.tsx | 50 +++++++++++++++++++ src/components/ContentPreview/index.tsx | 5 ++ src/components/Note/Reaction.tsx | 33 ++++++++++++ src/components/Note/index.tsx | 26 ++++++++++ src/components/ParentNotePreview/index.tsx | 11 ++-- src/components/ReactionList/index.tsx | 6 ++- src/constants.ts | 4 +- src/i18n/locales/ar.ts | 4 +- src/i18n/locales/de.ts | 4 +- src/i18n/locales/en.ts | 4 +- src/i18n/locales/es.ts | 4 +- src/i18n/locales/fa.ts | 4 +- src/i18n/locales/fr.ts | 4 +- src/i18n/locales/hi.ts | 4 +- src/i18n/locales/hu.ts | 4 +- src/i18n/locales/it.ts | 4 +- src/i18n/locales/ja.ts | 4 +- src/i18n/locales/ko.ts | 4 +- src/i18n/locales/pl.ts | 4 +- src/i18n/locales/pt-BR.ts | 4 +- src/i18n/locales/pt-PT.ts | 4 +- src/i18n/locales/ru.ts | 4 +- src/i18n/locales/th.ts | 4 +- src/i18n/locales/zh-TW.ts | 4 +- src/i18n/locales/zh.ts | 4 +- src/services/stuff-stats.service.ts | 31 ++++++++++-- 26 files changed, 209 insertions(+), 29 deletions(-) create mode 100644 src/components/ContentPreview/ReactionPreview.tsx create mode 100644 src/components/Note/Reaction.tsx diff --git a/src/components/ContentPreview/ReactionPreview.tsx b/src/components/ContentPreview/ReactionPreview.tsx new file mode 100644 index 0000000..7f7479a --- /dev/null +++ b/src/components/ContentPreview/ReactionPreview.tsx @@ -0,0 +1,50 @@ +import Image from '@/components/Image' +import { cn } from '@/lib/utils' +import { Heart } from 'lucide-react' +import { Event } from 'nostr-tools' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' + +export default function ReactionPreview({ + event, + className +}: { + event: Event + className?: string +}) { + const { t } = useTranslation() + + const reaction = useMemo(() => { + if (!event.content || event.content === '+') { + return + } + + const emojiName = /^:([^:]+):$/.exec(event.content)?.[1] + if (emojiName) { + const emojiTag = event.tags.find((tag) => tag[0] === 'emoji' && tag[1] === emojiName) + const emojiUrl = emojiTag?.[2] + if (emojiUrl) { + return ( + {emojiName}} + /> + ) + } + } + if (event.content.length > 4) { + return + } + return {event.content} + }, [event]) + + return ( +
+ [{t('Reaction')}] + {reaction} +
+ ) +} diff --git a/src/components/ContentPreview/index.tsx b/src/components/ContentPreview/index.tsx index 5cf0759..576bdca 100644 --- a/src/components/ContentPreview/index.tsx +++ b/src/components/ContentPreview/index.tsx @@ -16,6 +16,7 @@ import LongFormArticlePreview from './LongFormArticlePreview' import NormalContentPreview from './NormalContentPreview' import PictureNotePreview from './PictureNotePreview' import PollPreview from './PollPreview' +import ReactionPreview from './ReactionPreview' import VideoNotePreview from './VideoNotePreview' export default function ContentPreview({ @@ -110,6 +111,10 @@ export default function ContentPreview({ return } + if (event.kind === kinds.Reaction || event.kind === ExtendedKind.EXTERNAL_CONTENT_REACTION) { + return + } + return (
[ diff --git a/src/components/Note/Reaction.tsx b/src/components/Note/Reaction.tsx new file mode 100644 index 0000000..003c731 --- /dev/null +++ b/src/components/Note/Reaction.tsx @@ -0,0 +1,33 @@ +import Emoji from '@/components/Emoji' +import { getEmojiInfosFromEmojiTags } from '@/lib/tag' +import { Event } from 'nostr-tools' +import { useMemo } from 'react' + +export default function Reaction({ + event, + className +}: { + event: Event + className?: string +}) { + const emoji = useMemo(() => { + const content = event.content + if (!content || content === '+') return '+' + + const emojiName = /^:([^:]+):$/.exec(content)?.[1] + if (emojiName) { + const emojiInfos = getEmojiInfosFromEmojiTags(event.tags) + const emojiInfo = emojiInfos.find((e) => e.shortcode === emojiName) + if (emojiInfo) return emojiInfo + } + + if (content.length <= 4) return content + return '+' + }, [event]) + + return ( +
+ +
+ ) +} diff --git a/src/components/Note/index.tsx b/src/components/Note/index.tsx index 5dd1ced..0d53b7a 100644 --- a/src/components/Note/index.tsx +++ b/src/components/Note/index.tsx @@ -2,11 +2,13 @@ import { useSecondaryPage } from '@/PageManager' import { ExtendedKind, NSFW_DISPLAY_POLICY, SUPPORTED_KINDS } from '@/constants' import { getParentStuff, isNsfwEvent } from '@/lib/event' import { toExternalContent, toNote } from '@/lib/link' +import { generateBech32IdFromATag, generateBech32IdFromETag, tagNameEquals } from '@/lib/tag' import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useMuteList } from '@/providers/MuteListProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { Event, kinds } from 'nostr-tools' import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' import AudioPlayer from '../AudioPlayer' import ClientTag from '../ClientTag' import Content from '../Content' @@ -32,6 +34,7 @@ import MutedNote from './MutedNote' import NsfwNote from './NsfwNote' import PictureNote from './PictureNote' import Poll from './Poll' +import Reaction from './Reaction' import RelayReview from './RelayReview' import UnknownNote from './UnknownNote' import VideoNote from './VideoNote' @@ -51,11 +54,21 @@ export default function Note({ hideParentNotePreview?: boolean showFull?: boolean }) { + const { t } = useTranslation() const { push } = useSecondaryPage() const { isSmallScreen } = useScreenSize() const { parentEventId, parentExternalContent } = useMemo(() => { return getParentStuff(event) }, [event]) + const reactionTargetEventId = useMemo(() => { + if (event.kind !== kinds.Reaction && event.kind !== ExtendedKind.EXTERNAL_CONTENT_REACTION) { + return undefined + } + const aTag = event.tags.findLast(tagNameEquals('a')) + if (aTag) return generateBech32IdFromATag(aTag) + const eTag = event.tags.findLast(tagNameEquals('e')) + return eTag ? generateBech32IdFromETag(eTag) : undefined + }, [event]) const { nsfwDisplayPolicy } = useContentPolicy() const [showNsfw, setShowNsfw] = useState(false) const { mutePubkeySet } = useMuteList() @@ -117,6 +130,8 @@ export default function Note({ content = } else if (event.kind === ExtendedKind.FOLLOW_PACK) { content = + } else if (event.kind === kinds.Reaction) { + content = } else { content = } @@ -170,6 +185,17 @@ export default function Note({ }} /> )} + {reactionTargetEventId && ( + { + e.stopPropagation() + push(toNote(reactionTargetEventId)) + }} + /> + )} {content}
) diff --git a/src/components/ParentNotePreview/index.tsx b/src/components/ParentNotePreview/index.tsx index 2466cef..c9699e7 100644 --- a/src/components/ParentNotePreview/index.tsx +++ b/src/components/ParentNotePreview/index.tsx @@ -9,15 +9,18 @@ export default function ParentNotePreview({ eventId, externalContent, className, - onClick + onClick, + label }: { eventId?: string externalContent?: string className?: string onClick?: React.MouseEventHandler | undefined + label?: string }) { const { t } = useTranslation() const { event, isFetching } = useFetchEvent(eventId) + const displayLabel = label ?? t('reply to') if (externalContent) { return ( @@ -28,7 +31,7 @@ export default function ParentNotePreview({ )} onClick={onClick} > -
{t('reply to')}
+
{displayLabel}
{externalContent}
) @@ -46,7 +49,7 @@ export default function ParentNotePreview({ className )} > -
{t('reply to')}
+
{displayLabel}
@@ -64,7 +67,7 @@ export default function ParentNotePreview({ )} onClick={event ? onClick : undefined} > -
{t('reply to')}
+
{displayLabel}
{event && }
diff --git a/src/components/ReactionList/index.tsx b/src/components/ReactionList/index.tsx index d025907..ee36cad 100644 --- a/src/components/ReactionList/index.tsx +++ b/src/components/ReactionList/index.tsx @@ -2,7 +2,7 @@ import { useSecondaryPage } from '@/PageManager' import { SPECIAL_TRUST_SCORE_FILTER_ID } from '@/constants' import { useStuff } from '@/hooks/useStuff' import { useStuffStatsById } from '@/hooks/useStuffStatsById' -import { toProfile } from '@/lib/link' +import { toNote } from '@/lib/link' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useUserTrust } from '@/providers/UserTrustProvider' import { TEmoji } from '@/types' @@ -27,6 +27,7 @@ export default function ReactionList({ stuff }: { stuff: Event | string }) { const [filteredLikes, setFilteredLikes] = useState< Array<{ id: string + eventId: string pubkey: string emoji: string | TEmoji created_at: number @@ -38,6 +39,7 @@ export default function ReactionList({ stuff }: { stuff: Event | string }) { const likes = noteStats?.likes ?? [] const filtered: { id: string + eventId: string pubkey: string created_at: number emoji: string | TEmoji @@ -81,7 +83,7 @@ export default function ReactionList({ stuff }: { stuff: Event | string }) {
push(toProfile(like.pubkey))} + onClick={() => push(toNote(like.eventId))} >
- likes: { id: string; pubkey: string; created_at: number; emoji: TEmoji | string }[] + likes: { + id: string + eventId: string + pubkey: string + created_at: number + emoji: TEmoji | string + }[] repostPubkeySet: Set reposts: { id: string; pubkey: string; created_at: number }[] zapPrSet: Set @@ -269,7 +280,13 @@ class StuffStatsService { } likeIdSet.add(evt.id) - likes.push({ id: evt.id, pubkey: evt.pubkey, created_at: evt.created_at, emoji }) + likes.push({ + id: evt.id, + eventId: getNoteBech32Id(evt), + pubkey: evt.pubkey, + created_at: evt.created_at, + emoji + }) this.stuffStatsMap.set(targetEventKey, { ...old, likeIdSet, likes }) return targetEventKey } @@ -298,7 +315,13 @@ class StuffStatsService { } likeIdSet.add(evt.id) - likes.push({ id: evt.id, pubkey: evt.pubkey, created_at: evt.created_at, emoji }) + likes.push({ + id: evt.id, + eventId: getNoteBech32Id(evt), + pubkey: evt.pubkey, + created_at: evt.created_at, + emoji + }) this.stuffStatsMap.set(target, { ...old, likeIdSet, likes }) return target } From b00ff341c806baff5da57a0489f9e2b552b422fd Mon Sep 17 00:00:00 2001 From: Cody Tseng Date: Sat, 4 Apr 2026 15:18:04 +0800 Subject: [PATCH 07/10] feat: add schemata schema validation tests (#689) Co-authored-by: alltheseas Co-authored-by: Claude Opus 4.6 Co-authored-by: alltheseas <64376233+alltheseas@users.noreply.github.com> --- package-lock.json | 2766 ++++++++++++++++++++- package.json | 12 +- src/__tests__/schemata-validation.spec.ts | 481 ++++ vitest.config.ts | 19 + 4 files changed, 3210 insertions(+), 68 deletions(-) create mode 100644 src/__tests__/schemata-validation.spec.ts create mode 100644 vitest.config.ts diff --git a/package-lock.json b/package-lock.json index 0f21091..322fd10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,11 +83,14 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", - "@types/node": "^22.10.2", + "@nostrability/schemata": "^0.3.2", + "@types/node": "^22.19.17", "@types/react": "^18.3.17", "@types/react-dom": "^18.3.5", "@types/uri-templates": "^0.1.34", "@vitejs/plugin-react": "^4.3.4", + "ajv": "^8.18.0", + "ajv-errors": "^3.0.0", "autoprefixer": "^10.4.20", "eslint": "^9.17.0", "eslint-plugin-react-hooks": "^5.0.0", @@ -100,7 +103,8 @@ "typescript": "~5.6.2", "typescript-eslint": "^8.18.1", "vite": "^6.0.3", - "vite-plugin-pwa": "^0.21.1" + "vite-plugin-pwa": "^0.21.1", + "vitest": "^3.2.4" } }, "node_modules/@alloc/quick-lru": { @@ -127,6 +131,24 @@ "node": ">=6.0.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.9.3", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", + "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -1642,6 +1664,40 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@dnd-kit/accessibility": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", @@ -2181,6 +2237,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -2193,6 +2266,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/js": { "version": "9.17.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", @@ -2347,6 +2427,13 @@ } } }, + "node_modules/@glideapps/ts-necessities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@glideapps/ts-necessities/-/ts-necessities-2.4.0.tgz", + "integrity": "sha512-mDC+qosuNa4lxR3ioMBb6CD0XLRsQBplU+zRPUYiMLXKeVPZ6UYphdNG/EGReig0YyfnVlBKZEXl1wzTotYmPA==", + "dev": true, + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2464,9 +2551,10 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -2477,6 +2565,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, "node_modules/@lightninglabs/lnc-core": { "version": "0.3.4-alpha", "resolved": "https://registry.npmjs.org/@lightninglabs/lnc-core/-/lnc-core-0.3.4-alpha.tgz", @@ -2493,6 +2588,69 @@ "crypto-js": "4.2.0" } }, + "node_modules/@mark.probst/typescript-json-schema": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@mark.probst/typescript-json-schema/-/typescript-json-schema-0.55.0.tgz", + "integrity": "sha512-jI48mSnRgFQxXiE/UTUCVCpX8lK3wCFKLF1Ss2aEreboKNuLQGt3e0/YFqWVHe/WENxOaqiJvwOz+L/SrN2+qQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^16.9.2", + "glob": "^7.1.7", + "path-equal": "^1.1.2", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "4.9.4", + "yargs": "^17.1.1" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/@mark.probst/typescript-json-schema/node_modules/@types/node": { + "version": "16.18.126", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", + "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mark.probst/typescript-json-schema/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@mark.probst/typescript-json-schema/node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/@noble/ciphers": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", @@ -2567,6 +2725,57 @@ "node": ">= 8" } }, + "node_modules/@nostrability/schemata": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@nostrability/schemata/-/schemata-0.3.2.tgz", + "integrity": "sha512-KlMsA53phra3UY91eQskh+J7DXjaWrNW2Bm57dYOozwIEcT6o+RmHRAzwaMkxffk2RBQWVSJoynhL323gmRogw==", + "dev": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.7.2", + "ajv": "8.17.1", + "esbuild-yaml": "^1.2.0", + "json-loader": "0.5.7", + "quicktype": "23.0.170", + "quicktype-core": "23.0.170", + "webpack": "5.93.0", + "webpack-cli": "5.1.4", + "webpack-merge": "6.0.1", + "webpack-node-externals": "3.0.0", + "yaml": "2.4.5", + "yaml-convert": "1.0.1" + } + }, + "node_modules/@nostrability/schemata/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nostrability/schemata/node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -5263,6 +5472,34 @@ "@tiptap/pm": "^2.7.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -5304,6 +5541,17 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -5312,6 +5560,35 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -5372,12 +5649,13 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", - "dev": true, + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "devOptional": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/offscreencanvas": { @@ -5670,6 +5948,312 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, "node_modules/@webbtc/webln-types": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@webbtc/webln-types/-/webln-types-3.0.0.tgz", @@ -5679,11 +6263,85 @@ "url": "lightning:hello@getalby.com" } }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -5691,6 +6349,16 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -5700,22 +6368,64 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "devOptional": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.1" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -5779,6 +6489,16 @@ "node": ">=10" } }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -5816,6 +6536,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -6006,6 +6736,13 @@ "node": ">=8" } }, + "node_modules/browser-or-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-3.0.0.tgz", + "integrity": "sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ==", + "dev": true, + "license": "MIT" + }, "node_modules/browserslist": { "version": "4.24.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", @@ -6068,6 +6805,16 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -6161,6 +6908,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6177,6 +6941,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -6213,6 +6993,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -6247,6 +7037,16 @@ "node": ">= 6" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -6258,6 +7058,99 @@ "url": "https://polar.sh/cva" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -6687,6 +7580,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/collection-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collection-utils/-/collection-utils-1.0.1.tgz", + "integrity": "sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6703,6 +7603,13 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -6712,6 +7619,58 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-usage": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.4.tgz", + "integrity": "sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-back": "^6.2.2", + "chalk-template": "^0.4.0", + "table-layout": "^4.1.1", + "typical": "^7.3.0" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/command-line-usage/node_modules/array-back": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz", + "integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, + "node_modules/command-line-usage/node_modules/typical": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -6754,11 +7713,28 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" }, + "node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6865,9 +7841,10 @@ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -6892,6 +7869,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6971,6 +7958,16 @@ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -7103,6 +8100,20 @@ "emojibase": "*" } }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -7114,6 +8125,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/es-abstract": { "version": "1.23.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.7.tgz", @@ -7193,6 +8217,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -7275,6 +8306,19 @@ "@esbuild/win32-x64": "0.24.0" } }, + "node_modules/esbuild-yaml": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/esbuild-yaml/-/esbuild-yaml-1.3.0.tgz", + "integrity": "sha512-e7TQfmbB7FSHxz35QMjaaLlJUanutHZ5N3wkxPw4HFgM95AmC5TUp3REOo7F/HbxF/su6pPYhq/Ux+CTROnX2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^4.1.0" + }, + "peerDependencies": { + "esbuild": ">=0.19.0" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -7403,6 +8447,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", @@ -7477,6 +8545,36 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7531,6 +8629,16 @@ "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", "dev": true }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -7592,6 +8700,19 @@ "node": ">=8" } }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -7613,6 +8734,16 @@ "resolved": "https://registry.npmjs.org/flairup/-/flairup-1.0.0.tgz", "integrity": "sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA==" }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -7767,6 +8898,16 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", @@ -7852,6 +8993,13 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -7926,6 +9074,17 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/graphql": { + "version": "0.11.7", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.11.7.tgz", + "integrity": "sha512-x7uDjyz8Jx+QPbpCFCMQ8lltnQa4p4vSYHx6ADe8rVYRTdsyhCJbvSty5DAsLVmU6cGakl+r8HQYolKHxk/tiw==", + "deprecated": "No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support", + "dev": true, + "license": "MIT", + "dependencies": { + "iterall": "1.1.3" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -8156,6 +9315,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -8201,6 +9380,16 @@ "node": ">= 0.4" } }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -8498,6 +9687,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8612,6 +9814,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true, + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -8666,6 +9875,23 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/iterall": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.3.tgz", + "integrity": "sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ==", + "dev": true, + "license": "MIT" + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -8698,6 +9924,37 @@ "node": ">=10" } }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -8735,6 +9992,13 @@ } } }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8770,6 +10034,20 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -8777,10 +10055,11 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -8830,6 +10109,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -8876,6 +10165,20 @@ "uc.micro": "^2.0.0" } }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -8897,6 +10200,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -8944,6 +10254,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", @@ -8969,6 +10286,13 @@ "sourcemap-codec": "^1.4.8" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true, + "license": "ISC" + }, "node_modules/markdown-it": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", @@ -9274,6 +10598,13 @@ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -9829,6 +11160,29 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -9849,6 +11203,16 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9897,6 +11261,13 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/next-themes": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", @@ -9907,6 +11278,52 @@ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -10093,11 +11510,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10133,6 +11567,13 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" }, + "node_modules/path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -10192,6 +11633,23 @@ "node": ">=16" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10224,6 +11682,85 @@ "node": ">= 6" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -10484,6 +12021,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -10731,6 +12278,142 @@ } ] }, + "node_modules/quicktype": { + "version": "23.0.170", + "resolved": "https://registry.npmjs.org/quicktype/-/quicktype-23.0.170.tgz", + "integrity": "sha512-3gFyS7w36ktxrttEv1gMfuUlGairepnSpLN0cp7JVevkKX2N6Uk8AyMlDS2Puki09MY6PB6ch90plThvACtEHA==", + "dev": true, + "license": "Apache-2.0", + "workspaces": [ + "./packages/quicktype-core", + "./packages/quicktype-graphql-input", + "./packages/quicktype-typescript-input", + "./packages/quicktype-vscode" + ], + "dependencies": { + "@glideapps/ts-necessities": "^2.2.3", + "chalk": "^4.1.2", + "collection-utils": "^1.0.1", + "command-line-args": "^5.2.1", + "command-line-usage": "^7.0.1", + "cross-fetch": "^4.0.0", + "graphql": "^0.11.7", + "lodash": "^4.17.21", + "moment": "^2.30.1", + "quicktype-core": "23.0.170", + "quicktype-graphql-input": "23.0.170", + "quicktype-typescript-input": "23.0.170", + "readable-stream": "^4.5.2", + "stream-json": "1.8.0", + "string-to-stream": "^3.0.1", + "typescript": "4.9.5" + }, + "bin": { + "quicktype": "dist/index.js" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/quicktype-core": { + "version": "23.0.170", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.170.tgz", + "integrity": "sha512-ZsjveG0yJUIijUx4yQshzyQ5EAXKbFSBTQJHnJ+KoSZVxcS+m3GcmDpzrdUIRYMhgLaF11ZGvLSYi5U0xcwemw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@glideapps/ts-necessities": "2.2.3", + "browser-or-node": "^3.0.0", + "collection-utils": "^1.0.1", + "cross-fetch": "^4.0.0", + "is-url": "^1.2.4", + "js-base64": "^3.7.7", + "lodash": "^4.17.21", + "pako": "^1.0.6", + "pluralize": "^8.0.0", + "readable-stream": "4.5.2", + "unicode-properties": "^1.4.1", + "urijs": "^1.19.1", + "wordwrap": "^1.0.0", + "yaml": "^2.4.1" + } + }, + "node_modules/quicktype-core/node_modules/@glideapps/ts-necessities": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@glideapps/ts-necessities/-/ts-necessities-2.2.3.tgz", + "integrity": "sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/quicktype-core/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/quicktype-graphql-input": { + "version": "23.0.170", + "resolved": "https://registry.npmjs.org/quicktype-graphql-input/-/quicktype-graphql-input-23.0.170.tgz", + "integrity": "sha512-L0xPKdIFZFChwups9oqJuQw/vwEbRVKBvU9L5jAs0Z/aLyfdsuxDpKGMJXnNWa2yE7NhPX/UDX8ytxn8uc8hdQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "collection-utils": "^1.0.1", + "graphql": "^0.11.7", + "quicktype-core": "23.0.170" + } + }, + "node_modules/quicktype-typescript-input": { + "version": "23.0.170", + "resolved": "https://registry.npmjs.org/quicktype-typescript-input/-/quicktype-typescript-input-23.0.170.tgz", + "integrity": "sha512-lckhc//Mc95f/puRFKv4BFs7VpUUJXhw/psh+5ZAMiErxOWgoF87XthGusmaqoXNzjmEy1AVwGgMCG2pp/tJ/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@mark.probst/typescript-json-schema": "0.55.0", + "quicktype-core": "23.0.170", + "typescript": "4.9.5" + } + }, + "node_modules/quicktype-typescript-input/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/quicktype/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -10902,6 +12585,23 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -10913,6 +12613,19 @@ "node": ">=8.10.0" } }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz", @@ -11094,6 +12807,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -11122,6 +12845,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -11261,6 +13007,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -11269,6 +13025,59 @@ "loose-envify": "^1.1.0" } }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -11319,6 +13128,19 @@ "node": ">= 0.4" } }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11410,6 +13232,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -11492,6 +13321,72 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.8.0.tgz", + "integrity": "sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-to-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-3.0.1.tgz", + "integrity": "sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, + "node_modules/string-to-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -11711,6 +13606,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/style-to-js": { "version": "1.1.17", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", @@ -11771,6 +13686,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/table-layout": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", + "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-back": "^6.2.2", + "wordwrapjs": "^5.1.0" + }, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/table-layout/node_modules/array-back": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz", + "integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/tailwind-merge": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", @@ -11824,6 +13763,20 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -11869,6 +13822,73 @@ "node": ">=10" } }, + "node_modules/terser-webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -11900,24 +13920,53 @@ "integrity": "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg==", "license": "MIT" }, - "node_modules/tinyglobby": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", - "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", "dependencies": { - "fdir": "^6.4.2", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", - "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -11928,10 +13977,11 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -11939,6 +13989,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tippy.js": { "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", @@ -12016,6 +14096,57 @@ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -12154,6 +14285,16 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -12178,10 +14319,11 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", @@ -12214,6 +14356,17 @@ "node": ">=4" } }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/unicode-property-aliases-ecmascript": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", @@ -12223,6 +14376,24 @@ "node": ">=4" } }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true, + "license": "MIT" + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -12370,6 +14541,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -12380,6 +14552,13 @@ "integrity": "sha512-EWkjYEN0L6KOfEoOH6Wj4ghQqU7eBZMJqRHQnxQAq+dSEzRPClkWjf8557HkWQXF6BrAUoLSAyy9i3RVTliaNg==", "license": "http://geraintluff.github.io/tv4/LICENSE.txt" }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -12434,6 +14613,13 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/vaul": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", @@ -12543,6 +14729,29 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite-plugin-pwa": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.21.1.tgz", @@ -12573,6 +14782,102 @@ } } }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -12586,12 +14891,204 @@ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "dev": true }, + "node_modules/webpack": { + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", + "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-cli/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", @@ -12710,6 +15207,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -12719,6 +15240,23 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wordwrapjs": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz", + "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/workbox-background-sync": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.3.0.tgz", @@ -12862,22 +15400,6 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, - "node_modules/workbox-build/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/workbox-build/node_modules/estree-walker": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", @@ -12905,12 +15427,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/workbox-build/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/workbox-build/node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -13149,6 +15665,16 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -13166,6 +15692,106 @@ "node": ">= 14" } }, + "node_modules/yaml-convert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/yaml-convert/-/yaml-convert-1.0.1.tgz", + "integrity": "sha512-+LlsBOCJEd2hC+dGiEuHjHxdt7nhhru2lwq+tHWwjEViarR2DYxTKgQwlBzaMUA0K6yaDUrLEk4CuD6QxpHE3Q==", + "deprecated": "Command-line tool now included in yaml package", + "dev": true, + "license": "ISC", + "dependencies": { + "yaml": "^1.10.2", + "yargs": "^17.3.1" + }, + "bin": { + "yaml-convert": "cli.js", + "yc": "cli.js" + } + }, + "node_modules/yaml-convert/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yet-another-react-lightbox": { "version": "3.21.7", "resolved": "https://registry.npmjs.org/yet-another-react-lightbox/-/yet-another-react-lightbox-3.21.7.tgz", @@ -13178,6 +15804,16 @@ "react-dom": ">=16.8.0" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index ff3ae74..86469bc 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "build": "tsc -b && vite build", "lint": "eslint .", "format": "prettier --write .", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest run", + "test:schemas": "vitest run schemata-validation" }, "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -93,11 +95,14 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", - "@types/node": "^22.10.2", + "@nostrability/schemata": "^0.3.2", + "@types/node": "^22.19.17", "@types/react": "^18.3.17", "@types/react-dom": "^18.3.5", "@types/uri-templates": "^0.1.34", "@vitejs/plugin-react": "^4.3.4", + "ajv": "^8.18.0", + "ajv-errors": "^3.0.0", "autoprefixer": "^10.4.20", "eslint": "^9.17.0", "eslint-plugin-react-hooks": "^5.0.0", @@ -110,6 +115,7 @@ "typescript": "~5.6.2", "typescript-eslint": "^8.18.1", "vite": "^6.0.3", - "vite-plugin-pwa": "^0.21.1" + "vite-plugin-pwa": "^0.21.1", + "vitest": "^3.2.4" } } diff --git a/src/__tests__/schemata-validation.spec.ts b/src/__tests__/schemata-validation.spec.ts new file mode 100644 index 0000000..1326f5e --- /dev/null +++ b/src/__tests__/schemata-validation.spec.ts @@ -0,0 +1,481 @@ +/** + * Schemata validation tests for Jumble's draft event builders. + * + * Validates that every create*DraftEvent function in src/lib/draft-event.ts + * produces events conforming to @nostrability/schemata JSON schemas. + * + * Covers 21 kinds: 0, 1, 3, 5, 6, 7, 16, 17, 1111, 9802, + * 10000, 10001, 10002, 10003, 10012, 10030, 10063, + * 28934, 28936, 30002, 30078 + * + * Skipped: + * - kind 1018/1068 (NIP-88) — schemata e-tag schema enforces NIP-10 markers + * but NIP-88 spec uses bare e tags (https://github.com/nostrability/schemata/issues/108) + * - kind 1984 (NIP-56) — schemata schema overly strict on p tag + * (https://github.com/nostrability/schemata/issues/107) + * - kind 31987 — no schemata schema exists yet + */ + +// ── Mocks (must be before any imports that use these services) ────────── +import { vi, describe, it, expect, beforeAll } from 'vitest' + +vi.mock('@/services/client.service', () => ({ + default: { + getEventHint: () => 'wss://relay.example.com', + getReplaeableEventFromCache: () => undefined, + fetchEvent: vi.fn().mockResolvedValue(undefined), + fetchRelayList: vi.fn().mockResolvedValue({ read: [], write: [] }) + } +})) + +vi.mock('@/services/custom-emoji.service', () => ({ + default: { + getEmojiById: () => undefined + } +})) + +vi.mock('@/services/media-upload.service', () => ({ + default: { + getImetaTagByUrl: () => undefined + } +})) + +// ── Imports ───────────────────────────────────────────────────────────── +import { createRequire } from 'node:module' +import Ajv from 'ajv' +import ajvErrors from 'ajv-errors' +import { kinds, type Event } from 'nostr-tools' + +import { + createProfileDraftEvent, + createShortTextNoteDraftEvent, + createFollowListDraftEvent, + createDeletionRequestDraftEvent, + createRepostDraftEvent, + createReactionDraftEvent, + createCommentDraftEvent, + createHighlightDraftEvent, + createMuteListDraftEvent, + createRelayListDraftEvent, + createBookmarkDraftEvent, + createPinListDraftEvent, + createFavoriteRelaysDraftEvent, + createUserEmojiListDraftEvent, + createBlossomServerListDraftEvent, + createJoinDraftEvent, + createLeaveDraftEvent, + createSeenNotificationsAtDraftEvent, + createRelaySetDraftEvent, + createExternalContentReactionDraftEvent +} from '@/lib/draft-event' + +import { ExtendedKind } from '@/constants' + +// ── Schema loading ────────────────────────────────────────────────────── +const require_ = createRequire(import.meta.url) +const schemataBase = require_ + .resolve('@nostrability/schemata') + .replace(/\/dist\/.*/, '/dist/nips') + +function loadSchema(path: string): object { + return require_(`${schemataBase}/${path}`) +} + +/** + * Recursively strip nested $schema, $id, and errorMessage fields + * that confuse AJV's strict mode. + */ +function stripSchemaFields(obj: unknown): unknown { + if (Array.isArray(obj)) { + return obj.map(stripSchemaFields) + } + if (obj !== null && typeof obj === 'object') { + const result: Record = {} + for (const [key, value] of Object.entries(obj as Record)) { + if (key === 'errorMessage') continue + if (key === '$schema' || key === '$id') continue + result[key] = stripSchemaFields(value) + } + return result + } + return obj +} + +function createValidator(schema: object): ReturnType { + const cleaned = stripSchemaFields(schema) as Record + const ajv = new Ajv({ allErrors: true, strict: false }) + ajvErrors(ajv) + return ajv.compile(cleaned) +} + +// ── Schema definitions (loaded at module level) ───────────────────────── +const kind0Schema = loadSchema('nip-01/kind-0/schema.json') +const kind1Schema = loadSchema('nip-01/kind-1/schema.json') +const kind3Schema = loadSchema('nip-02/kind-3/schema.json') +const kind5Schema = loadSchema('nip-09/kind-5/schema.json') +const kind6Schema = loadSchema('nip-18/kind-6/schema.json') +const kind7Schema = loadSchema('nip-25/kind-7/schema.json') +const kind16Schema = loadSchema('nip-18/kind-16/schema.json') +const kind17Schema = loadSchema('nip-25/kind-17/schema.json') +const kind1111Schema = loadSchema('nip-22/kind-1111/schema.json') +const kind9802Schema = loadSchema('nip-84/kind-9802/schema.json') +const kind10000Schema = loadSchema('nip-51/kind-10000/schema.json') +const kind10001Schema = loadSchema('nip-51/kind-10001/schema.json') +const kind10002Schema = loadSchema('nip-65/kind-10002/schema.json') +const kind10003Schema = loadSchema('nip-51/kind-10003/schema.json') +const kind10012Schema = loadSchema('nip-51/kind-10012/schema.json') +const kind10030Schema = loadSchema('nip-51/kind-10030/schema.json') +const kind10063Schema = loadSchema('nip-b7/kind-10063/schema.json') +const kind28934Schema = loadSchema('nip-43/kind-28934/schema.json') +const kind28936Schema = loadSchema('nip-43/kind-28936/schema.json') +const kind30002Schema = loadSchema('nip-51/kind-30002/schema.json') +const kind30078Schema = loadSchema('nip-78/kind-30078/schema.json') + +function buildSchemaRegistry(): Map { + const entries: [number, object][] = [ + [0, kind0Schema], + [1, kind1Schema], + [3, kind3Schema], + [5, kind5Schema], + [6, kind6Schema], + [7, kind7Schema], + [16, kind16Schema], + [17, kind17Schema], + [1111, kind1111Schema], + [9802, kind9802Schema], + [10000, kind10000Schema], + [10001, kind10001Schema], + [10002, kind10002Schema], + [10003, kind10003Schema], + [10012, kind10012Schema], + [10030, kind10030Schema], + [10063, kind10063Schema], + [28934, kind28934Schema], + [28936, kind28936Schema], + [30002, kind30002Schema], + [30078, kind30078Schema] + ] + return new Map(entries) +} + +// ── Helpers ───────────────────────────────────────────────────────────── +const FAKE_PUBKEY = '0'.repeat(64) +const FAKE_PUBKEY_2 = '1'.repeat(64) +const FAKE_ID = 'a'.repeat(64) +const FAKE_SIG = 'b'.repeat(128) + +type DraftEvent = { kind: number; content: string; tags: string[][]; created_at: number } + +function validateDraftEvent( + draft: DraftEvent, + schemaRegistry: Map +): { valid: boolean; errors: string[] } { + const schema = schemaRegistry.get(draft.kind) + if (!schema) { + return { valid: false, errors: [`No schema found for kind ${draft.kind}`] } + } + + const signedEvent = { + ...draft, + pubkey: FAKE_PUBKEY, + id: FAKE_ID, + sig: FAKE_SIG + } + + const validate = createValidator(schema) + const valid = validate(signedEvent) as boolean + const errors = valid + ? [] + : (validate.errors ?? []).map((e) => `${e.instancePath || '/'}: ${e.message}`) + + return { valid, errors } +} + +/** Build a fake signed Event for functions that require one as input */ +function makeEvent(overrides: Partial = {}): Event { + return { + id: 'c'.repeat(64), + pubkey: FAKE_PUBKEY_2, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: 'hello nostr', + sig: 'e'.repeat(128), + ...overrides + } +} + +// ── Tests ─────────────────────────────────────────────────────────────── +describe('Schemata Schema Validation', () => { + let schemaRegistry: Map + + beforeAll(() => { + schemaRegistry = buildSchemaRegistry() + }) + + it('schema registry has 21 kinds', () => { + expect(schemaRegistry.size).toBe(21) + }) + + // Kind 0 – Profile metadata + it('kind 0 (Profile) via createProfileDraftEvent', () => { + const draft = createProfileDraftEvent( + JSON.stringify({ name: 'Alice', about: 'Test profile', picture: 'https://example.com/avatar.png' }) + ) + expect(draft.kind).toBe(0) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 1 – Short text note + it('kind 1 (Short Text Note) via createShortTextNoteDraftEvent', async () => { + const draft = await createShortTextNoteDraftEvent('Hello, Nostr!', []) + expect(draft.kind).toBe(1) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + it('kind 1 (Short Text Note reply) via createShortTextNoteDraftEvent', async () => { + const parent = makeEvent() + const draft = await createShortTextNoteDraftEvent('Nice post!', [], { parentEvent: parent }) + expect(draft.kind).toBe(1) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 3 – Follow list + it('kind 3 (Follow List) via createFollowListDraftEvent', () => { + const tags = [ + ['p', 'a'.repeat(64), 'wss://relay1.example.com', 'alice'], + ['p', 'b'.repeat(64), 'wss://relay2.example.com', 'bob'] + ] + const draft = createFollowListDraftEvent(tags) + expect(draft.kind).toBe(kinds.Contacts) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 5 – Deletion request + it('kind 5 (Deletion) via createDeletionRequestDraftEvent', () => { + const event = makeEvent({ kind: 1 }) + const draft = createDeletionRequestDraftEvent(event) + expect(draft.kind).toBe(kinds.EventDeletion) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 6 – Repost (text note) + it('kind 6 (Repost) via createRepostDraftEvent', () => { + const event = makeEvent({ kind: 1 }) + const draft = createRepostDraftEvent(event) + expect(draft.kind).toBe(kinds.Repost) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 7 – Reaction + it('kind 7 (Reaction: like) via createReactionDraftEvent', () => { + const event = makeEvent() + const draft = createReactionDraftEvent(event) + expect(draft.kind).toBe(kinds.Reaction) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + it('kind 7 (Reaction: custom emoji) via createReactionDraftEvent', () => { + const event = makeEvent() + const draft = createReactionDraftEvent(event, '🤙') + expect(draft.kind).toBe(kinds.Reaction) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 16 – Generic repost (non-text) + it('kind 16 (Generic Repost) via createRepostDraftEvent', () => { + const event = makeEvent({ kind: 30023 }) + const draft = createRepostDraftEvent(event) + expect(draft.kind).toBe(kinds.GenericRepost) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 17 – External content reaction + it('kind 17 (External Content Reaction) via createExternalContentReactionDraftEvent', () => { + const draft = createExternalContentReactionDraftEvent('https://example.com/article') + expect(draft.kind).toBe(ExtendedKind.EXTERNAL_CONTENT_REACTION) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + it('kind 17 (External Content Reaction: custom) via createExternalContentReactionDraftEvent', () => { + const draft = createExternalContentReactionDraftEvent('https://example.com/article', '🔥') + expect(draft.kind).toBe(ExtendedKind.EXTERNAL_CONTENT_REACTION) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 1111 – Comment + it('kind 1111 (Comment on event) via createCommentDraftEvent', async () => { + const parent = makeEvent({ kind: 1 }) + const draft = await createCommentDraftEvent('Great post!', parent, []) + expect(draft.kind).toBe(ExtendedKind.COMMENT) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + it('kind 1111 (Comment on external content) via createCommentDraftEvent', async () => { + const draft = await createCommentDraftEvent('Interesting article', 'https://example.com/article', []) + expect(draft.kind).toBe(ExtendedKind.COMMENT) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 9802 – Highlight + it('kind 9802 (Highlight from event) via createHighlightDraftEvent', () => { + const event = makeEvent({ kind: 30023, content: 'A long article with highlighted text in it.' }) + const draft = createHighlightDraftEvent('highlighted text', '', event, []) + expect(draft.kind).toBe(kinds.Highlights) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 10000 – Mute list + it('kind 10000 (Mute List) via createMuteListDraftEvent', () => { + const tags = [ + ['p', 'a'.repeat(64)], + ['e', 'b'.repeat(64)], + ['word', 'spam'], + ['t', 'nsfw'] + ] + const draft = createMuteListDraftEvent(tags) + expect(draft.kind).toBe(kinds.Mutelist) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 10001 – Pin list + it('kind 10001 (Pin List) via createPinListDraftEvent', () => { + const tags = [['e', 'a'.repeat(64)], ['e', 'b'.repeat(64)]] + const draft = createPinListDraftEvent(tags) + expect(draft.kind).toBe(kinds.Pinlist) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 10002 – Relay list + it('kind 10002 (Relay List) via createRelayListDraftEvent', () => { + const relays = [ + { url: 'wss://relay1.example.com', scope: 'both' as const }, + { url: 'wss://relay2.example.com', scope: 'read' as const } + ] + const draft = createRelayListDraftEvent(relays) + expect(draft.kind).toBe(kinds.RelayList) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 10003 – Bookmark list + it('kind 10003 (Bookmarks) via createBookmarkDraftEvent', () => { + const tags = [ + ['e', 'a'.repeat(64)], + ['a', `30023:${'b'.repeat(64)}:my-article`], + ['r', 'https://example.com'] + ] + const draft = createBookmarkDraftEvent(tags) + expect(draft.kind).toBe(kinds.BookmarkList) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 10012 – Favorite relays + it('kind 10012 (Favorite Relays) via createFavoriteRelaysDraftEvent', () => { + const draft = createFavoriteRelaysDraftEvent( + ['wss://relay1.example.com', 'wss://relay2.example.com'], + [] + ) + expect(draft.kind).toBe(ExtendedKind.FAVORITE_RELAYS) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 10030 – User emoji list + it('kind 10030 (User Emoji List) via createUserEmojiListDraftEvent', () => { + const tags = [['a', `30030:${'a'.repeat(64)}:my-emojis`]] + const draft = createUserEmojiListDraftEvent(tags) + expect(draft.kind).toBe(kinds.UserEmojiList) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 10063 – Blossom server list + it('kind 10063 (Blossom Server List) via createBlossomServerListDraftEvent', () => { + const draft = createBlossomServerListDraftEvent([ + 'https://blossom1.example.com', + 'https://blossom2.example.com' + ]) + expect(draft.kind).toBe(ExtendedKind.BLOSSOM_SERVER_LIST) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 28934 – Join request (NIP-43) + it('kind 28934 (Join) via createJoinDraftEvent', () => { + const draft = createJoinDraftEvent('invite-code-123') + expect(draft.kind).toBe(28934) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 28936 – Leave request (NIP-43) + it('kind 28936 (Leave) via createLeaveDraftEvent', () => { + const draft = createLeaveDraftEvent() + expect(draft.kind).toBe(28936) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 30002 – Relay set + it('kind 30002 (Relay Set) via createRelaySetDraftEvent', () => { + const draft = createRelaySetDraftEvent({ + id: 'my-set', + name: 'My Relay Set', + relayUrls: ['wss://relay1.example.com', 'wss://relay2.example.com'] + }) + expect(draft.kind).toBe(kinds.Relaysets) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) + + // Kind 30078 – Application-specific data (seen notifications) + it('kind 30078 (Application Data) via createSeenNotificationsAtDraftEvent', () => { + const draft = createSeenNotificationsAtDraftEvent() + expect(draft.kind).toBe(kinds.Application) + const result = validateDraftEvent(draft, schemaRegistry) + expect(result.errors).toEqual([]) + expect(result.valid).toBe(true) + }) +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..df4ff48 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,19 @@ +import path from 'path' +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + define: { + 'import.meta.env.GIT_COMMIT': '"test"', + 'import.meta.env.APP_VERSION': '"0.0.0"', + 'import.meta.env.VITE_COMMUNITY_RELAY_SETS': '[]', + 'import.meta.env.VITE_COMMUNITY_RELAYS': '[]' + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + }, + test: { + include: ['src/**/*.spec.ts'] + } +}) From 4fb40e81b355e3854001ddd890d21b271f322803 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 4 Apr 2026 16:30:48 +0800 Subject: [PATCH 08/10] feat: improve addressable video (kind 34235/34236) display with title and hashtags Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ContentPreview/VideoNotePreview.tsx | 13 ++++++++++++- src/components/ContentPreview/index.tsx | 7 ++++++- src/components/Note/VideoNote.tsx | 18 ++++++++++++++++++ src/lib/event-metadata.ts | 19 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/components/ContentPreview/VideoNotePreview.tsx b/src/components/ContentPreview/VideoNotePreview.tsx index d189b72..950e90f 100644 --- a/src/components/ContentPreview/VideoNotePreview.tsx +++ b/src/components/ContentPreview/VideoNotePreview.tsx @@ -1,5 +1,8 @@ +import { ExtendedKind } from '@/constants' +import { getVideoMetadataFromEvent } from '@/lib/event-metadata' import { cn } from '@/lib/utils' import { Event } from 'nostr-tools' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' export default function VideoNotePreview({ @@ -10,10 +13,18 @@ export default function VideoNotePreview({ className?: string }) { const { t } = useTranslation() + const isAddressable = + event.kind === ExtendedKind.ADDRESSABLE_NORMAL_VIDEO || + event.kind === ExtendedKind.ADDRESSABLE_SHORT_VIDEO + const metadata = useMemo( + () => (isAddressable ? getVideoMetadataFromEvent(event) : null), + [event, isAddressable] + ) return (
- [{t('Media')}] {event.content} + [{t('Media')}]{' '} + {metadata?.title || event.content}
) } diff --git a/src/components/ContentPreview/index.tsx b/src/components/ContentPreview/index.tsx index 576bdca..bc0f402 100644 --- a/src/components/ContentPreview/index.tsx +++ b/src/components/ContentPreview/index.tsx @@ -83,7 +83,12 @@ export default function ContentPreview({ return } - if (event.kind === ExtendedKind.VIDEO || event.kind === ExtendedKind.SHORT_VIDEO) { + if ( + event.kind === ExtendedKind.VIDEO || + event.kind === ExtendedKind.SHORT_VIDEO || + event.kind === ExtendedKind.ADDRESSABLE_NORMAL_VIDEO || + event.kind === ExtendedKind.ADDRESSABLE_SHORT_VIDEO + ) { return } diff --git a/src/components/Note/VideoNote.tsx b/src/components/Note/VideoNote.tsx index a5debe0..512d9ff 100644 --- a/src/components/Note/VideoNote.tsx +++ b/src/components/Note/VideoNote.tsx @@ -1,15 +1,33 @@ +import { ExtendedKind } from '@/constants' import { getImetaInfosFromEvent } from '@/lib/event' +import { getVideoMetadataFromEvent } from '@/lib/event-metadata' import { Event } from 'nostr-tools' import { useMemo } from 'react' import Content from '../Content' +import { EmbeddedHashtag } from '../Embedded' import MediaPlayer from '../MediaPlayer' export default function VideoNote({ event, className }: { event: Event; className?: string }) { const videoInfos = useMemo(() => getImetaInfosFromEvent(event), [event]) + const isAddressable = + event.kind === ExtendedKind.ADDRESSABLE_NORMAL_VIDEO || + event.kind === ExtendedKind.ADDRESSABLE_SHORT_VIDEO + const metadata = useMemo( + () => (isAddressable ? getVideoMetadataFromEvent(event) : null), + [event, isAddressable] + ) return (
+ {metadata?.title &&
{metadata.title}
} + {metadata && metadata.tags.length > 0 && ( +
+ {metadata.tags.map((tag) => ( + + ))} +
+ )} {videoInfos.map((video) => ( ))} diff --git a/src/lib/event-metadata.ts b/src/lib/event-metadata.ts index e85839b..433eea7 100644 --- a/src/lib/event-metadata.ts +++ b/src/lib/event-metadata.ts @@ -387,6 +387,25 @@ export function getEmojisFromEvent(event: Event): TEmoji[] { return info.emojis } +export function getVideoMetadataFromEvent(event: Event) { + let title: string | undefined + const tags = new Set() + + event.tags.forEach(([tagName, tagValue]) => { + if (tagName === 'title') { + title = tagValue + } else if (tagName === 't' && tagValue && tags.size < 6) { + tags.add(tagValue.toLocaleLowerCase()) + } + }) + + if (!title) { + title = event.tags.find(tagNameEquals('d'))?.[1] + } + + return { title, tags: Array.from(tags) } +} + export function getStarsFromRelayReviewEvent(event: Event): number { const ratingTag = event.tags.find((t) => t[0] === 'rating') if (ratingTag) { From 2efc884e01be62e4f8b791a9f9ca407f09854d99 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 4 Apr 2026 23:52:49 +0800 Subject: [PATCH 09/10] feat: migrate NIP-51 list encryption from NIP-04 to NIP-44 NIP-04 encryption is deprecated due to security vulnerabilities. This migrates MuteList (kind 10000) and PinnedUsers (kind 10010) private entries to use NIP-44 encryption, with backward compatibility for reading existing NIP-04 encrypted content. When NIP-04 content is detected, it is automatically re-encrypted with NIP-44 and republished to gradually migrate users. Co-Authored-By: captain-stacks <201298974+captain-stacks@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 (1M context) --- src/providers/MuteListProvider.tsx | 79 ++++++++++++++----- src/providers/NostrProvider/bunker.signer.ts | 14 ++++ src/providers/NostrProvider/index.tsx | 12 +++ src/providers/NostrProvider/nip-07.signer.ts | 20 +++++ .../NostrProvider/nostrConnection.signer.ts | 14 ++++ src/providers/NostrProvider/npub.signer.ts | 8 ++ src/providers/NostrProvider/nsec.signer.ts | 17 ++++ src/providers/PinnedUsersProvider.tsx | 53 ++++++++++--- src/types/index.d.ts | 6 ++ 9 files changed, 191 insertions(+), 32 deletions(-) diff --git a/src/providers/MuteListProvider.tsx b/src/providers/MuteListProvider.tsx index 0daab73..dc8428b 100644 --- a/src/providers/MuteListProvider.tsx +++ b/src/providers/MuteListProvider.tsx @@ -41,7 +41,8 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { publish, updateMuteListEvent, nip04Decrypt, - nip04Encrypt + nip44Encrypt, + nip44Decrypt } = useNostr() const [tags, setTags] = useState([]) const [privateTags, setPrivateTags] = useState([]) @@ -56,28 +57,53 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { const [changing, setChanging] = useState(false) const getPrivateTags = useCallback( - async (muteListEvent: Event) => { - if (!muteListEvent.content) return [] + async ( + muteListEvent: Event + ): Promise<{ privateTags: string[][]; wasNip04: boolean }> => { + if (!muteListEvent.content) return { privateTags: [], wasNip04: false } try { + const wasNip04 = muteListEvent.content.includes('?iv=') const storedPlainText = await indexedDb.getDecryptedContent(muteListEvent.id) let plainText: string if (storedPlainText) { + console.log('[MuteList] Using cached decrypted content for event', muteListEvent.id) plainText = storedPlainText } else { - plainText = await nip04Decrypt(muteListEvent.pubkey, muteListEvent.content) + console.log('[MuteList] Decrypting content with', wasNip04 ? 'NIP-04' : 'NIP-44', 'for event', muteListEvent.id) + plainText = wasNip04 + ? await nip04Decrypt(muteListEvent.pubkey, muteListEvent.content) + : await nip44Decrypt(muteListEvent.pubkey, muteListEvent.content) await indexedDb.putDecryptedContent(muteListEvent.id, plainText) } const privateTags = z.array(z.array(z.string())).parse(JSON.parse(plainText)) - return privateTags + console.log('[MuteList] Decrypted privateTags count:', privateTags.length, 'wasNip04:', wasNip04) + return { privateTags, wasNip04 } } catch (error) { console.error('Failed to decrypt mute list content', error) - return [] + return { privateTags: [], wasNip04: false } } }, - [nip04Decrypt] + [nip04Decrypt, nip44Decrypt] + ) + + const migrateToNip44 = useCallback( + async (muteListEvent: Event, privateTags: string[][]) => { + if (!accountPubkey) return + console.log('[MuteList] Migrating from NIP-04 to NIP-44, privateTags count:', privateTags.length) + try { + const cipherText = await nip44Encrypt(accountPubkey, JSON.stringify(privateTags)) + const newMuteListDraftEvent = createMuteListDraftEvent(muteListEvent.tags, cipherText) + const event = await publish(newMuteListDraftEvent) + console.log('[MuteList] Migration successful, new event id:', event.id) + await updateMuteListEvent(event, privateTags) + } catch (error) { + console.error('[MuteList] Failed to migrate to NIP-44', error) + } + }, + [accountPubkey, nip44Encrypt, publish, updateMuteListEvent] ) useEffect(() => { @@ -88,11 +114,16 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { return } - const privateTags = await getPrivateTags(muteListEvent).catch(() => { - return [] - }) + const { privateTags, wasNip04 } = await getPrivateTags(muteListEvent).catch(() => ({ + privateTags: [] as string[][], + wasNip04: false + })) setPrivateTags(privateTags) setTags(muteListEvent.tags) + + if (wasNip04 && privateTags.length > 0) { + migrateToNip44(muteListEvent, privateTags) + } } updateMuteTags() }, [muteListEvent]) @@ -143,8 +174,14 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { return } const newTags = (muteListEvent?.tags ?? []).concat([['p', pubkey]]) - const newMuteListEvent = await publishNewMuteListEvent(newTags, muteListEvent?.content) - const privateTags = await getPrivateTags(newMuteListEvent) + const { privateTags } = muteListEvent + ? await getPrivateTags(muteListEvent) + : { privateTags: [] } + const cipherText = + privateTags.length > 0 + ? await nip44Encrypt(accountPubkey, JSON.stringify(privateTags)) + : '' + const newMuteListEvent = await publishNewMuteListEvent(newTags, cipherText) await updateMuteListEvent(newMuteListEvent, privateTags) } catch (error) { const errors = formatError(error) @@ -163,13 +200,15 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { try { const muteListEvent = await client.fetchMuteListEvent(accountPubkey) checkMuteListEvent(muteListEvent) - const privateTags = muteListEvent ? await getPrivateTags(muteListEvent) : [] + const { privateTags } = muteListEvent + ? await getPrivateTags(muteListEvent) + : { privateTags: [] as string[][] } if (privateTags.some(([tagName, tagValue]) => tagName === 'p' && tagValue === pubkey)) { return } const newPrivateTags = privateTags.concat([['p', pubkey]]) - const cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newPrivateTags)) + const cipherText = await nip44Encrypt(accountPubkey, JSON.stringify(newPrivateTags)) const newMuteListEvent = await publishNewMuteListEvent(muteListEvent?.tags ?? [], cipherText) await updateMuteListEvent(newMuteListEvent, newPrivateTags) } catch (error) { @@ -190,11 +229,11 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { const muteListEvent = await client.fetchMuteListEvent(accountPubkey) if (!muteListEvent) return - const privateTags = await getPrivateTags(muteListEvent) + const { privateTags } = await getPrivateTags(muteListEvent) const newPrivateTags = privateTags.filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey) let cipherText = muteListEvent.content if (newPrivateTags.length !== privateTags.length) { - cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newPrivateTags)) + cipherText = await nip44Encrypt(accountPubkey, JSON.stringify(newPrivateTags)) } const newMuteListEvent = await publishNewMuteListEvent( @@ -220,13 +259,13 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { const muteListEvent = await client.fetchMuteListEvent(accountPubkey) if (!muteListEvent) return - const privateTags = await getPrivateTags(muteListEvent) + const { privateTags } = await getPrivateTags(muteListEvent) const newPrivateTags = privateTags.filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey) if (newPrivateTags.length === privateTags.length) { return } - const cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newPrivateTags)) + const cipherText = await nip44Encrypt(accountPubkey, JSON.stringify(newPrivateTags)) const newMuteListEvent = await publishNewMuteListEvent( muteListEvent.tags .filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey) @@ -257,11 +296,11 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) { return } - const privateTags = await getPrivateTags(muteListEvent) + const { privateTags } = await getPrivateTags(muteListEvent) const newPrivateTags = privateTags .filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey) .concat([['p', pubkey]]) - const cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newPrivateTags)) + const cipherText = await nip44Encrypt(accountPubkey, JSON.stringify(newPrivateTags)) const newMuteListEvent = await publishNewMuteListEvent(newTags, cipherText) await updateMuteListEvent(newMuteListEvent, newPrivateTags) } catch (error) { diff --git a/src/providers/NostrProvider/bunker.signer.ts b/src/providers/NostrProvider/bunker.signer.ts index ab29a69..570906f 100644 --- a/src/providers/NostrProvider/bunker.signer.ts +++ b/src/providers/NostrProvider/bunker.signer.ts @@ -68,6 +68,20 @@ export class BunkerSigner implements ISigner { return await this.signer.nip04Decrypt(pubkey, cipherText) } + async nip44Encrypt(pubkey: string, plainText: string) { + if (!this.signer) { + throw new Error('Not logged in') + } + return await this.signer.nip44Encrypt(pubkey, plainText) + } + + async nip44Decrypt(pubkey: string, cipherText: string) { + if (!this.signer) { + throw new Error('Not logged in') + } + return await this.signer.nip44Decrypt(pubkey, cipherText) + } + getClientSecretKey() { return bytesToHex(this.clientSecretKey) } diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 8bbad75..0defdfc 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -81,6 +81,8 @@ type TNostrContext = { signEvent: (draftEvent: TDraftEvent) => Promise nip04Encrypt: (pubkey: string, plainText: string) => Promise nip04Decrypt: (pubkey: string, cipherText: string) => Promise + nip44Encrypt: (pubkey: string, plainText: string) => Promise + nip44Decrypt: (pubkey: string, cipherText: string) => Promise startLogin: () => void checkLogin: (cb?: () => T) => Promise updateRelayListEvent: (relayListEvent: Event) => Promise @@ -730,6 +732,14 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { return signer?.nip04Decrypt(pubkey, cipherText) ?? '' } + const nip44Encrypt = async (pubkey: string, plainText: string) => { + return signer?.nip44Encrypt(pubkey, plainText) ?? '' + } + + const nip44Decrypt = async (pubkey: string, cipherText: string) => { + return signer?.nip44Decrypt(pubkey, cipherText) ?? '' + } + const checkLogin = async (cb?: () => T): Promise => { if (signer) { return cb && cb() @@ -857,6 +867,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { signHttpAuth, nip04Encrypt, nip04Decrypt, + nip44Encrypt, + nip44Decrypt, startLogin: () => setOpenLoginDialog(true), checkLogin, signEvent, diff --git a/src/providers/NostrProvider/nip-07.signer.ts b/src/providers/NostrProvider/nip-07.signer.ts index 0736bd3..5718346 100644 --- a/src/providers/NostrProvider/nip-07.signer.ts +++ b/src/providers/NostrProvider/nip-07.signer.ts @@ -57,4 +57,24 @@ export class Nip07Signer implements ISigner { } return await this.signer.nip04.decrypt(pubkey, cipherText) } + + async nip44Encrypt(pubkey: string, plainText: string) { + if (!this.signer) { + throw new Error('Should call init() first') + } + if (!this.signer.nip44?.encrypt) { + throw new Error('The extension you are using does not support nip44 encryption') + } + return await this.signer.nip44.encrypt(pubkey, plainText) + } + + async nip44Decrypt(pubkey: string, cipherText: string) { + if (!this.signer) { + throw new Error('Should call init() first') + } + if (!this.signer.nip44?.decrypt) { + throw new Error('The extension you are using does not support nip44 decryption') + } + return await this.signer.nip44.decrypt(pubkey, cipherText) + } } diff --git a/src/providers/NostrProvider/nostrConnection.signer.ts b/src/providers/NostrProvider/nostrConnection.signer.ts index 3c79de6..556aa90 100644 --- a/src/providers/NostrProvider/nostrConnection.signer.ts +++ b/src/providers/NostrProvider/nostrConnection.signer.ts @@ -66,6 +66,20 @@ export class NostrConnectionSigner implements ISigner { return await this.signer.nip04Decrypt(pubkey, cipherText) } + async nip44Encrypt(pubkey: string, plainText: string) { + if (!this.signer) { + throw new Error('Not logged in') + } + return await this.signer.nip44Encrypt(pubkey, plainText) + } + + async nip44Decrypt(pubkey: string, cipherText: string) { + if (!this.signer) { + throw new Error('Not logged in') + } + return await this.signer.nip44Decrypt(pubkey, cipherText) + } + getClientSecretKey() { return bytesToHex(this.clientSecretKey) } diff --git a/src/providers/NostrProvider/npub.signer.ts b/src/providers/NostrProvider/npub.signer.ts index 68b32b3..502d651 100644 --- a/src/providers/NostrProvider/npub.signer.ts +++ b/src/providers/NostrProvider/npub.signer.ts @@ -31,4 +31,12 @@ export class NpubSigner implements ISigner { async nip04Decrypt(): Promise { throw new Error('Not logged in') } + + async nip44Encrypt(): Promise { + throw new Error('Not logged in') + } + + async nip44Decrypt(): Promise { + throw new Error('Not logged in') + } } diff --git a/src/providers/NostrProvider/nsec.signer.ts b/src/providers/NostrProvider/nsec.signer.ts index 138575a..936ac38 100644 --- a/src/providers/NostrProvider/nsec.signer.ts +++ b/src/providers/NostrProvider/nsec.signer.ts @@ -1,5 +1,6 @@ import { ISigner, TDraftEvent } from '@/types' import { finalizeEvent, getPublicKey as nGetPublicKey, nip04, nip19 } from 'nostr-tools' +import { v2 as nip44 } from 'nostr-tools/nip44' export class NsecSigner implements ISigner { private privkey: Uint8Array | null = null @@ -50,4 +51,20 @@ export class NsecSigner implements ISigner { } return nip04.decrypt(this.privkey, pubkey, cipherText) } + + async nip44Encrypt(pubkey: string, plainText: string) { + if (!this.privkey) { + throw new Error('Not logged in') + } + const conversationKey = nip44.utils.getConversationKey(this.privkey, pubkey) + return nip44.encrypt(plainText, conversationKey) + } + + async nip44Decrypt(pubkey: string, cipherText: string) { + if (!this.privkey) { + throw new Error('Not logged in') + } + const conversationKey = nip44.utils.getConversationKey(this.privkey, pubkey) + return nip44.decrypt(cipherText, conversationKey) + } } diff --git a/src/providers/PinnedUsersProvider.tsx b/src/providers/PinnedUsersProvider.tsx index 42f6899..093ca93 100644 --- a/src/providers/PinnedUsersProvider.tsx +++ b/src/providers/PinnedUsersProvider.tsx @@ -42,7 +42,8 @@ export function PinnedUsersProvider({ children }: { children: React.ReactNode }) updatePinnedUsersEvent, publish, nip04Decrypt, - nip04Encrypt + nip44Encrypt, + nip44Decrypt } = useNostr() const [privateTags, setPrivateTags] = useState([]) const pinnedPubkeySet = useMemo(() => { @@ -50,6 +51,23 @@ export function PinnedUsersProvider({ children }: { children: React.ReactNode }) return new Set(getPubkeysFromPTags(pinnedUsersEvent.tags.concat(privateTags))) }, [pinnedUsersEvent, privateTags]) + const migrateToNip44 = useCallback( + async (event: Event, privateTags: string[][]) => { + if (!accountPubkey) return + console.log('[PinnedUsers] Migrating from NIP-04 to NIP-44, privateTags count:', privateTags.length) + try { + const cipherText = await nip44Encrypt(accountPubkey, JSON.stringify(privateTags)) + const draftEvent = createPinnedUsersListDraftEvent(event.tags, cipherText) + const newEvent = await publish(draftEvent) + console.log('[PinnedUsers] Migration successful, new event id:', newEvent.id) + await updatePinnedUsersEvent(newEvent, privateTags) + } catch (error) { + console.error('[PinnedUsers] Failed to migrate to NIP-44', error) + } + }, + [accountPubkey, nip44Encrypt, publish, updatePinnedUsersEvent] + ) + useEffect(() => { const updatePrivateTags = async () => { if (!pinnedUsersEvent) { @@ -57,37 +75,48 @@ export function PinnedUsersProvider({ children }: { children: React.ReactNode }) return } - const privateTags = await getPrivateTags(pinnedUsersEvent).catch(() => { - return [] - }) + const { privateTags, wasNip04 } = await getPrivateTags(pinnedUsersEvent).catch(() => ({ + privateTags: [] as string[][], + wasNip04: false + })) setPrivateTags(privateTags) + + if (wasNip04 && privateTags.length > 0) { + migrateToNip44(pinnedUsersEvent, privateTags) + } } updatePrivateTags() }, [pinnedUsersEvent]) const getPrivateTags = useCallback( - async (event: Event) => { - if (!event.content) return [] + async (event: Event): Promise<{ privateTags: string[][]; wasNip04: boolean }> => { + if (!event.content) return { privateTags: [], wasNip04: false } try { + const wasNip04 = event.content.includes('?iv=') const storedPlainText = await indexedDb.getDecryptedContent(event.id) let plainText: string if (storedPlainText) { + console.log('[PinnedUsers] Using cached decrypted content for event', event.id) plainText = storedPlainText } else { - plainText = await nip04Decrypt(event.pubkey, event.content) + console.log('[PinnedUsers] Decrypting content with', wasNip04 ? 'NIP-04' : 'NIP-44', 'for event', event.id) + plainText = wasNip04 + ? await nip04Decrypt(event.pubkey, event.content) + : await nip44Decrypt(event.pubkey, event.content) await indexedDb.putDecryptedContent(event.id, plainText) } const privateTags = z.array(z.array(z.string())).parse(JSON.parse(plainText)) - return privateTags + console.log('[PinnedUsers] Decrypted privateTags count:', privateTags.length, 'wasNip04:', wasNip04) + return { privateTags, wasNip04 } } catch (error) { console.error('Failed to decrypt pinned users content', error) - return [] + return { privateTags: [], wasNip04: false } } }, - [nip04Decrypt] + [nip04Decrypt, nip44Decrypt] ) const isPinned = useCallback( @@ -129,7 +158,7 @@ export function PinnedUsersProvider({ children }: { children: React.ReactNode }) ) let newContent = pinnedUsersEvent.content if (newPrivateTags.length !== privateTags.length) { - newContent = await nip04Encrypt(pinnedUsersEvent.pubkey, JSON.stringify(newPrivateTags)) + newContent = await nip44Encrypt(pinnedUsersEvent.pubkey, JSON.stringify(newPrivateTags)) } const draftEvent = createPinnedUsersListDraftEvent(newTags, newContent) const newEvent = await publish(draftEvent) @@ -148,7 +177,7 @@ export function PinnedUsersProvider({ children }: { children: React.ReactNode }) publish, updatePinnedUsersEvent, privateTags, - nip04Encrypt + nip44Encrypt ] ) diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 1831554..225b422 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -91,6 +91,10 @@ export type TNip07 = { encrypt?: (pubkey: string, plainText: string) => Promise decrypt?: (pubkey: string, cipherText: string) => Promise } + nip44?: { + encrypt?: (pubkey: string, plainText: string) => Promise + decrypt?: (pubkey: string, cipherText: string) => Promise + } } export interface ISigner { @@ -98,6 +102,8 @@ export interface ISigner { signEvent: (draftEvent: TDraftEvent) => Promise nip04Encrypt: (pubkey: string, plainText: string) => Promise nip04Decrypt: (pubkey: string, cipherText: string) => Promise + nip44Encrypt: (pubkey: string, plainText: string) => Promise + nip44Decrypt: (pubkey: string, cipherText: string) => Promise } export type TSignerType = 'nsec' | 'nip-07' | 'bunker' | 'browser-nsec' | 'ncryptsec' | 'npub' From e740d199f1231fda97cdc919b986896061c7c7a5 Mon Sep 17 00:00:00 2001 From: rodbishop Date: Tue, 7 Apr 2026 13:58:24 +1000 Subject: [PATCH 10/10] feat: Add Shosho as external client for Live Events (#770) * feat: Add Shosho as an external client for Live Events Adds 'Shosho' to the list of external clients for kind 30311 (Live Event) events. The link format is `https://shosho.live/live/`. * refactor: Reorder clients for Live Events Based on feedback, this commit reorders the external clients for Live Events to place 'shosho' after 'zap.stream'. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/components/ClientSelect/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ClientSelect/index.tsx b/src/components/ClientSelect/index.tsx index 2efe939..06da471 100644 --- a/src/components/ClientSelect/index.tsx +++ b/src/components/ClientSelect/index.tsx @@ -65,6 +65,10 @@ const clients: Record string }> name: 'Pareto', getUrl: (id: string) => `https://pareto.space/a/${id}` }, + shosho: { + name: 'Shosho', + getUrl: (id: string) => `https://shosho.live/live/${id}` + }, njump: { name: 'Njump', getUrl: (id: string) => `https://njump.me/${id}` @@ -107,7 +111,7 @@ export default function ClientSelect({ case kinds.DraftLong: return ['yakihonne', 'coracle', 'habla', 'lumilumi', 'pareto', 'njump'] case kinds.LiveEvent: - return ['zapStream', 'nostrudel', 'njump'] + return ['zapStream', 'shosho', 'nostrudel', 'njump'] case kinds.Date: case kinds.Time: return ['coracle', 'njump']