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 && (
-