diff --git a/donation-replay.js b/donation-replay.js index adbb9f5..412e98a 100644 --- a/donation-replay.js +++ b/donation-replay.js @@ -21,13 +21,6 @@ window.replayDonation = (amount = '$25', username = 'GenerousDonor', message = ' timestamp: new Date().toISOString() }; - // Create the pusher event structure - const pusherEvent = { - event: 'App\\Events\\DonationEvent', // Using generic donation event - data: JSON.stringify(donationEventData), - channel: 'chatrooms.69188411' // Your current chatroom - }; - console.log('šŸ“¤ Dispatching donation event:', donationEventData); // Dispatch the event through the SharedKickPusher @@ -65,12 +58,6 @@ window.replayGiftSub = (gifterUsername = 'GiftMaster', recipientUsername = 'Luck gifter_total: total }; - const pusherEvent = { - event: 'App\\Events\\GiftedSubscriptionsEvent', - data: JSON.stringify(giftSubEventData), - channel: 'chatrooms.69188411' - }; - console.log('šŸ“¤ Dispatching gift subscription event:', giftSubEventData); try { diff --git a/eslint.config.js b/eslint.config.js index fb6551a..475a0b1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -89,7 +89,9 @@ module.exports = [ react: { version: "detect" }, }, rules: { - // Keep defaults; enable stricter rules later as desired. + // Essential React rules to properly detect JSX usage + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", }, }, ]; diff --git a/package-lock.json b/package-lock.json index c8057b2..8c4724b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4774,13 +4774,13 @@ } }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -7497,14 +7497,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -12367,9 +12368,9 @@ } }, "node_modules/vite": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", - "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==", + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", + "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/scripts/event-collection/event-analyzer.js b/scripts/event-collection/event-analyzer.js index c126e31..0d5b26b 100644 --- a/scripts/event-collection/event-analyzer.js +++ b/scripts/event-collection/event-analyzer.js @@ -1,7 +1,6 @@ #!/usr/bin/env node const fs = require('fs'); -const path = require('path'); const readline = require('readline'); /** @@ -68,7 +67,7 @@ class KickEventAnalyzer { console.log(` Processed ${processedCount} events...`); } - } catch (error) { + } catch (_error) { console.warn('Skipping malformed line:', line.substring(0, 100)); } } diff --git a/scripts/event-collection/event-collector.js b/scripts/event-collection/event-collector.js index 1c8c819..53a0782 100644 --- a/scripts/event-collection/event-collector.js +++ b/scripts/event-collection/event-collector.js @@ -3,7 +3,6 @@ const WebSocket = require('ws'); const https = require('https'); const fs = require('fs'); -const path = require('path'); /** * Kick Event Collector @@ -129,7 +128,7 @@ class KickEventCollector { console.log(`Loaded ${this.channelData.size} IDs to monitor`); // Print the IDs we'll monitor - for (const [slug, data] of this.channelData) { + for (const [, data] of this.channelData) { console.log(`āœ“ ID ${data.id} - will try both streamer and chatroom patterns`); } console.log(''); @@ -214,7 +213,7 @@ class KickEventCollector { // For each ID, subscribe to both streamer channel patterns AND chatroom patterns // since we don't know which type each ID is - for (const [slug, data] of this.channelData) { + for (const [, data] of this.channelData) { const channels = [ // Streamer channel patterns (in case this ID is a streamer ID) `channel_${data.id}`, diff --git a/scripts/test-telemetry-settings.js b/scripts/test-telemetry-settings.js index e13703d..65aab96 100755 --- a/scripts/test-telemetry-settings.js +++ b/scripts/test-telemetry-settings.js @@ -13,81 +13,115 @@ const Store = require('electron-store'); // Test configuration const store = new Store(); -async function testTelemetryDisabled() { - console.log('\n=== TEST 1: Telemetry DISABLED ==='); - - // Disable telemetry - store.set('telemetry', { enabled: false }); - console.log('āœ“ Set telemetry.enabled = false'); - - // Start the app and capture logs - console.log('Starting app with telemetry disabled...'); - const proc = spawn('npm', ['run', 'dev'], { +const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +const RUNTIME_WAIT_MS = Number(process.env.TELEMETRY_TEST_RUNTIME_MS || 5000); +const SHUTDOWN_TIMEOUT_MS = Number(process.env.TELEMETRY_TEST_SHUTDOWN_TIMEOUT_MS || 2000); +const SKIP_RUNTIME_TESTS = process.env.SKIP_TELEMETRY_RUNTIME === '1'; +const originalTelemetrySetting = store.get('telemetry'); + +function restoreTelemetrySetting() { + if (typeof originalTelemetrySetting === 'undefined') { + store.delete('telemetry'); + console.log('\nRestored telemetry setting: deleted (was undefined)'); + } else { + store.set('telemetry', originalTelemetrySetting); + console.log(`\nRestored telemetry.enabled = ${originalTelemetrySetting?.enabled}`); + } +} + +async function runAppAndCollectLogs(label) { + console.log(`Starting app with telemetry ${label}...`); + + const proc = spawn(npmCommand, ['run', 'dev'], { + cwd: path.join(__dirname, '..'), env: { ...process.env, ELECTRON_ENABLE_LOGGING: '1' }, stdio: 'pipe' }); - + let logs = ''; - proc.stdout.on('data', (data) => logs += data.toString()); - proc.stderr.on('data', (data) => logs += data.toString()); - - // Wait 5 seconds then kill - await new Promise(resolve => setTimeout(resolve, 5000)); - proc.kill(); - - // Check logs for telemetry behavior + let spawnError; + + proc.stdout.on('data', (data) => { + logs += data.toString(); + }); + proc.stderr.on('data', (data) => { + logs += data.toString(); + }); + proc.on('error', (err) => { + spawnError = err; + }); + + await new Promise((resolve) => setTimeout(resolve, RUNTIME_WAIT_MS)); + proc.kill('SIGTERM'); + + await new Promise((resolve) => { + const fallback = setTimeout(() => { + if (proc.exitCode === null) { + proc.kill('SIGKILL'); + } + resolve(); + }, SHUTDOWN_TIMEOUT_MS); + + proc.once('close', () => { + clearTimeout(fallback); + resolve(); + }); + }); + + if (spawnError) { + throw spawnError; + } + + return logs; +} + +async function testTelemetryDisabled() { + console.log('\n=== TEST 1: Telemetry DISABLED ==='); + + store.set('telemetry', { enabled: false }); + console.log('āœ“ Set telemetry.enabled = false'); + + const logs = await runAppAndCollectLogs('disabled'); + const checks = { 'Telemetry disabled by user settings': logs.includes('Telemetry disabled by user settings'), 'SDK not initialized': logs.includes('skipping initialization') || logs.includes('SDK creation skipped'), 'No OTLP connection': !logs.includes('OTLP') || !logs.includes('exporter'), 'No spans created': !logs.includes('span started') && !logs.includes('websocket.connect') }; - + console.log('\nResults:'); for (const [check, passed] of Object.entries(checks)) { console.log(` ${passed ? 'āœ“' : 'āœ—'} ${check}`); } - - return Object.values(checks).every(v => v); + + return Object.values(checks).every(Boolean); } async function testTelemetryEnabled() { console.log('\n=== TEST 2: Telemetry ENABLED ==='); - - // Enable telemetry + store.set('telemetry', { enabled: true }); console.log('āœ“ Set telemetry.enabled = true'); - - // Start the app and capture logs - console.log('Starting app with telemetry enabled...'); - const proc = spawn('npm', ['run', 'dev'], { - env: { ...process.env, ELECTRON_ENABLE_LOGGING: '1' }, - stdio: 'pipe' - }); - - let logs = ''; - proc.stdout.on('data', (data) => logs += data.toString()); - proc.stderr.on('data', (data) => logs += data.toString()); - - // Wait 5 seconds then kill - await new Promise(resolve => setTimeout(resolve, 5000)); - proc.kill(); - - // Check logs for telemetry behavior + + const logs = await runAppAndCollectLogs('enabled'); + const checks = { 'SDK initialized': logs.includes('NodeSDK') || logs.includes('Web tracer initialized'), 'OTLP configured': logs.includes('OTLP') || logs.includes('exporter'), 'Instrumentation active': logs.includes('instrumentation') || logs.includes('WebSocket instrumentation installed') }; - + console.log('\nResults:'); for (const [check, passed] of Object.entries(checks)) { console.log(` ${passed ? 'āœ“' : 'āœ—'} ${check}`); } - - return Object.values(checks).every(v => v); + + return Object.values(checks).every(Boolean); } + + async function testIPCHandlers() { console.log('\n=== TEST 3: IPC Handlers Check Settings ==='); @@ -122,8 +156,12 @@ async function runAllTests() { try { // Note: These tests require the app to be built - // results.push(await testTelemetryDisabled()); - // results.push(await testTelemetryEnabled()); + if (SKIP_RUNTIME_TESTS) { + console.log('\n[warning] Skipping runtime telemetry tests (set SKIP_TELEMETRY_RUNTIME=1 to suppress this warning intentionally).'); + } else { + results.push(await testTelemetryDisabled()); + results.push(await testTelemetryEnabled()); + } results.push(await testIPCHandlers()); console.log('\n' + '='.repeat(50)); @@ -141,13 +179,11 @@ async function runAllTests() { console.log('\nāœ— Some tests failed. Review the implementation.'); } - // Restore original setting - const originalSetting = store.get('telemetry', { enabled: false }); - console.log(`\nRestored telemetry.enabled = ${originalSetting.enabled}`); - } catch (error) { console.error('Test error:', error); - process.exit(1); + process.exitCode = 1; + } finally { + restoreTelemetrySetting(); } } diff --git a/src/main/index.js b/src/main/index.js index 760f26c..7b643d4 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -74,7 +74,6 @@ const genRequestId = () => { }; // Initialize telemetry early if enabled -let initTelemetry = null; let shutdownTelemetry = null; let isTelemetryEnabled = () => false; // Default fallback @@ -2196,7 +2195,7 @@ const findStreamlink = () => { if (result.status === 0) { return path; } - } catch (pathError) { + } catch (_pathError) { // Command not found in PATH, continue continue; } @@ -2206,7 +2205,7 @@ const findStreamlink = () => { return path; } } - } catch (error) { + } catch (_error) { // Continue checking other paths continue; } diff --git a/src/preload/index.js b/src/preload/index.js index a7e23b5..fb75ac2 100644 --- a/src/preload/index.js +++ b/src/preload/index.js @@ -1,4 +1,4 @@ -import { contextBridge, ipcRenderer, shell, session } from "electron"; +import { contextBridge, ipcRenderer, shell } from "electron"; import { electronAPI } from "@electron-toolkit/preload"; import { sendMessageToChannel, @@ -10,7 +10,6 @@ import { getUserChatroomInfo, getSelfChatroomInfo, getSilencedUsers, - getLinkThumbnail, getInitialChatroomMessages, getInitialPollInfo, getSubmitPollVote, @@ -32,8 +31,6 @@ import { // Kick Auth for Events getKickAuthForEvents, - getUpdateTitle, - getClearChatroom, } from "../../utils/services/kick/kickAPI"; import { getUserStvProfile, getChannelEmotes } from "../../utils/services/seventv/stvAPI"; diff --git a/src/renderer/src/components/Chat/Input/EmoteDialogs.jsx b/src/renderer/src/components/Chat/Input/EmoteDialogs.jsx index a719141..9420207 100644 --- a/src/renderer/src/components/Chat/Input/EmoteDialogs.jsx +++ b/src/renderer/src/components/Chat/Input/EmoteDialogs.jsx @@ -6,8 +6,6 @@ import STVLogo from "../../../assets/logos/stvLogo.svg?asset"; import { CaretDownIcon, GlobeIcon, LockIcon, UserIcon } from "@phosphor-icons/react"; import useClickOutside from "../../../utils/useClickOutside"; import KickLogoIcon from "../../../assets/logos/kickLogoIcon.svg?asset"; -import useChatStore from "../../../providers/ChatProvider"; -import { useShallow } from "zustand/react/shallow"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../Shared/Tooltip"; import { useAccessibleKickEmotes } from "./useAccessibleKickEmotes"; import { useAllStvEmotes } from "../hooks/useAllStvEmotes"; diff --git a/src/renderer/src/components/Chat/Input/index.jsx b/src/renderer/src/components/Chat/Input/index.jsx index ec26464..cd28307 100644 --- a/src/renderer/src/components/Chat/Input/index.jsx +++ b/src/renderer/src/components/Chat/Input/index.jsx @@ -1037,7 +1037,7 @@ const ReplyCapture = ({ chatroomId, setReplyData, chatInputRef }) => { has_sender: !!data.sender }); } - } catch (e) { + } catch (_e) { // Telemetry recording failed, but don't break the flow } })(); diff --git a/src/renderer/src/components/Chat/Pin.jsx b/src/renderer/src/components/Chat/Pin.jsx index 3496b98..fa4746d 100644 --- a/src/renderer/src/components/Chat/Pin.jsx +++ b/src/renderer/src/components/Chat/Pin.jsx @@ -13,15 +13,29 @@ const Pin = memo( const pinnedBy = pinDetails?.pinned_by || pinDetails?.pinnedBy; const originalSender = pinDetails?.message?.sender; - const getUnpinMessage = async () => { - if (!canModerate) return; - const response = await window.app.kick.getUnpinMessage(chatroomName); + const handleUnpinClick = async () => { + if (!canModerate || !chatroomName) return; - if (response?.code === 201) { - setShowPinnedMessage(false); + try { + const response = await window?.app?.kick?.getUnpinMessage?.(chatroomName); + + if (response?.code === 201) { + setShowPinnedMessage(false); + } else if (response?.ok === false || response?.code >= 400) { + console.warn("[Chat][Pin] Unpin request failed", { + chatroomName, + response, + }); + } + } catch (error) { + console.error("[Chat][Pin] Failed to unpin message", { + chatroomName, + error: error?.message || error, + }); } }; + return (
@@ -43,7 +57,11 @@ const Pin = memo( /> {canModerate && ( - )} diff --git a/src/renderer/src/components/Chat/Poll.jsx b/src/renderer/src/components/Chat/Poll.jsx index 9d93901..b1b10b1 100644 --- a/src/renderer/src/components/Chat/Poll.jsx +++ b/src/renderer/src/components/Chat/Poll.jsx @@ -32,18 +32,16 @@ const Poll = memo( if (!pollDetails?.title) return null; - const [poll, setPoll] = useState(pollDetails); const [percentRemaining, setPercentRemaining] = useState(0); const [selectedOption, setSelectedOption] = useState(pollDetails?.voted_option_id || null); - const [hasVoted, setHasVoted] = useState(pollDetails?.has_voted || false); + const [hasVoted] = useState(pollDetails?.has_voted || false); const [pollWinner, setPollWinner] = useState(null); const [isPollExpanded, setIsPollExpanded] = useState(false); const isPollEnded = pollDetails?.remaining <= 0; const currentTime = Date.now(); - let startTime = pollDetails?.start_time || currentTime; let endTime = null; const intervalTimerRef = useRef(null); @@ -138,7 +136,7 @@ const Poll = memo( ); } } - } catch (error) {} + } catch (_error) {} return; } @@ -200,7 +198,6 @@ const Poll = memo( const hasVoted = pollDetails?.voted_option_id === option.id; // Option has Won - const isSelected = selectedOption === option.id; const isWinner = pollWinner === option.id && isPollEnded; return ( diff --git a/src/renderer/src/components/Dialogs/Mentions.jsx b/src/renderer/src/components/Dialogs/Mentions.jsx index bcf4d0f..7bfd74e 100644 --- a/src/renderer/src/components/Dialogs/Mentions.jsx +++ b/src/renderer/src/components/Dialogs/Mentions.jsx @@ -19,10 +19,6 @@ const Mentions = ({ setActiveChatroom, chatroomId, showCloseButton = false, onCl mentions, getAllMentions, getChatroomMentions, - getUnreadMentionCount, - getChatroomUnreadMentionCount, - markAllMentionsAsRead, - markChatroomMentionsAsRead, clearAllMentions, clearChatroomMentions, chatrooms, @@ -47,17 +43,7 @@ const Mentions = ({ setActiveChatroom, chatroomId, showCloseButton = false, onCl return allMentions; }, [selectedChatroom, mentions, getChatroomMentions]); - const unreadCount = useMemo(() => { - return selectedChatroom === "all" ? getUnreadMentionCount() : getChatroomUnreadMentionCount(selectedChatroom); - }, [selectedChatroom, getUnreadMentionCount, getChatroomUnreadMentionCount]); - const handleMarkAllAsRead = () => { - if (selectedChatroom === "all") { - markAllMentionsAsRead(); - } else { - markChatroomMentionsAsRead(selectedChatroom); - } - }; const handleClearAll = () => { if (selectedChatroom === "all") { diff --git a/src/renderer/src/components/Dialogs/Settings.jsx b/src/renderer/src/components/Dialogs/Settings.jsx index d8d21e1..1f0a2de 100644 --- a/src/renderer/src/components/Dialogs/Settings.jsx +++ b/src/renderer/src/components/Dialogs/Settings.jsx @@ -16,7 +16,7 @@ import kickLogoIcon from "../../assets/logos/kickLogoIcon.svg?asset"; const Settings = () => { const { updateSettings, settings } = useSettings(); const [activeSection, setActiveSection] = useState("general"); - const [userData, setUserData] = useState(null); + const [, setUserData] = useState(null); const [openColorPicker, setOpenColorPicker] = useState(false); const [settingsData, setSettingsData] = useState(null); const [appInfo, setAppInfo] = useState(null); diff --git a/src/renderer/src/components/Dialogs/User.jsx b/src/renderer/src/components/Dialogs/User.jsx index f7488d9..e75eb8e 100644 --- a/src/renderer/src/components/Dialogs/User.jsx +++ b/src/renderer/src/components/Dialogs/User.jsx @@ -4,7 +4,7 @@ import { userKickTalkBadges } from "@utils/kickTalkBadges"; import clsx from "clsx"; import Message from "../Messages/Message"; import { PushPinIcon, ArrowUpRightIcon, CopyIcon, GavelIcon, UserPlusIcon, CheckIcon } from "@phosphor-icons/react"; -import { KickBadges, KickTalkBadges, StvBadges } from "../Cosmetics/Badges"; +import { KickBadges, StvBadges } from "../Cosmetics/Badges"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../Shared/Tooltip"; // TODO: Add Kick Talk Badges to UserIcon Dialog @@ -25,7 +25,7 @@ const User = () => { const kickUsername = localStorage.getItem("kickUsername"); - const [silencedUsers, setSilencedUsers] = useState(() => { + const [, setSilencedUsers] = useState(() => { try { return JSON.parse(localStorage.getItem("silencedUsers")) || { data: [] }; } catch (e) { diff --git a/src/renderer/src/components/Messages/ModActions.jsx b/src/renderer/src/components/Messages/ModActions.jsx index f2cee84..69862b9 100644 --- a/src/renderer/src/components/Messages/ModActions.jsx +++ b/src/renderer/src/components/Messages/ModActions.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback, useRef } from "react"; +import { useState, useCallback, useRef } from "react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../Shared/Tooltip"; import { Slider } from "../Shared/Slider"; import { GavelIcon, ClockUserIcon, UserPlusIcon } from "@phosphor-icons/react"; diff --git a/src/renderer/src/components/Messages/ReplyMessage.jsx b/src/renderer/src/components/Messages/ReplyMessage.jsx index a4c03b1..460b489 100644 --- a/src/renderer/src/components/Messages/ReplyMessage.jsx +++ b/src/renderer/src/components/Messages/ReplyMessage.jsx @@ -3,7 +3,6 @@ import RegularMessage from "./RegularMessage"; import { ArrowBendUpRightIcon } from "@phosphor-icons/react"; import useChatStore from "../../providers/ChatProvider"; import { useShallow } from "zustand/shallow"; -import { memo, useMemo } from "react"; const ReplyMessage = ({ message, diff --git a/src/renderer/src/providers/ChatProvider.jsx b/src/renderer/src/providers/ChatProvider.jsx index 5b48457..c6cefcd 100644 --- a/src/renderer/src/providers/ChatProvider.jsx +++ b/src/renderer/src/providers/ChatProvider.jsx @@ -377,7 +377,7 @@ const getInitialState = () => { const savedPersonalEmoteSets = JSON.parse(localStorage.getItem("stvPersonalEmoteSets")) || []; const chatrooms = savedChatrooms.map((room) => { - const { pinDetails = null, pollDetails = null, chatters = [], ...rest } = room; + const { pinDetails, pollDetails, chatters, ...rest } = room; return rest; }); @@ -692,7 +692,7 @@ const useChatStore = create((set, get) => ({ 'message.content_length': content?.length || 0 }); - const errMsg = chatroomErrorHandler(error); + const ___ = chatroomErrorHandler(error); // Find and mark the optimistic message as failed const messages = get().messages[chatroomId] || []; @@ -824,7 +824,7 @@ const useChatStore = create((set, get) => ({ 'reply.original_message_id': metadata.original_message?.id }); - const errMsg = chatroomErrorHandler(error); + const ___ = chatroomErrorHandler(error); // Find and mark the optimistic reply as failed const messages = get().messages[chatroomId] || []; @@ -907,12 +907,13 @@ const useChatStore = create((set, get) => ({ case "cosmetic.create": useCosmeticsStore?.getState()?.addCosmetics(body); break; - case "entitlement.create": + case "entitlement.create": { const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username; const transformedUsername = username?.replaceAll("-", "_").toLowerCase(); useCosmeticsStore?.getState()?.addUserStyle(transformedUsername, body); break; + } default: break; @@ -970,7 +971,6 @@ const useChatStore = create((set, get) => ({ // Fetch fresh channel info on connect to sync current chatroom state // This ensures we get up-to-date mode information (emote mode, slow mode, etc.) - const wasConnectedBefore = __wsConnectedOnce.get(chatroom.id) === true; __wsConnectedOnce.set(chatroom.id, true); // Always fetch current state - both for first connect and reconnects @@ -1097,7 +1097,7 @@ const useChatStore = create((set, get) => ({ const parsedEvent = JSON.parse(event.detail.data); switch (event.detail.event) { - case "App\\Events\\ChatMessageEvent": + case "App\\Events\\ChatMessageEvent": { // Add user to chatters list if they're not already in there get().addChatter(chatroom.id, parsedEvent?.sender); @@ -1186,6 +1186,7 @@ const useChatStore = create((set, get) => ({ } break; + } case "App\\Events\\MessageDeletedEvent": get().handleMessageDelete(chatroom.id, parsedEvent.message.id); break; diff --git a/src/renderer/src/telemetry/webTracing.js b/src/renderer/src/telemetry/webTracing.js index 770ecdf..fd1e56f 100644 --- a/src/renderer/src/telemetry/webTracing.js +++ b/src/renderer/src/telemetry/webTracing.js @@ -5,7 +5,7 @@ import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; // In sdk-trace-web v2, addSpanProcessor is not available; use SimpleSpanProcessor from sdk-trace-base -import { SimpleSpanProcessor, AlwaysOnSampler } from '@opentelemetry/sdk-trace-base'; +import { AlwaysOnSampler } from '@opentelemetry/sdk-trace-base'; import { context, trace } from '@opentelemetry/api'; // Check if telemetry is enabled before doing anything @@ -457,7 +457,8 @@ if (!window.__KT_RENDERER_OTEL_INITIALIZED__ && telemetryEnabled) { return origFetch(input, init); }; // Sanitize AnyValue tree for collector limits and JSON mapping correctness - const sanitizeAnyValue = (av) => { + // eslint-disable-next-line no-unused-vars -- Function is used recursively within its own definition + const ___sanitizeAnyValue = (av) => { try { if (!av || typeof av !== 'object') return { stringValue: '' }; if (Object.prototype.hasOwnProperty.call(av, 'stringValue')) { @@ -477,13 +478,13 @@ if (!window.__KT_RENDERER_OTEL_INITIALIZED__ && telemetryEnabled) { } if (Object.prototype.hasOwnProperty.call(av, 'arrayValue')) { const vals = Array.isArray(av.arrayValue?.values) ? av.arrayValue.values : []; - const capped = vals.slice(0, 64).map(sanitizeAnyValue); + const capped = vals.slice(0, 64).map(___sanitizeAnyValue); return { arrayValue: { values: capped } }; } if (Object.prototype.hasOwnProperty.call(av, 'kvlistValue')) { // Not used by us; be safe const list = Array.isArray(av.kvlistValue?.values) ? av.kvlistValue.values : []; - const sanitized = list.slice(0, 64).map((kv) => ({ key: String(kv.key).slice(0,128), value: sanitizeAnyValue(kv.value) })); + const sanitized = list.slice(0, 64).map((kv) => ({ key: String(kv.key).slice(0,128), value: ___sanitizeAnyValue(kv.value) })); return { kvlistValue: { values: sanitized } }; } if (Object.prototype.hasOwnProperty.call(av, 'bytesValue')) { @@ -495,20 +496,6 @@ if (!window.__KT_RENDERER_OTEL_INITIALIZED__ && telemetryEnabled) { return { stringValue: '' }; } }; - const sanitizeAttributes = (kvs) => { - try { - const out = []; - const seen = new Set(); - for (const kv of Array.isArray(kvs) ? kvs : []) { - const key = String(kv?.key ?? '').slice(0, 128); - if (!key || seen.has(key)) continue; - seen.add(key); - out.push({ key, value: sanitizeAnyValue(kv?.value) }); - if (out.length >= 128) break; // cap attribute count - } - return out; - } catch { return []; } - }; } else { console.warn('[Renderer OTEL]: window.fetch not available; fetch interceptor skipped'); } @@ -783,7 +770,7 @@ if (!window.__KT_RENDERER_OTEL_INITIALIZED__ && telemetryEnabled) { } }; // Local sanitizers to ensure availability within _toOtlpJson scope - const sanitizeAnyValue = (av) => { + const ___sanitizeAnyValue = (av) => { try { if (!av || typeof av !== 'object') return { stringValue: '' }; if (Object.prototype.hasOwnProperty.call(av, 'stringValue')) { @@ -802,12 +789,12 @@ if (!window.__KT_RENDERER_OTEL_INITIALIZED__ && telemetryEnabled) { } if (Object.prototype.hasOwnProperty.call(av, 'arrayValue')) { const vals = Array.isArray(av.arrayValue?.values) ? av.arrayValue.values : []; - const capped = vals.slice(0, 64).map(sanitizeAnyValue); + const capped = vals.slice(0, 64).map(___sanitizeAnyValue); return { arrayValue: { values: capped } }; } if (Object.prototype.hasOwnProperty.call(av, 'kvlistValue')) { const list = Array.isArray(av.kvlistValue?.values) ? av.kvlistValue.values : []; - const sanitized = list.slice(0, 64).map((kv) => ({ key: String(kv.key).slice(0,128), value: sanitizeAnyValue(kv.value) })); + const sanitized = list.slice(0, 64).map((kv) => ({ key: String(kv.key).slice(0,128), value: ___sanitizeAnyValue(kv.value) })); return { kvlistValue: { values: sanitized } }; } if (Object.prototype.hasOwnProperty.call(av, 'bytesValue')) { @@ -818,7 +805,7 @@ if (!window.__KT_RENDERER_OTEL_INITIALIZED__ && telemetryEnabled) { return { stringValue: '' }; } }; - const sanitizeAttributes = (kvs) => { + const ___sanitizeAttributes = (kvs) => { try { const out = []; const seen = new Set(); @@ -826,7 +813,7 @@ if (!window.__KT_RENDERER_OTEL_INITIALIZED__ && telemetryEnabled) { const key = String(kv?.key ?? '').slice(0, 128); if (!key || seen.has(key)) continue; seen.add(key); - out.push({ key, value: sanitizeAnyValue(kv?.value) }); + out.push({ key, value: ___sanitizeAnyValue(kv?.value) }); if (out.length >= 128) break; } return out; @@ -853,7 +840,7 @@ if (!window.__KT_RENDERER_OTEL_INITIALIZED__ && telemetryEnabled) { } } } catch {} - attrs = sanitizeAttributes(attrs); + attrs = ___sanitizeAttributes(attrs); // Convert times let startNs = toEpochNanos(s.startTime); let endNs = toEpochNanos(s.endTime); @@ -1292,7 +1279,6 @@ if (!window.__KT_RENDERER_OTEL_INITIALIZED__ && telemetryEnabled) { 'service.namespace': 'kicktalk' } }); - const activeCtx = trace.setSpan(context.active(), parent); registerInstrumentations({ instrumentations: [ @@ -1959,112 +1945,121 @@ try { writable: false, value: async () => { const testId = Math.random().toString(36).substring(2, 8); - const nowNs = BigInt(Date.now()) * 1000000n; // Convert to nanoseconds - const traceId = testId.padEnd(32, '0'); // Ensure 32 chars - const spanId = testId.substring(0, 8).padEnd(16, '0'); // Ensure 16 chars - - const payload = { - resourceSpans: [{ - resource: { - attributes: [ - { key: "service.name", value: { stringValue: "kicktalk-verification" } }, - { key: "service.namespace", value: { stringValue: "kicktalk" } }, - { key: "deployment.environment", value: { stringValue: "development" } } - ] - }, - scopeSpans: [{ - scope: { - name: "kicktalk-verification", - version: "1.0.0" - }, - spans: [{ - traceId, - spanId, - name: "grafana_verification_test", - kind: 1, - startTimeUnixNano: nowNs.toString(), - endTimeUnixNano: (nowNs + 1000000000n).toString(), // +1 second - status: { code: 1 }, - attributes: [ - { key: "test.id", value: { stringValue: testId } }, - { key: "test.method", value: { stringValue: "javascript_fetch" } }, - { key: "verification.timestamp", value: { stringValue: new Date().toISOString() } } - ] - }] - }] - }] - }; + let traceId = testId.padEnd(32, '0'); + let spanId = testId.substring(0, 8).padEnd(16, '0'); + const startedAt = performance.now(); + const startTimeNs = BigInt(Date.now()) * 1_000_000n; + const endTimeNs = startTimeNs + 1_000_000_000n; // +1 second try { - console.log(`[Grafana Verification][${testId}] Sending test trace via OpenTelemetry API...`); - - // Use the OpenTelemetry tracer to create a real span instead of direct fetch + console.log(`[Grafana Verification][${testId}] Sending test trace via IPC relay...`); + + const telemetryBridge = window.telemetry; + if (!telemetryBridge?.exportTracesJson) { + throw new Error('window.telemetry.exportTracesJson bridge is unavailable'); + } + + const timestampIso = new Date().toISOString(); + + // Create a real span so instrumentation is exercised alongside manual payload const { trace } = await import('@opentelemetry/api'); const tracer = trace.getTracer('kicktalk-verification'); - + const span = tracer.startSpan('grafana_verification_test', { attributes: { 'service.name': 'kicktalk-verification', 'test.id': testId, - 'test.method': 'otel_tracer', - 'verification.timestamp': new Date().toISOString() + 'test.method': 'ipc_relay', + 'verification.timestamp': timestampIso } }); - - // Add some events and end the span to trigger export + span.addEvent('verification_test_started'); span.addEvent('verification_test_completed'); span.end(); - - // Force flush to ensure it gets exported - const provider = window.__KT_TRACE_PROVIDER__; + + const provider = window.__KT_OTEL_PROVIDER__ || window.__KT_TRACE_PROVIDER__; if (provider && typeof provider.forceFlush === 'function') { await provider.forceFlush(); + } else { + console.warn(`[Grafana Verification][${testId}] No OTEL provider with forceFlush registered; continuing with IPC export.`); } - + const spanContext = span.spanContext(); - const traceId = spanContext.traceId; - const spanId = spanContext.spanId; - - console.log(`[Grafana Verification][${testId}] Span created and exported via IPC:`, { - testId, - traceId, - spanId, - method: 'otel_tracer_api' - }); - - // Simulate success response since we used the working IPC system - const response = { ok: true, status: 200, statusText: 'OK' }; - const result = 'Exported via IPC relay system'; + traceId = spanContext?.traceId || traceId; + spanId = spanContext?.spanId || spanId; + + const payload = { + resourceSpans: [{ + resource: { + attributes: [ + { key: 'service.name', value: { stringValue: 'kicktalk-verification' } }, + { key: 'service.namespace', value: { stringValue: 'kicktalk' } }, + { key: 'deployment.environment', value: { stringValue: 'development' } }, + { key: 'test.id', value: { stringValue: testId } }, + { key: 'test.method', value: { stringValue: 'ipc_relay' } } + ] + }, + scopeSpans: [{ + scope: { + name: 'kicktalk-verification', + version: '1.0.0' + }, + spans: [{ + traceId, + spanId, + name: 'grafana_verification_test', + kind: 1, + startTimeUnixNano: startTimeNs.toString(), + endTimeUnixNano: endTimeNs.toString(), + status: { code: 1 }, + attributes: [ + { key: 'test.id', value: { stringValue: testId } }, + { key: 'verification.timestamp', value: { stringValue: timestampIso } } + ] + }] + }] + }] + }; + + const response = await telemetryBridge.exportTracesJson(payload); + + if (!response?.ok) { + const reason = response?.reason || 'unknown_error'; + const status = response?.status ? `status ${response.status}` : 'no status'; + throw new Error(`IPC relay rejected trace export (${status}, reason: ${reason})`); + } + + const totalDuration = Math.round(performance.now() - startedAt); console.log(`[Grafana Verification][${testId}] Export completed:`, { testId, traceId, spanId, - status: response.status, - statusText: response.statusText, - result, + status: response.status ?? 200, + requestId: response.requestId, + durationMs: totalDuration, grafanaTraceUrl: `https://kicktalk.grafana.net/explore?left=%7B%22datasource%22%3A%22tempo%22%2C%22queries%22%3A%5B%7B%22query%22%3A%22${traceId}%22%7D%5D%7D` }); return { - success: response.ok, + success: true, testId, traceId, spanId, - status: response.status, - result, - message: response.ok ? - `āœ… Trace sent successfully! Check Grafana in 1-2 minutes for trace ID: ${traceId}` : - `āŒ Failed to send trace: ${response.status} ${response.statusText}` + status: response.status ?? 200, + requestId: response.requestId, + message: `āœ… Trace sent successfully! Check Grafana in 1-2 minutes for trace ID: ${traceId}` }; } catch (error) { console.error(`[Grafana Verification][${testId}] Error:`, error); return { success: false, testId, + traceId, + spanId, error: error.message, - message: `āŒ Network error: ${error.message}` + message: `āŒ Telemetry export failed: ${error.message}` }; } } diff --git a/src/renderer/src/utils/MessageParser.jsx b/src/renderer/src/utils/MessageParser.jsx index 0298e89..1833c0b 100644 --- a/src/renderer/src/utils/MessageParser.jsx +++ b/src/renderer/src/utils/MessageParser.jsx @@ -498,7 +498,6 @@ export const MessageParser = ({ if (messageContentCache.has(cacheKey)) { // No span for cache hits (reduce trace noise) - const parseTime = performance.now() - startTime; return messageContentCache.get(cacheKey); } diff --git a/src/telemetry/error-monitoring.js b/src/telemetry/error-monitoring.js index 1ae85e0..1aadb2f 100644 --- a/src/telemetry/error-monitoring.js +++ b/src/telemetry/error-monitoring.js @@ -146,7 +146,7 @@ class CircuitBreaker { if (fallback && typeof fallback === 'function') { try { return await fallback(); - } catch (fallbackError) { + } catch (_fallbackError) { throw error; // Return original error if fallback fails } } @@ -429,7 +429,7 @@ const ErrorMonitor = { this.recordRecovery(errorRecord.error_id, 'fallback', true, duration); return fallbackResult; - } catch (fallbackError) { + } catch (_fallbackError) { this.recordRecovery(errorRecord.error_id, 'fallback', false); } } diff --git a/src/telemetry/performance-budget.js b/src/telemetry/performance-budget.js index bf84d11..904913b 100644 --- a/src/telemetry/performance-budget.js +++ b/src/telemetry/performance-budget.js @@ -1,13 +1,11 @@ // KickTalk Performance Budget & User Experience Impact Analysis -const { metrics, trace } = require('@opentelemetry/api'); -const { ErrorMonitor } = require('./error-monitoring'); +const { metrics } = require('@opentelemetry/api'); // Lazy load UserAnalytics to avoid circular dependency let UserAnalytics = null; const pkg = require('../../package.json'); const meter = metrics.getMeter('kicktalk-performance-budget', pkg.version); -const tracer = trace.getTracer('kicktalk-performance-budget', pkg.version); // Performance Budget Metrics const performanceBudgetViolation = meter.createCounter('kicktalk_performance_budget_violations_total', { @@ -459,7 +457,6 @@ class PerformanceBudgetMonitor { this.monitorMemoryUsage(memMB); // CPU usage monitoring (simplified) - const cpuUsage = process.cpuUsage(); // This is a simplified CPU calculation - in practice you'd want more sophisticated monitoring } catch (error) { diff --git a/src/telemetry/prometheus-server.js b/src/telemetry/prometheus-server.js index 2fb79d4..6f00b91 100644 --- a/src/telemetry/prometheus-server.js +++ b/src/telemetry/prometheus-server.js @@ -17,7 +17,7 @@ const startMetricsServer = (port = 9464) => { try { const { PrometheusRegistry: PR } = require('@opentelemetry/exporter-prometheus'); PrometheusRegistry = PR; - } catch (error) { + } catch (_error) { console.warn('[Metrics]: @opentelemetry/exporter-prometheus not available, using fallback'); PrometheusRegistry = null; } @@ -48,8 +48,6 @@ const startMetricsServer = (port = 9464) => { // Fallback: create a simple HTTP server that returns basic metrics try { - const { metrics } = require('@opentelemetry/api'); - metricsServer = http.createServer((req, res) => { if (req.url === '/metrics' && req.method === 'GET') { res.writeHead(200, { diff --git a/src/telemetry/user-analytics.js b/src/telemetry/user-analytics.js index b5c3de4..2172aa8 100644 --- a/src/telemetry/user-analytics.js +++ b/src/telemetry/user-analytics.js @@ -1,6 +1,5 @@ // KickTalk User Experience Analytics & Session Tracking -const { metrics, trace, context } = require('@opentelemetry/api'); -const { ErrorMonitor } = require('./error-monitoring'); +const { metrics, trace } = require('@opentelemetry/api'); const pkg = require('../../package.json'); const meter = metrics.getMeter('kicktalk-user-analytics', pkg.version); @@ -100,14 +99,6 @@ let performanceCorrelationData = { error_impact_sessions: new Set() }; -// User satisfaction scoring factors -const SATISFACTION_FACTORS = { - RESPONSE_TIME_WEIGHT: 0.3, - ERROR_RATE_WEIGHT: 0.25, - ENGAGEMENT_WEIGHT: 0.2, - CONNECTION_QUALITY_WEIGHT: 0.15, - FEATURE_ADOPTION_WEIGHT: 0.1 -}; class UserSession { constructor(sessionId, userId = null) { @@ -161,7 +152,7 @@ class UserSession { } getFeatureFromAction(actionType) { - for (const [category, config] of Object.entries(FEATURE_CATEGORIES)) { + for (const [, config] of Object.entries(FEATURE_CATEGORIES)) { if (config.actions.some(action => actionType.includes(action))) { return config.name; } @@ -226,7 +217,6 @@ class UserSession { } calculateFinalSatisfactionScore() { - const sessionDuration = this.getSessionDuration(); const engagementRate = this.getEngagementRate(); const errorRate = this.actions.length > 0 ? this.errorCount / this.actions.length : 0; const avgResponseTime = this.getAverageResponseTime(); @@ -344,8 +334,6 @@ const UserAnalytics = { return; } - const startTime = Date.now(); - return tracer.startActiveSpan(`user_action_${actionType}`, (span) => { try { // Record the action @@ -531,7 +519,7 @@ const UserAnalytics = { userBehaviorData.delete(sessionId); cleanedHistoricalData++; } - } catch (error) { + } catch (_error) { // If session ID format is unexpected, clean it up anyway if too old if (data.timestamp && data.timestamp < historicalCutoff) { userBehaviorData.delete(sessionId); @@ -544,7 +532,7 @@ const UserAnalytics = { const adoptionCutoff = Date.now() - (30 * 24 * 60 * 60 * 1000); let cleanedAdoptionData = 0; - for (const [userId, features] of featureAdoptionData) { + for (const [userId] of featureAdoptionData) { // Remove adoption data if no recent sessions for this user const hasRecentSession = Array.from(userBehaviorData.values()) .some(data => data.user_id === userId && diff --git a/utils/fetchQueue.js b/utils/fetchQueue.js index 1709898..3e30f9d 100644 --- a/utils/fetchQueue.js +++ b/utils/fetchQueue.js @@ -5,7 +5,7 @@ const processQueue = async () => { if (isFetching || fetchQueue.length === 0) return; isFetching = true; - const { input, resolve, reject } = fetchQueue.shift(); + const { input, resolve } = fetchQueue.shift(); const apiStart = Date.now(); try { diff --git a/utils/services/connectionManager.js b/utils/services/connectionManager.js index ff70288..ce6fb7f 100644 --- a/utils/services/connectionManager.js +++ b/utils/services/connectionManager.js @@ -6,7 +6,7 @@ let tracer; try { const { trace } = require('@opentelemetry/api'); tracer = trace.getTracer('kicktalk-connection-manager', '1.0.0'); -} catch (e) { +} catch (_e) { // Fallback if OpenTelemetry not available tracer = { startSpan: (name, options) => ({ diff --git a/utils/services/kick/kickAPI.js b/utils/services/kick/kickAPI.js index 98adf2f..5c5d720 100644 --- a/utils/services/kick/kickAPI.js +++ b/utils/services/kick/kickAPI.js @@ -54,24 +54,6 @@ const getKickAuthForEvents = async (eventChannelName, socketId, sessionCookie, k * */ -const getFollowChannel = async (channelName) => { - // TODO: Error Handling for already following and not following - // { - // "message": "The given data was invalid.", - // "errors": { - // "channel": [ - // "You are not following this channel." - // ] - // } - // } - const response = await axios.post(`${APIUrl}/api/v2/channels/${channelName}/follow`); - return response.data; -}; - -const getUnfollowChannel = async (channelName) => { - const response = await axios.delete(`${APIUrl}/api/v2/channels/${channelName}/follow`); - return response.data; -}; /** * @@ -555,6 +537,7 @@ const getSubmitPollVote = async (channelName, optionId, sessionCookie, kickSessi return response.data; } catch (error) { + console.error('Poll vote error:', error); throw error; } }; diff --git a/utils/services/kick/sharedKickPusher.js b/utils/services/kick/sharedKickPusher.js index 574c9c5..33f2a02 100644 --- a/utils/services/kick/sharedKickPusher.js +++ b/utils/services/kick/sharedKickPusher.js @@ -3,7 +3,7 @@ let tracer; try { const { trace } = require('@opentelemetry/api'); tracer = trace.getTracer('kicktalk-shared-kick-pusher', '1.0.0'); -} catch (e) { +} catch (_e) { // Fallback if OpenTelemetry not available tracer = { startSpan: (name, options) => ({ diff --git a/utils/services/seventv/sharedStvWebSocket.js b/utils/services/seventv/sharedStvWebSocket.js index 387ba23..2355ee4 100644 --- a/utils/services/seventv/sharedStvWebSocket.js +++ b/utils/services/seventv/sharedStvWebSocket.js @@ -75,9 +75,9 @@ const updateCosmetics = async (body) => { name: data.name, style: data.function, shape: data.shape, - backgroundImage: - `${data.function || "linear-gradient"}(${isDeg_or_Shape}, ${gradient})` || - `${data.style || "linear-gradient"}(${data.shape || ""} 0deg, ${randomColor}, ${randomColor})`, + backgroundImage: data.function || data.style + ? `${data.function || "linear-gradient"}(${isDeg_or_Shape}, ${gradient})` + : `${data.style || "linear-gradient"}(${data.shape || ""} 0deg, ${randomColor}, ${randomColor})`, shadows: null, KIND: "non-animated", url: data.image_url, @@ -88,9 +88,9 @@ const updateCosmetics = async (body) => { name: data.name, style: data.function, shape: data.shape, - backgroundImage: - `url('${[data.image_url]}')` || - `${data.style || "linear-gradient"}(${data.shape || ""} 0deg, ${randomColor}, ${randomColor})`, + backgroundImage: data.image_url + ? `url('${[data.image_url]}')` + : `${data.style || "linear-gradient"}(${data.shape || ""} 0deg, ${randomColor}, ${randomColor})`, shadows: null, KIND: "animated", url: data.image_url, @@ -138,7 +138,7 @@ let tracer; try { const { trace } = require('@opentelemetry/api'); tracer = trace.getTracer('kicktalk-shared-7tv-websocket', '1.0.0'); -} catch (e) { +} catch (_e) { // Fallback if OpenTelemetry not available tracer = { startSpan: (name, options) => ({ diff --git a/utils/services/seventv/stvAPI.js b/utils/services/seventv/stvAPI.js index 6169381..fb25966 100644 --- a/utils/services/seventv/stvAPI.js +++ b/utils/services/seventv/stvAPI.js @@ -1,9 +1,5 @@ import axios from "axios"; -const getPersonalEmoteSet = async (userId) => { - const response = await axios.get(`https://7tv.io/v3/users/${userId}/emote-sets/personal`); - return response.data; -}; const getChannelEmotes = async (channelId) => { console.log("[7tv Emotes] Fetching channel emotes for", channelId); diff --git a/utils/services/seventv/stvWebsocket.js b/utils/services/seventv/stvWebsocket.js index d3b2779..01fccf1 100644 --- a/utils/services/seventv/stvWebsocket.js +++ b/utils/services/seventv/stvWebsocket.js @@ -73,9 +73,9 @@ const updateCosmetics = async (body) => { name: data.name, style: data.function, shape: data.shape, - backgroundImage: - `${data.function || "linear-gradient"}(${isDeg_or_Shape}, ${gradient})` || - `${data.style || "linear-gradient"}(${data.shape || ""} 0deg, ${randomColor}, ${randomColor})`, + backgroundImage: data.function || data.style + ? `${data.function || "linear-gradient"}(${isDeg_or_Shape}, ${gradient})` + : `${data.style || "linear-gradient"}(${data.shape || ""} 0deg, ${randomColor}, ${randomColor})`, shadows: null, KIND: "non-animated", url: data.image_url, @@ -86,9 +86,9 @@ const updateCosmetics = async (body) => { name: data.name, style: data.function, shape: data.shape, - backgroundImage: - `url('${[data.image_url]}')` || - `${data.style || "linear-gradient"}(${data.shape || ""} 0deg, ${randomColor}, ${randomColor})`, + backgroundImage: data.image_url + ? `url('${[data.image_url]}')` + : `${data.style || "linear-gradient"}(${data.shape || ""} 0deg, ${randomColor}, ${randomColor})`, shadows: null, KIND: "animated", url: data.image_url, @@ -189,13 +189,12 @@ class StvWebSocket extends EventTarget { await this.delay(100); } - const waitTime = Date.now() - waitStartTime; - // Subscribe to user & cosmetic events if (this.stvId !== "0") { this.subscribeToUserEvents(); this.subscribeToCosmeticEvents(); } else { + // No STV ID } // Subscribe to entitlement events @@ -206,8 +205,10 @@ class StvWebSocket extends EventTarget { if (this.stvEmoteSetId !== "0") { this.subscribeToEmoteSetEvents(); } else { + // No emote set ID } } else { + // No channel kick ID } // Setup message handler