diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index c1e2ddd..4e09fbf 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -1,5 +1,100 @@ +import { useEffect, useMemo, useState } from 'react'; +import { EventExplorerPage } from './pages/EventExplorerPage'; +import { NotificationPreferencesPage } from './pages/NotificationPreferencesPage'; + +const VALID_TAB_KEYS = ['explorer', 'preferences'] as const; + +type TabKey = (typeof VALID_TAB_KEYS)[number]; + +function parseActiveTab(search: string): TabKey { + const params = new URLSearchParams(search); + const tab = params.get('tab'); + return VALID_TAB_KEYS.includes(tab as TabKey) ? (tab as TabKey) : 'explorer'; +} + +export function App() { + const initialSearch = typeof window !== 'undefined' ? window.location.search : ''; + const [activeTab, setActiveTab] = useState(() => parseActiveTab(initialSearch)); + + useEffect(() => { + if (typeof window === 'undefined') { + return; + } + + const handlePopState = () => { + setActiveTab(parseActiveTab(window.location.search)); + }; + + window.addEventListener('popstate', handlePopState); + return () => window.removeEventListener('popstate', handlePopState); + }, []); + + useEffect(() => { + if (typeof window === 'undefined') { + return; + } + + const params = new URLSearchParams(window.location.search); + params.set('tab', activeTab); + window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`); + }, [activeTab]); + + const tabLabel = useMemo( + () => (activeTab === 'preferences' ? 'Notification Preferences' : 'Event Explorer'), + [activeTab] + ); + + return ( +
+
+
+

Notify Chain

+

{tabLabel}

+
+ + +
+ + {activeTab === 'preferences' ? : } + className={`nav-tab-btn ${activeTab === 'exports' ? 'nav-tab-btn--active' : ''}`} + onClick={() => setActiveTab('exports')} + > + Export Center + +
+ + + {activeTab === 'explorer' ? : } import { DeliveryHeatmap } from './components/DeliveryHeatmap'; import { ThemeToggle } from './components/ThemeToggle'; import { useTheme } from './hooks/useTheme'; diff --git a/dashboard/src/index.css b/dashboard/src/index.css index a59a356..40b9701 100644 --- a/dashboard/src/index.css +++ b/dashboard/src/index.css @@ -226,6 +226,439 @@ body { padding: 24px; } +.app__topbar { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 16px; + align-items: center; + margin-bottom: 24px; + padding-bottom: 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.app__brand { + display: flex; + flex-direction: column; + gap: 6px; +} + +.app__brand-eyebrow { + margin: 0; + color: #9aa0a6; + font-size: 0.85rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.app__nav { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.app__tab { + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.03); + color: inherit; + padding: 10px 16px; + border-radius: 999px; + cursor: pointer; + transition: border-color 0.15s ease, background 0.15s ease; +} + +.app__tab:hover, +.app__tab:focus-visible { + border-color: rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.06); + outline: none; +} + +.app__tab--active { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.25); +} + +.button { + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 10px; + padding: 10px 16px; + background: rgba(255, 255, 255, 0.04); + color: inherit; + cursor: pointer; + transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease; +} + +.button:hover, +.button:focus-visible { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); + outline: none; +} + +.button:disabled { + opacity: 0.55; + cursor: not-allowed; + transform: none; +} + +.button--primary { + background: #5b7dff; + border-color: #5b7dff; + color: #0b1224; +} + +.button--primary:hover, +.button--primary:focus-visible { + background: #4d6ded; + border-color: #4d6ded; +} + +.button--secondary { + background: rgba(255, 255, 255, 0.04); +} + +.button--full { + width: 100%; +} + +.notification-preferences-page { + display: grid; + gap: 24px; +} + +.notification-preferences__header, +.notification-preferences__panel-header { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 20px; + align-items: flex-start; +} + +.notification-preferences__eyebrow { + margin: 0; + color: #9aa0a6; + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.notification-preferences__lead { + margin: 10px 0 0; + max-width: 60rem; + color: #b0b8c2; +} + +.notification-preferences__header-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.notification-preferences__status-row { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: 12px; + padding: 16px; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 14px; + background: rgba(255, 255, 255, 0.03); +} + +.notification-preferences__status-text { + margin: 0; + color: #c2c8d3; + font-size: 0.95rem; +} + +.notification-preferences__pill { + display: inline-flex; + padding: 6px 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + color: #e8eaed; + font-size: 0.85rem; +} + +.notification-preferences__pill--error { + background: rgba(244, 180, 0, 0.18); + color: #f4b400; +} + +.notification-preferences__pill--success { + background: rgba(52, 199, 89, 0.16); + color: #b4f5a1; +} + +.notification-preferences__skeleton { + display: grid; + gap: 16px; +} + +.skeleton-block { + height: 80px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.04); + position: relative; + overflow: hidden; +} + +.skeleton-block::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent); + animation: shimmer 1.4s infinite; +} + +.skeleton-block--header { + height: 116px; +} + +.skeleton-block--row { + height: 64px; +} + +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } +} + +.notification-preferences__error-panel, +.notification-preferences__validation-panel { + padding: 18px; + border-radius: 14px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.notification-preferences__error-panel strong, +.notification-preferences__validation-panel strong { + display: block; + margin-bottom: 8px; +} + +.notification-preferences__validation-panel ul { + margin: 10px 0 0; + padding-left: 20px; + color: #c5cbd6; +} + +.notification-preferences__workspace { + display: grid; + grid-template-columns: minmax(0, 1.75fr) minmax(280px, 1fr); + gap: 24px; +} + +.notification-preferences__matrix-panel, +.notification-preferences__control-panel { + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 18px; + padding: 22px; + background: rgba(255, 255, 255, 0.02); +} + +.notification-preferences__matrix-note { + margin: 0; + color: #9aa0a6; + max-width: 38rem; +} + +.notification-preferences__matrix { + display: grid; + grid-template-columns: minmax(180px, 1fr) repeat(4, 88px); + gap: 10px; + margin-top: 20px; +} + +.matrix-cell { + min-height: 72px; + display: flex; + align-items: center; + justify-content: center; + padding: 14px; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); + text-align: center; +} + +.matrix-cell--corner { + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.matrix-cell--header { + font-weight: 600; + font-size: 0.95rem; +} + +.matrix-cell--category { + text-align: left; + display: grid; + gap: 8px; + align-items: flex-start; +} + +.matrix-cell--category p { + margin: 0; + color: #9aa0a6; + font-size: 0.9rem; + line-height: 1.4; +} + +.matrix-cell--toggle { + cursor: pointer; + position: relative; + justify-content: center; +} + +.matrix-cell--toggle input { + position: absolute; + opacity: 0; + width: 100%; + height: 100%; + margin: 0; + cursor: pointer; +} + +.matrix-toggle { + position: relative; + width: 44px; + height: 26px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + transition: background 0.15s ease; +} + +.matrix-toggle::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 20px; + height: 20px; + border-radius: 50%; + background: #0b0d12; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + transition: transform 0.15s ease; +} + +.matrix-cell--toggle input:checked + .matrix-toggle { + background: #5b7dff; +} + +.matrix-cell--toggle input:checked + .matrix-toggle::after { + transform: translateX(18px); +} + +.notification-preferences__control-panel { + display: grid; + gap: 18px; +} + +.notification-preferences__panel-block h2 { + margin: 0 0 8px; +} + +.notification-preferences__panel-block p { + margin: 0; + color: #9aa0a6; +} + +.notification-preferences__field { + display: grid; + gap: 10px; +} + +.notification-preferences__field label { + color: #c2c8d3; + font-size: 0.95rem; +} + +.notification-preferences__field input { + width: 100%; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 12px; + padding: 12px 14px; + background: #12151c; + color: inherit; +} + +.notification-preferences__field input:focus-visible { + outline: 2px solid rgba(91, 125, 255, 0.4); +} + +.notification-preferences__field-error { + margin: 0; + color: #f4b400; + font-size: 0.9rem; +} + +.notification-preferences__actions-panel { + display: grid; + gap: 12px; +} + +.notification-preferences__save-note { + margin: 0; + color: #9aa0a6; + font-size: 0.92rem; +} + +@media (max-width: 980px) { + .notification-preferences__workspace { + grid-template-columns: 1fr; + } + + .notification-preferences__matrix { + grid-template-columns: minmax(140px, 1fr) repeat(4, 1fr); + } +} + +@media (max-width: 720px) { + .app, + .events-page { + padding: 16px; + } + + .app__topbar { + flex-direction: column; + align-items: stretch; + } + + .app__nav { + justify-content: stretch; + } + + .app__tab { + flex: 1 1 auto; + } + + .notification-preferences__matrix { + grid-template-columns: 1fr; + } + + .matrix-cell--header, + .matrix-cell--toggle { + justify-content: flex-start; + } + + .notification-preferences__status-row { + flex-direction: column; + align-items: stretch; + } +} + .events-page__header h1 { margin: 0 0 8px; font-size: 1.75rem; diff --git a/dashboard/src/pages/NotificationPreferencesPage.tsx b/dashboard/src/pages/NotificationPreferencesPage.tsx new file mode 100644 index 0000000..b001146 --- /dev/null +++ b/dashboard/src/pages/NotificationPreferencesPage.tsx @@ -0,0 +1,316 @@ +import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; + +type ChannelKey = 'inApp' | 'email' | 'discord' | 'telegram'; +type CategoryKey = 'security' | 'governance' | 'system' | 'custom'; +type PreferencesRow = Record; +type PreferencesMatrix = Record; + +const channelDefinitions = [ + { key: 'inApp' as const, label: 'In-App' }, + { key: 'email' as const, label: 'Email' }, + { key: 'discord' as const, label: 'Discord' }, + { key: 'telegram' as const, label: 'Telegram' }, +]; + +const categoryDefinitions = [ + { key: 'security' as const, label: 'Security Alerts', description: 'Critical account and access events.' }, + { key: 'governance' as const, label: 'Protocol Governance', description: 'Voting, proposals and governance lifecycle updates.' }, + { key: 'system' as const, label: 'System Updates', description: 'Platform health, maintenance windows, and service notices.' }, + { key: 'custom' as const, label: 'Custom Triggers', description: 'User-defined automations and webhook-style notifications.' }, +]; + +const defaultPreferences: PreferencesMatrix = { + security: { inApp: true, email: true, discord: false, telegram: false }, + governance: { inApp: true, email: false, discord: true, telegram: false }, + system: { inApp: true, email: false, discord: false, telegram: true }, + custom: { inApp: false, email: false, discord: true, telegram: true }, +}; + +const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +const TELEGRAM_PATTERN = /^@[A-Za-z0-9_]{3,}$/; + +function validateEmail(value: string) { + return EMAIL_PATTERN.test(value.trim()); +} + +function validateTelegram(value: string) { + return TELEGRAM_PATTERN.test(value.trim()); +} + +export function NotificationPreferencesPage() { + const [preferences, setPreferences] = useState(defaultPreferences); + const [contactEmail, setContactEmail] = useState('recipient@example.com'); + const [telegramHandle, setTelegramHandle] = useState('@notifychain'); + const [isLoading, setIsLoading] = useState(true); + const [loadError, setLoadError] = useState(null); + const [simulateFailure, setSimulateFailure] = useState(false); + const [saveState, setSaveState] = useState<'idle' | 'saving' | 'saved'>('idle'); + + const errorCategories = useMemo( + () => + categoryDefinitions + .filter((category) => !Object.values(preferences[category.key]).some(Boolean)) + .map((category) => category.label), + [preferences] + ); + + const emailSelected = useMemo( + () => Object.values(preferences).some((row) => row.email), + [preferences] + ); + + const telegramSelected = useMemo( + () => Object.values(preferences).some((row) => row.telegram), + [preferences] + ); + + const emailInvalid = useMemo( + () => emailSelected && !validateEmail(contactEmail), + [contactEmail, emailSelected] + ); + + const telegramInvalid = useMemo( + () => telegramSelected && !validateTelegram(telegramHandle), + [telegramHandle, telegramSelected] + ); + + const canSave = useMemo( + () => !isLoading && !loadError && !emailInvalid && !telegramInvalid && errorCategories.length === 0, + [emailInvalid, errorCategories.length, isLoading, loadError, telegramInvalid] + ); + + const summaryText = useMemo(() => { + const channelCounts = channelDefinitions.map((channel) => { + const count = categoryDefinitions.reduce( + (total, category) => total + (preferences[category.key][channel.key] ? 1 : 0), + 0 + ); + return `${channel.label}: ${count}`; + }); + return `Active routing: ${channelCounts.join(' • ')}`; + }, [preferences]); + + useEffect(() => { + let cancelled = false; + setIsLoading(true); + setLoadError(null); + setSaveState('idle'); + + const timer = window.setTimeout(() => { + if (cancelled) { + return; + } + + if (simulateFailure) { + setLoadError('Unable to retrieve notification settings. Toggle the simulation or retry.'); + setIsLoading(false); + return; + } + + setPreferences(defaultPreferences); + setLoadError(null); + setIsLoading(false); + }, 780); + + return () => { + cancelled = true; + window.clearTimeout(timer); + }; + }, [simulateFailure]); + + const handleTogglePreference = useCallback( + (categoryKey: CategoryKey, channelKey: ChannelKey) => { + setPreferences((current) => ({ + ...current, + [categoryKey]: { + ...current[categoryKey], + [channelKey]: !current[categoryKey][channelKey], + }, + })); + setSaveState('idle'); + }, + [] + ); + + const handleSave = useCallback(() => { + if (!canSave) { + return; + } + + setSaveState('saving'); + window.setTimeout(() => { + setSaveState('saved'); + }, 600); + }, [canSave]); + + const handleRetry = useCallback(() => { + setSimulateFailure(false); + setIsLoading(true); + setLoadError(null); + }, []); + + return ( +
+
+
+

Notification Preferences

+

Recipient preference control panel

+

+ Toggle notification channels for each recipient category and validate contact fields before saving. +

+
+
+ + +
+
+ +
+

{summaryText}

+ {isLoading && Loading…} + {loadError && Error} + {saveState === 'saved' && Saved} +
+ + {isLoading ? ( +
+
+
+
+
+
+ ) : loadError ? ( +
+ Unable to load preferences. +

Use the button above to retry or disable the simulated error boundary.

+
+ ) : ( +
+
+
+
+

Delivery matrix

+

Map channels to notification categories with fast toggles and immediate validation feedback.

+
+

+ Categories must route through at least one channel. Email and Telegram require valid contact details. +

+
+ +
+
Categories / Channels
+ {channelDefinitions.map((channel) => ( +
+ {channel.label} +
+ ))} + + {categoryDefinitions.map((category) => ( + +
+
{category.label}
+

{category.description}

+
+ {channelDefinitions.map((channel) => { + const fieldId = `pref-${category.key}-${channel.key}`; + return ( + + ); + })} +
+ ))} +
+
+ + +
+ )} +
+ ); +} diff --git a/listener/package-lock.json b/listener/package-lock.json index d2b5404..4aa27eb 100644 --- a/listener/package-lock.json +++ b/listener/package-lock.json @@ -29,7 +29,7 @@ "jest": "^29.7.0", "ts-jest": "^29.4.11", "ts-node": "^10.9.2", - "typescript": "^6.0.3" + "typescript": "~5.4.5" } }, "node_modules/@babel/code-frame": { @@ -790,6 +790,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "version": "3.15.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", @@ -1007,6 +1010,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@jest/core/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + } + }, + "node_modules/@jest/core/node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/core/node_modules/jest-regex-util": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", @@ -1017,6 +1035,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/create-cache-key-function": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-30.4.1.tgz", + "integrity": "sha512-R+xGEtzA95NIsvpXJSROG4t01956dDOt17KpamguY4XOnGvdHNFFXE7Er0C1OAsRjOwiIxpKqOvGlznIGZIQlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.4.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/create-cache-key-function": { "version": "30.4.1", "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-30.4.1.tgz", @@ -1509,6 +1540,45 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/transform/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2859,6 +2929,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", "version": "2.10.40", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", @@ -3478,6 +3551,51 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3692,6 +3810,9 @@ } }, "node_modules/electron-to-chromium": { + "version": "1.5.378", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.378.tgz", + "integrity": "sha512-VinvOAuuPmdD1guEgGv5f2Qp7/vlfqOrUOMYNnOD4wj3pit8kRsQHzfIf6teyUGWo15Tg5+bOJaRunvyltpVWQ==", "version": "1.5.380", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.380.tgz", "integrity": "sha512-W6d5AbuEoRayO447cqrg6lKJIlscgRnnxOZl/08kfV71BQDoEBC7Wwis68z87LjyK6f4kWyTaubuDbhHKrZkbA==", @@ -5826,6 +5947,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-runner": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", @@ -6310,6 +6441,9 @@ "license": "MIT" }, "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", @@ -6920,6 +7054,9 @@ "license": "MIT" }, "node_modules/node-releases": { + "version": "2.0.49", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.49.tgz", + "integrity": "sha512-f06bl1D+8ZDkn2oOQQKAh5/otFWqVnM1Q5oerA8Pex7UfT66Tx4IPHIqVVFKqFT3FUtaDstdgkM7yT7JWhqxfw==", "version": "2.0.50", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", @@ -8446,9 +8583,9 @@ } }, "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/listener/package.json b/listener/package.json index 884ad85..11ff0f8 100644 --- a/listener/package.json +++ b/listener/package.json @@ -26,18 +26,18 @@ "winston": "^3.19.0" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^6.10.0", - "@typescript-eslint/parser": "^6.10.0", - "eslint": "^8.46.0", "@swc/core": "^1.15.41", "@swc/jest": "^0.2.39", "@types/jest": "^29.5.14", "@types/node": "^25.9.3", "@types/node-cache": "^4.1.3", "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "eslint": "^8.46.0", "jest": "^29.7.0", "ts-jest": "^29.4.11", "ts-node": "^10.9.2", - "typescript": "^6.0.3" + "typescript": "~5.4.5" } }