Treat successful but empty live projections with existing live input as stale, reject them on the frontend, and keep the previous visible channel state during rebuild failures. Add DOM coverage for the Settings analysis flow and backend serialization coverage for empty projection state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
61 lines
2.8 KiB
JavaScript
61 lines
2.8 KiB
JavaScript
(function (root, factory) {
|
|
const service = factory();
|
|
if (typeof module === 'object' && module.exports) module.exports = service;
|
|
if (root) root.LiveChannelProjection = service;
|
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
function mapResponse(response, sources = []) {
|
|
if (!response || !Array.isArray(response.channels)) {
|
|
throw new Error('Invalid live channel projection response');
|
|
}
|
|
|
|
const sourceById = new Map(sources.map(source => [String(source.id), source]));
|
|
const favorites = new Set();
|
|
const hiddenItems = new Set();
|
|
const channels = [];
|
|
|
|
response.channels.forEach(logicalChannel => {
|
|
if (!logicalChannel || !Array.isArray(logicalChannel.variants)) return;
|
|
logicalChannel.variants.forEach(variant => {
|
|
const source = sourceById.get(String(variant.sourceId));
|
|
const id = String(variant.id);
|
|
const mapped = {
|
|
...variant,
|
|
id,
|
|
logicalId: logicalChannel.id,
|
|
logicalFingerprint: logicalChannel.fingerprint || logicalChannel.id,
|
|
logicalDisplayName: logicalChannel.displayName || logicalChannel.name,
|
|
streamId: variant.streamId ?? id,
|
|
tvgId: variant.tvgId ?? variant.epg_channel_id ?? null,
|
|
tvgLogo: variant.tvgLogo ?? variant.streamIcon ?? variant.stream_icon ?? '',
|
|
url: variant.url ?? variant.streamUrl ?? variant.stream_url ?? '',
|
|
groupTitle: variant.groupTitle ?? variant.categoryName ?? variant.categoryId ?? 'Uncategorized',
|
|
sourceType: variant.sourceType ?? source?.type ?? 'xtream',
|
|
sourceName: variant.sourceName ?? source?.name ?? ''
|
|
};
|
|
channels.push(mapped);
|
|
const key = `${mapped.sourceId}:${id}`;
|
|
if (variant.isFavorite) favorites.add(key);
|
|
if (variant.isHidden) hiddenItems.add(`channel:${mapped.sourceId}:${mapped.streamId || id}`);
|
|
});
|
|
});
|
|
|
|
return { channels, favorites, hiddenItems, projection: response.projection || {} };
|
|
}
|
|
|
|
function canUse(response) {
|
|
if (!response || !Array.isArray(response.channels) || response.projection?.stale) return false;
|
|
|
|
const projection = response.projection || {};
|
|
const expectedChannelCount = [
|
|
projection.total,
|
|
projection.processed,
|
|
projection.catalogCount,
|
|
projection.sourceChannelCount
|
|
].some(count => Number(count) > 0);
|
|
|
|
return response.channels.length > 0 || !expectedChannelCount;
|
|
}
|
|
|
|
return { canUse, mapResponse };
|
|
});
|