fix(server-storage): keep a playlist id like __proto__ when filtering a read

withoutDeleted() builds its maps without a prototype, so a surviving
playlist whose id is __proto__ stays a key instead of changing the
object's prototype and losing its favorites and tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-13 09:41:12 +02:00
1 parent 132d6ccc63
commit b4eedb0d8d
2 files changed
+36 -2

No files matched your search

@@ -1,4 +1,4 @@
import { applyEdit } from './server-user-data-edit';
import { applyEdit, withoutDeleted } from './server-user-data-edit';
describe('applyEdit', () => {
it('adds the new favorite to what the server already has', () => {
@@ -59,3 +59,36 @@ describe('applyEdit', () => {
expect(applyEdit(['x'], ['x'], undefined)).toBeUndefined();
});
});
describe('withoutDeleted', () => {
it('drops deleted playlists and keeps every other id', () => {
const read = withoutDeleted(
{
entries: {
'xtream-favorites': JSON.parse(
'{"gone":["a"],"__proto__":["b"],"kept":["c"]}'
),
'xtream-recent-items': {},
'xtream-collection-items': {},
'xtream-playback-positions': {},
},
tags: {
'xtream-favorites': JSON.parse(
'{"gone":"t1","__proto__":"t2","kept":"t3"}'
),
'xtream-recent-items': {},
'xtream-collection-items': {},
'xtream-playback-positions': {},
},
},
['gone']
);
expect(Object.keys(read.entries['xtream-favorites'])).toEqual([
'__proto__',
'kept',
]);
expect(read.entries['xtream-favorites']['__proto__']).toEqual(['b']);
expect(read.tags?.['xtream-favorites']['__proto__']).toBe('t2');
});
});
@@ -161,7 +161,8 @@ export function withoutDeleted(
return read;
}
const keep = <T>(map: Record<string, T> = {}): Record<string, T> => {
const kept: Record<string, T> = {};
// No prototype: a playlist id such as `__proto__` stays a key.
const kept: Record<string, T> = Object.create(null);
for (const [id, value] of Object.entries(map)) {
if (!deleted.some((gone) => gone === undefined || gone === id)) {
kept[id] = value;