Files
KVideo/lib/utils/sync-records.ts
T
can4hou6joeng4 b63f94fed4 fix(sync): 丢弃缺少 videoId 的同步记录,避免整页崩溃
服务端同步恢复回来的记录不保证结构完整(旧结构或半截写入)。缺少 videoId
的记录会让 HistoryItem.tsx:24 与 FavoritesItem.tsx:21 在渲染期拼播放地址
时对 undefined 调用 toString(),抛出未捕获异常。由于 getVideoUrl() 是在
渲染 href 时调用的,React 会直接卸载整棵树,首页白屏且刷新无效——该账号
只能靠直接改库才能恢复。

新增 lib/utils/sync-records.ts 做可渲染性判定(要求 videoId、source、
title 齐备),在云端拉取入口 useCloudSync 与 HistoryList / FavoritesList
各过滤一次:脏数据进不来,已经落库的也渲染不出来。
2026-07-31 18:28:35 +08:00

33 lines
1.3 KiB
TypeScript

import type { FavoriteItem, VideoHistoryItem } from '@/lib/types';
function hasIdentity(value: unknown): boolean {
if (!value || typeof value !== 'object') return false;
const record = value as { videoId?: unknown; source?: unknown; title?: unknown };
const videoId = record.videoId;
const hasVideoId = typeof videoId === 'string' ? videoId.length > 0 : typeof videoId === 'number';
return hasVideoId && typeof record.source === 'string' && typeof record.title === 'string';
}
/**
* Records restored from server-side sync are not guaranteed to be well formed —
* they may come from an older schema or a partial write. Rendering one without
* `videoId` throws while building the player URL, which takes down the whole
* page, so anything lacking a usable identity is dropped on the way in.
*/
export function isRenderableHistoryItem(value: unknown): value is VideoHistoryItem {
return hasIdentity(value);
}
export function isRenderableFavoriteItem(value: unknown): value is FavoriteItem {
return hasIdentity(value);
}
export function keepRenderableHistory(items: unknown): VideoHistoryItem[] {
return Array.isArray(items) ? items.filter(isRenderableHistoryItem) : [];
}
export function keepRenderableFavorites(items: unknown): FavoriteItem[] {
return Array.isArray(items) ? items.filter(isRenderableFavoriteItem) : [];
}