Attempt to resolve web socket issues

This commit is contained in:
HighImKevin
2026-10-16 05:33:54 -04:00
parent 6f97a75d69
commit cef8d3db28
2 changed files with 94 additions and 38 deletions
+19 -10
View File
@@ -62,6 +62,8 @@
subscribeAttached[messageType] = true;
subscribeUnsubs[messageType] = typeof unsub === 'function' ? unsub : null;
deliveryMode = 'subscribe';
// Drop any legacy onmessage hook immediately — do not wait for the poll tick
unhookSocket();
LOG(`Subscribed via ApiClient.subscribe for ${messageType}`);
return true;
} catch (err) {
@@ -95,8 +97,11 @@
/**
* Legacy path: wrap socket.onmessage, re-bind when the socket instance changes.
* Only used when ApiClient.subscribe is unavailable.
*/
function hookSocketIfNeeded() {
if (canSubscribe()) return false;
const socket = getSocket();
if (!socket) return false;
@@ -143,17 +148,21 @@
}
function tryInitializeDelivery() {
// Prefer JF-web style subscribe (reconnect-safe)
// Prefer JF-web style subscribe (reconnect-safe). Never wrap onmessage when subscribe exists.
if (canSubscribe()) {
if (ensureSubscribeForAllRegisteredTypes()) {
return true;
}
return ensureSubscribeForAllRegisteredTypes();
}
// Fallback: raw socket onmessage with rebind support
return hookSocketIfNeeded();
}
function stopPolling() {
if (pollingIntervalId == null) return;
clearInterval(pollingIntervalId);
pollingIntervalId = null;
}
function startPolling() {
if (pollingIntervalId != null) return;
if (tryInitializeDelivery()) {
@@ -163,15 +172,14 @@
pollingIntervalId = setInterval(() => {
if (canSubscribe()) {
ensureSubscribeForAllRegisteredTypes();
if (deliveryMode === 'subscribe') {
// Subscribe path owns delivery; drop legacy hook if any
if (ensureSubscribeForAllRegisteredTypes() && deliveryMode === 'subscribe') {
unhookSocket();
clearInterval(pollingIntervalId);
pollingIntervalId = null;
stopPolling();
LOG('Delivery mode: ApiClient.subscribe');
return;
}
// subscribe exists but types not attached yet — keep polling, never hook onmessage
return;
}
const socket = getSocket();
@@ -252,5 +260,6 @@
},
};
startPolling();
// Do not startPolling() here — wait until the first listen() so we never
// wrap JF's socket before any consumer needs delivery.
})();
+75 -28
View File
@@ -2141,11 +2141,30 @@
let cardLibraryChangedListenerStarted = false;
let pendingLibraryUpdatedIds = new Set();
let libraryUpdatedFlushTimer = null;
const LIBRARY_UPDATED_DEBOUNCE_MS = 400;
let libraryUpdatedFlushRunning = false;
const LIBRARY_UPDATED_DEBOUNCE_MS = 750;
const LIVE_SYNC_REBUILD_BATCH_SIZE = 8;
// Match apiHelper DEFAULT_ITEM_FIELDS so rebuilt cards keep aspect/overlays/indicators.
const LIVE_SYNC_ITEM_FIELDS =
'PrimaryImageAspectRatio,DateCreated,Overview,Taglines,ProductionYear,RecursiveItemCount,ChildCount,UserData,ProviderIds,MediaSourceCount';
function scheduleLibraryUpdatedFlush() {
if (libraryUpdatedFlushTimer) clearTimeout(libraryUpdatedFlushTimer);
libraryUpdatedFlushTimer = setTimeout(() => {
void flushVisibleCardLibraryUpdates();
}, LIBRARY_UPDATED_DEBOUNCE_MS);
}
function yieldToMainThread() {
return new Promise((resolve) => {
if (typeof requestAnimationFrame === 'function') {
requestAnimationFrame(() => resolve());
} else {
setTimeout(resolve, 0);
}
});
}
function escapeCssIdentForCardSync(value) {
return typeof CSS !== 'undefined' && CSS.escape
? CSS.escape(String(value))
@@ -2447,33 +2466,45 @@
async function flushVisibleCardLibraryUpdates() {
libraryUpdatedFlushTimer = null;
if (libraryUpdatedFlushRunning) {
// Keep pending ids; schedule one follow-up after the in-flight flush
if (pendingLibraryUpdatedIds.size) {
liveSyncLog('Flush already running — deferring follow-up', {
pendingSize: pendingLibraryUpdatedIds.size,
});
scheduleLibraryUpdatedFlush();
}
return;
}
const ids = Array.from(pendingLibraryUpdatedIds);
pendingLibraryUpdatedIds.clear();
liveSyncLog('Flush start', { pendingIds: ids });
if (!ids.length) return;
const matchCounts = {};
const idsToFetch = ids.filter((id) => {
const count = findVisibleCardsForItemId(id).length;
matchCounts[id] = count;
return count > 0;
});
liveSyncLog('Visible card matches', matchCounts);
if (!idsToFetch.length) {
liveSyncLog('No on-page cards for updated ids — skipping fetch');
return;
}
if (typeof ApiClient === 'undefined' || typeof ApiClient.getItems !== 'function') {
liveSyncWarn('ApiClient.getItems unavailable — cannot refresh cards');
return;
}
const userId = ApiClient.getCurrentUserId?.();
liveSyncLog('Fetching items', { userId, idsToFetch, fields: LIVE_SYNC_ITEM_FIELDS });
libraryUpdatedFlushRunning = true;
try {
const matchCounts = {};
const idsToFetch = ids.filter((id) => {
const count = findVisibleCardsForItemId(id).length;
matchCounts[id] = count;
return count > 0;
});
liveSyncLog('Visible card matches', matchCounts);
if (!idsToFetch.length) {
liveSyncLog('No on-page cards for updated ids — skipping fetch');
return;
}
if (typeof ApiClient === 'undefined' || typeof ApiClient.getItems !== 'function') {
liveSyncWarn('ApiClient.getItems unavailable — cannot refresh cards');
return;
}
const userId = ApiClient.getCurrentUserId?.();
liveSyncLog('Fetching items', { userId, idsToFetch, fields: LIVE_SYNC_ITEM_FIELDS });
const response = await ApiClient.getItems(userId, {
Ids: idsToFetch.join(','),
Recursive: false,
@@ -2494,8 +2525,8 @@
})),
});
let rebuilt = 0;
let skipped = 0;
// Flatten id → cards into a rebuild work list, then process in rAF batches
const work = [];
idsToFetch.forEach((id) => {
const freshItem = itemsById.get(normalizeItemIdKey(id));
if (!freshItem) {
@@ -2505,13 +2536,29 @@
const cards = findVisibleCardsForItemId(id);
liveSyncLog('Replacing cards for item', { id, cardCount: cards.length });
cards.forEach((card) => {
work.push({ card, freshItem });
});
});
let rebuilt = 0;
let skipped = 0;
for (let i = 0; i < work.length; i += LIVE_SYNC_REBUILD_BATCH_SIZE) {
if (i > 0) await yieldToMainThread();
const batch = work.slice(i, i + LIVE_SYNC_REBUILD_BATCH_SIZE);
batch.forEach(({ card, freshItem }) => {
if (replaceVisibleCardWithFreshItem(card, freshItem)) rebuilt += 1;
else skipped += 1;
});
});
liveSyncLog('Flush complete', { rebuilt, skipped });
}
liveSyncLog('Flush complete', { rebuilt, skipped, workItems: work.length });
} catch (err) {
console.error('[KefinTweaks CardBuilder][LiveSync] Visible card live sync failed:', err);
} finally {
libraryUpdatedFlushRunning = false;
// Ids queued during this flush — schedule another pass
if (pendingLibraryUpdatedIds.size) {
scheduleLibraryUpdatedFlush();
}
}
}
@@ -2528,9 +2575,9 @@
pendingSizeBefore: before,
pendingSizeAfter: pendingLibraryUpdatedIds.size,
debounceMs: LIBRARY_UPDATED_DEBOUNCE_MS,
flushRunning: libraryUpdatedFlushRunning,
});
if (libraryUpdatedFlushTimer) clearTimeout(libraryUpdatedFlushTimer);
libraryUpdatedFlushTimer = setTimeout(flushVisibleCardLibraryUpdates, LIBRARY_UPDATED_DEBOUNCE_MS);
scheduleLibraryUpdatedFlush();
}
function handleLibraryChangedMessage(msg) {