Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,33 @@ try {
console.warn('[Telemetry]: Failed to set service.version from package/app version:', e?.message || e);
}

// Telemetry debug configuration for main process
const getTelemetryDebug = () => {
try {
// Check consolidated VITE_ vars first (available to all processes), then fall back to old vars
return import.meta.env.VITE_TELEMETRY_DEBUG === 'true' ||
import.meta.env.MAIN_VITE_TELEMETRY_DEBUG === 'true';
} catch {
return false;
}
};

const getTelemetryLevel = () => {
try {
// Check consolidated VITE_ vars first (available to all processes), then fall back to old vars
const level = import.meta.env.VITE_TELEMETRY_LEVEL ||
import.meta.env.MAIN_VITE_TELEMETRY_LEVEL ||
'NORMAL';
return level.toUpperCase();
} catch {
return 'NORMAL';
}
};

const shouldLogDebug = () => {
return getTelemetryDebug() || getTelemetryLevel() === 'VERBOSE';
};

// Load metrics with fallback
let metrics = null;
try {
Expand Down Expand Up @@ -686,8 +713,10 @@ ipcMain.handle("otel:trace-export-json", async (_e, exportJson) => {
const startedAt = Date.now();

try {
console.log(`[OTEL IPC Relay][${requestId}] Received trace export from renderer`);
console.log(`[OTEL IPC Relay][${requestId}] Payload size: ${JSON.stringify(exportJson || {}).length} chars`);
if (shouldLogDebug()) {
console.log(`[OTEL IPC Relay][${requestId}] Received trace export from renderer`);
console.log(`[OTEL IPC Relay][${requestId}] Payload size: ${JSON.stringify(exportJson || {}).length} chars`);
}

const base = import.meta.env.MAIN_VITE_OTEL_EXPORTER_OTLP_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "";
const endpoint = import.meta.env.MAIN_VITE_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
Expand Down Expand Up @@ -732,7 +761,9 @@ ipcMain.handle("otel:trace-export-json", async (_e, exportJson) => {
timeout: 15000,
};

console.log(`[OTEL IPC Relay][${requestId}] → POST ${url.hostname}${options.path}`);
if (shouldLogDebug()) {
console.log(`[OTEL IPC Relay][${requestId}] → POST ${url.hostname}${options.path}`);
}

const result = await new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
Expand All @@ -742,7 +773,9 @@ ipcMain.handle("otel:trace-export-json", async (_e, exportJson) => {
const ms = Date.now() - startedAt;
const responseBody = Buffer.concat(chunks).toString("utf8");

console.log(`[OTEL IPC Relay][${requestId}] ← ${res.statusCode} (${ms}ms)`);
if (shouldLogDebug()) {
console.log(`[OTEL IPC Relay][${requestId}] ← ${res.statusCode} (${ms}ms)`);
}
resolve({ statusCode: res.statusCode || 0, responseBody });
});
});
Expand All @@ -762,7 +795,9 @@ ipcMain.handle("otel:trace-export-json", async (_e, exportJson) => {
});

const success = result.statusCode >= 200 && result.statusCode < 300;
console.log(`[OTEL IPC Relay][${requestId}] Result: ${success ? 'success' : 'failed'}`);
if (shouldLogDebug()) {
console.log(`[OTEL IPC Relay][${requestId}] Result: ${success ? 'success' : 'failed'}`);
}

return { ok: success, status: result.statusCode, requestId };
} catch (e) {
Expand Down
3 changes: 2 additions & 1 deletion src/preload/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
getUpdateTitle,
getClearChatroom,
} from "../../utils/services/kick/kickAPI";
import { getUserStvProfile, getChannelEmotes } from "../../utils/services/seventv/stvAPI";
import { getUserStvProfile, getChannelEmotes, getChannelCosmetics } from "../../utils/services/seventv/stvAPI";

import Store from "electron-store";

Expand Down Expand Up @@ -498,6 +498,7 @@ if (process.contextIsolated) {
// 7TV API
stv: {
getChannelEmotes,
getChannelCosmetics,
},

// Utility functions
Expand Down
20 changes: 11 additions & 9 deletions src/renderer/src/components/Messages/Message.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ModActionMessage from "./ModActionMessage";
import RegularMessage from "./RegularMessage";
import EmoteUpdateMessage from "./EmoteUpdateMessage";
import clsx from "clsx";
import { useShallow } from "zustand/shallow";
import { useShallow } from "zustand/react/shallow";
import useCosmeticsStore from "../../providers/CosmeticsProvider";
import useChatStore from "../../providers/ChatProvider";
import ReplyMessage from "./ReplyMessage";
Expand Down Expand Up @@ -41,15 +41,17 @@ const Message = ({
const getDeleteMessage = useChatStore(useShallow((state) => state.getDeleteMessage));
const [rightClickedEmote, setRightClickedEmote] = useState(null);

let userStyle;
const subscribedUserStyle = useCosmeticsStore(
useShallow((state) => {
if (!message?.sender || type === "replyThread" || type === "dialog") {
return null;
}

if (message?.sender && type !== "replyThread") {
if (type === "dialog") {
userStyle = dialogUserStyle;
} else {
userStyle = useCosmeticsStore(useShallow((state) => state.getUserStyle(message?.sender?.username)));
}
}
return state.getUserStyle(message.sender.username);
}),
);

const userStyle = type === "dialog" ? dialogUserStyle : subscribedUserStyle;

// CheckIcon if user can moderate
const canModerate = useMemo(
Expand Down
4 changes: 1 addition & 3 deletions src/renderer/src/components/Messages/ModActionMessage.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { useCallback } from "react";
import { convertMinutesToHumanReadable } from "../../utils/ChatUtils";
import useCosmeticsStore from "../../providers/CosmeticsProvider";
import { useShallow } from "zustand/react/shallow";

const ModActionMessage = ({ message, chatroomId, allStvEmotes, subscriberBadges, chatroomName, userChatroomInfo }) => {
const { modAction, modActionDetails } = message;
const getUserStyle = useCosmeticsStore(useShallow((state) => state.getUserStyle));

const actionTaker = modActionDetails?.banned_by?.username || modActionDetails?.unbanned_by?.username;
const moderator = actionTaker !== "moderated" ? actionTaker : "Bot";
Expand All @@ -20,7 +18,7 @@ const ModActionMessage = ({ message, chatroomId, allStvEmotes, subscriberBadges,
const user = await window.app.kick.getUserChatroomInfo(chatroomName, usernameDialog);
if (!user?.data?.id) return;

const userStyle = getUserStyle(usernameDialog);
const userStyle = useCosmeticsStore.getState().getUserStyle(usernameDialog);

const userDialogInfo = {
id: user.data.id,
Expand Down
130 changes: 114 additions & 16 deletions src/renderer/src/providers/ChatProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,9 @@ const useChatStore = create((set, get) => ({

if (stvPresenceUpdates.has(userId)) {
const lastUpdateTime = stvPresenceUpdates.get(userId);
console.log("[7tv Presence]: Last update time for chatroom:", userId, lastUpdateTime, stvPresenceUpdates);
if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) {
console.log("[7tv Presence]: Last update time for chatroom:", userId, lastUpdateTime, stvPresenceUpdates);
}
if (currentTime - lastUpdateTime < PRESENCE_UPDATE_INTERVAL) {
return;
}
Expand Down Expand Up @@ -1225,6 +1227,24 @@ const useChatStore = create((set, get) => ({
if (pusher.chat.OPEN) {
const channel7TVEmotes = await window.app.stv.getChannelEmotes(chatroom.streamerData.user_id);

// Load initial cosmetics (badges and paints) for the channel
try {
const channelCosmetics = await window.app.stv.getChannelCosmetics(chatroom.streamerData.user_id);
console.log('[7TV Cosmetics] Initial cosmetics loaded:', {
badges: channelCosmetics?.badges?.length || 0,
paints: channelCosmetics?.paints?.length || 0
});

// Add cosmetics to the store
const addCosmetics = useCosmeticsStore?.getState()?.addCosmetics;
if (addCosmetics && channelCosmetics && (channelCosmetics.badges?.length > 0 || channelCosmetics.paints?.length > 0)) {
addCosmetics(channelCosmetics);
console.log('[7TV Cosmetics] Added initial cosmetics to store');
}
} catch (error) {
console.error('[7TV Cosmetics] Failed to load initial cosmetics:', error);
}

if (channel7TVEmotes) {
const seenEmoteNames = new Set();

Expand Down Expand Up @@ -1509,14 +1529,21 @@ const useChatStore = create((set, get) => ({
// 7TV event handlers
onStvMessage: (event) => {
try {
const { chatroomId } = event.detail;
const { chatroomId, type, body } = event.detail;
if (chatroomId) {
get().handleStvMessage(chatroomId, event.detail);
} else {
// Broadcast to all chatrooms if no specific chatroom
chatrooms.forEach(chatroom => {
get().handleStvMessage(chatroom.id, event.detail);
});
// Handle global cosmetic events once instead of broadcasting to all chatrooms
if (type === 'cosmetic.create' || type === 'entitlement.create' || type === 'entitlement.delete') {
console.log(`[ChatProvider] Processing global ${type} event for ${body?.object?.user?.username || 'unknown'}`);
// Handle once with a null chatroomId to indicate global event
get().handleStvMessage(null, event.detail);
} else {
// Broadcast to all chatrooms if no specific chatroom (for non-cosmetic events)
chatrooms.forEach(chatroom => {
get().handleStvMessage(chatroom.id, event.detail);
});
}
}
} catch (error) {
console.error("[ChatProvider] Error handling 7TV message:", error);
Expand Down Expand Up @@ -1625,7 +1652,9 @@ const useChatStore = create((set, get) => ({
get().connectToChatroom(chatroom);

// Connect to 7TV WebSocket
get().connectToStvWebSocket(chatroom);
// DISABLED: Using shared connection system via connectionManager.initializeConnections
console.log(`[ChatProvider] Skipping individual 7TV connection for chatroom ${chatroom.id} - using shared connection system`);
// get().connectToStvWebSocket(chatroom);
}
});
},
Expand Down Expand Up @@ -1893,19 +1922,79 @@ const useChatStore = create((set, get) => ({
handleStvMessage: (chatroomId, eventDetail) => {
const { type, body } = eventDetail;

console.log(
`[ChatProvider] Received 7TV event ${type} for ${chatroomId ?? 'broadcast'}`,
{
badgeCount: body?.badges?.length,
paintCount: body?.paints?.length,
entitlementUser: body?.object?.user?.username,
},
);

switch (type) {
case "connection_established":
break;
case "emote_set.update":
get().handleEmoteSetUpdate(chatroomId, body);
break;
case "cosmetic.create":
useCosmeticsStore?.getState()?.addCosmetics(body);
case "cosmetic.create": {
console.log(
`[ChatProvider] Applying cosmetic catalog update for ${chatroomId ?? 'all chatrooms'}`,
{
badges: body?.badges?.length,
paints: body?.paints?.length,
},
);
const addCosmetics = useCosmeticsStore?.getState()?.addCosmetics;
if (addCosmetics) {
if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Calling CosmeticsStore.addCosmetics with body:`, {
badges: body?.badges?.length,
paints: body?.paints?.length
});
addCosmetics(body);
} else {
console.error(`[ChatProvider] CosmeticsStore.addCosmetics method not available!`);
}
break;
}
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);
console.log(
`[ChatProvider] Processing entitlement for ${transformedUsername || 'unknown user'}`,
{
badgeId: body?.object?.user?.style?.badge_id,
paintId: body?.object?.user?.style?.paint_id,
chatroomId,
},
);
const addUserStyle = useCosmeticsStore?.getState()?.addUserStyle;
if (addUserStyle) {
console.log(`[ChatProvider] Calling CosmeticsStore.addUserStyle for ${transformedUsername}`);
addUserStyle(transformedUsername, body);
} else {
console.error(`[ChatProvider] CosmeticsStore.addUserStyle method not available!`);
}
Comment on lines +1964 to +1977
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Username normalization may diverge from store lookups.

You store under username.replaceAll('-', '_').toLowerCase() but CosmeticsStore.getUserStyle() normalizes to lowercase only. This can break lookups for hyphenated names.

Prefer a shared normalizer used by both writer and reader (e.g., keep original username from KICK connection and only lowercase), or update getUserStyle to mirror the same normalization.


🏁 Script executed:

#!/bin/bash
# Find all places replacing '-' with '_' for usernames to assess consistency
rg -nP "replaceAll\(\s*['\"]-['\"],\s*['\"]_['\"]\s*\).*username" -C2 --type=ts --type=tsx --type=js --type=jsx

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Searching for replaceAll occurrences (no type flags)..."
rg -n -C2 "replaceAll\(" || true

echo
echo "2) Searching for any replace( occurrences (showing context)..."
rg -n -C2 "replace\(" || true

echo
echo "3) Searching for transformedUsername occurrences..."
rg -n -C2 "transformedUsername" || true

echo
echo "4) Searching for getUserStyle occurrences..."
rg -n -C2 "getUserStyle" || true

echo
echo "5) Searching for addUserStyle occurrences..."
rg -n -C2 "addUserStyle" || true

echo
echo "6) Searching for CosmeticsStore occurrences..."
rg -n -C2 "CosmeticsStore" || true

echo
echo "7) Show ChatProvider.jsx snippet (lines 1900-1980) if file exists..."
if [ -f src/renderer/src/providers/ChatProvider.jsx ]; then
  sed -n '1900,1980p' src/renderer/src/providers/ChatProvider.jsx || true
else
  echo "src/renderer/src/providers/ChatProvider.jsx not found"
fi

Length of output: 47048


Inconsistent username normalization — hyphenated usernames will miss stored cosmetics

ChatProvider normalizes KICK usernames with username?.replaceAll("-", "_").toLowerCase() (src/renderer/src/providers/ChatProvider.jsx:1949) but CosmeticsProvider.addUserStyle/getUserStyle only call username.toLowerCase() (src/renderer/src/providers/CosmeticsProvider.jsx:32,75). Result: "foo-bar" vs "foo_bar" keys are inconsistent and lookups can fail.

Fix: centralize normalization (e.g., utils/normalizeUsername that replaces '-'→'_' and lowercases) and use it in both addUserStyle and getUserStyle — or have getUserStyle mirror ChatProvider's replaceAll behavior before lookup.

🤖 Prompt for AI Agents
In src/renderer/src/providers/ChatProvider.jsx around lines 1951-1964, username
normalization is inconsistent with CosmeticsProvider leading to hyphenated
usernames ("foo-bar") not matching stored keys ("foo_bar"); create a single
utils function (e.g., utils/normalizeUsername) that replaces '-' with '_' and
lowercases the string, then use that function in ChatProvider where usernames
are processed and in CosmeticsProvider's addUserStyle and getUserStyle (and any
lookup sites) so all keys are normalized identically before storage and lookup.

break;
}
case "entitlement.delete": {
const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
console.log(
`[ChatProvider] Processing entitlement deletion for ${transformedUsername || 'unknown user'}`,
{
refId: body?.object?.ref_id,
kind: body?.object?.kind,
chatroomId,
},
);
const removeUserStyle = useCosmeticsStore?.getState()?.removeUserStyle;
if (removeUserStyle) {
console.log(`[ChatProvider] Calling CosmeticsStore.removeUserStyle for ${transformedUsername}`);
removeUserStyle(transformedUsername, body);
} else {
console.error(`[ChatProvider] CosmeticsStore.removeUserStyle method not available!`);
}
break;
}
default:
Expand Down Expand Up @@ -2144,8 +2233,18 @@ const useChatStore = create((set, get) => ({
// Connect to chatroom
get().connectToChatroom(newChatroom);

// Connect to 7TV WebSocket
get().connectToStvWebSocket(newChatroom);
// Connect to 7TV WebSocket via connectionManager
if (connectionManager) {
console.log(`[ChatProvider] Adding new chatroom ${newChatroom.id} to connectionManager for 7TV subscriptions`);
try {
await connectionManager.addChatroom(newChatroom);
console.log(`[ChatProvider] Successfully added chatroom ${newChatroom.id} to connectionManager`);
} catch (error) {
console.error(`[ChatProvider] Error adding chatroom ${newChatroom.id} to connectionManager:`, error);
}
} else {
console.warn(`[ChatProvider] ConnectionManager not available for new chatroom ${newChatroom.id}`);
}

// Save to local storage
localStorage.setItem("chatrooms", JSON.stringify([...savedChatrooms, newChatroom]));
Expand Down Expand Up @@ -3180,9 +3279,6 @@ const useChatStore = create((set, get) => ({
// Clear emote cache to ensure new emotes are loaded from updated store
clearChatroomEmoteCache(chatroomId);

// Refresh emote data to get the updated emote set
get().refresh7TVEmotes(chatroomId);

try {
const processingDuration = performance.now() - startTime;
// Record emote update metrics via IPC
Expand Down Expand Up @@ -4031,7 +4127,9 @@ if (window.location.pathname === "/" || window.location.pathname.endsWith("index
if (chatrooms?.length === 0) return;

chatrooms.forEach((chatroom) => {
console.log("[7tv Presence]: Sending presence check for chatroom:", chatroom.streamerData.user_id);
if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) {
console.log("[7tv Presence]: Sending presence check for chatroom:", chatroom.streamerData.user_id);
}
useChatStore.getState().sendPresenceUpdate(storeStvId, chatroom.streamerData.user_id);
});
},
Expand Down
Loading
Loading