test(performance): add end-to-end Xtream benchmark harness (#1300)

* docs(performance): plan Xtream benchmark

* feat(xtream-mock-server): add deterministic 100k fixture

* style(xtream-mock-server): apply repository formatting

* fix(xtream-mock-server): harden performance fixture data

* feat(xtream-mock-server): add performance control plane

* docs(performance): correct Xtream capture plan

* fix(xtream-mock-server): harden performance controls

* fix(xtream-mock-server): harden control lifecycle

* feat(performance): add Xtream preload markers

* feat(performance): trace Xtream main phases

* feat(performance): mark Xtream store publications

* feat(performance): trace Xtream database phases

* feat(performance): trace Xtream delete cancellation

* feat(performance): capture Xtream phase attribution

* feat(performance): mark Sources Xtream refresh

* test(performance): define Xtream benchmark evidence contracts

* test(performance): add Xtream benchmark runner

* test(performance): surface failure evidence writes

* test(performance): align database read clock

* test(performance): preserve capture failure contracts
This commit is contained in:
4gray authored and GitHub committed 2026-07-28 08:08:07 +02:00
1 parent 5e725a06f3
commit a2fafcfc08
256 files changed
+43760 -883

No files matched your search

@@ -3,6 +3,7 @@ import type {
Playlist,
PlaylistRefreshPayload,
PreloadPerformanceMarker,
XtreamPreloadPerformanceMarker,
} from '@iptvnator/shared/interfaces';
export const PRELOAD_ENVIRONMENT = {
@@ -31,6 +32,7 @@ export interface PreloadPerformanceHarness {
getApi: () => ExposedElectronApi;
getMarkers: () => PreloadPerformanceMarker[];
getSentPayloads: <T>(channel: string) => T[];
getXtreamMarkers: () => XtreamPreloadPerformanceMarker[];
ipcRenderer: MockIpcRenderer;
load: (environment?: PreloadEnvironment) => Promise<void>;
}
@@ -130,6 +132,10 @@ export function createPreloadPerformanceHarness(): PreloadPerformanceHarness {
'IPTVNATOR_PERF_CAPTURE_MARKER'
),
getSentPayloads,
getXtreamMarkers: () =>
getSentPayloads<XtreamPreloadPerformanceMarker>(
'IPTVNATOR_XTREAM_PERF_CAPTURE_MARKER'
),
ipcRenderer,
load: async (environment = {}) => {
jest.resetModules();
@@ -15,6 +15,11 @@ import {
createPreloadPerformanceCapture,
toPreloadPerformanceTargetMethod,
} from './preload-performance-capture';
import {
createXtreamPreloadPerformanceCapture,
isXtreamPreloadPerformanceCaptureEnabled,
toXtreamPreloadPerformanceTargetMethod,
} from './xtream-preload-performance-capture';
import type {
EmbeddedMpvBounds,
EmbeddedMpvRecordingStartOptions,
@@ -78,10 +83,17 @@ const dbSaveContentProgressListeners = new Set<
const shouldTraceRendererApi = isRendererApiTraceEnabled();
const shouldCapturePerformance = isPerformanceCaptureEnabled();
const shouldCaptureXtreamPerformance =
isXtreamPreloadPerformanceCaptureEnabled();
const preloadPerformanceCapture = createPreloadPerformanceCapture(
shouldCapturePerformance,
(channel, marker) => ipcRenderer.send(channel, marker)
);
const xtreamPreloadPerformanceCapture = shouldCaptureXtreamPerformance
? createXtreamPreloadPerformanceCapture(true, (channel, marker) =>
ipcRenderer.send(channel, marker)
)
: null;
function emitRendererTrace(payload: {
method: string;
@@ -105,12 +117,18 @@ function wrapElectronApi<T extends object>(api: T): T {
return Object.fromEntries(
Object.entries(api).map(([name, value]) => {
const isPreloadPerformanceTarget =
toPreloadPerformanceTargetMethod(name) !== null;
const isXtreamPreloadPerformanceTarget =
shouldCaptureXtreamPerformance &&
toXtreamPreloadPerformanceTargetMethod(name) !== null;
if (
typeof value !== 'function' ||
name.startsWith('on') ||
name.startsWith('remove') ||
(!shouldTraceRendererApi &&
!toPreloadPerformanceTargetMethod(name))
!isPreloadPerformanceTarget &&
!isXtreamPreloadPerformanceTarget)
) {
return [name, value];
}
@@ -134,6 +152,9 @@ function wrapElectronApi<T extends object>(api: T): T {
name,
args
);
const xtreamPerformanceCall =
xtreamPreloadPerformanceCapture?.start(name, args) ??
null;
try {
const result = original(...args);
@@ -162,6 +183,9 @@ function wrapElectronApi<T extends object>(api: T): T {
performanceCall,
resolvedValue
);
xtreamPreloadPerformanceCapture?.success(
xtreamPerformanceCall
);
return resolvedValue;
})
.catch((error: unknown) => {
@@ -179,6 +203,9 @@ function wrapElectronApi<T extends object>(api: T): T {
preloadPerformanceCapture.error(
performanceCall
);
xtreamPreloadPerformanceCapture?.error(
xtreamPerformanceCall
);
throw error;
});
}
@@ -198,6 +225,9 @@ function wrapElectronApi<T extends object>(api: T): T {
performanceCall,
result
);
xtreamPreloadPerformanceCapture?.success(
xtreamPerformanceCall
);
return result;
} catch (error) {
@@ -213,6 +243,9 @@ function wrapElectronApi<T extends object>(api: T): T {
});
}
preloadPerformanceCapture.error(performanceCall);
xtreamPreloadPerformanceCapture?.error(
xtreamPerformanceCall
);
throw error;
}
},
@@ -738,11 +771,7 @@ const electronApi: ElectronBridgeApi = {
kind: 'all' | 'vod' | 'series',
limit?: number,
playlistType?:
| 'xtream'
| 'stalker'
| 'm3u-file'
| 'm3u-text'
| 'm3u-url'
'xtream' | 'stalker' | 'm3u-file' | 'm3u-text' | 'm3u-url'
) =>
ipcRenderer.invoke(
'DB_GET_GLOBAL_RECENTLY_ADDED',
@@ -0,0 +1,187 @@
import {
PRELOAD_PERFORMANCE_MARKER_CHANNEL,
XTREAM_PRELOAD_PERFORMANCE_MARKER_CHANNEL,
type XtreamPreloadPerformanceMarker,
} from '@iptvnator/shared/interfaces';
import {
createPreloadPerformanceHarness,
createRefreshPayload,
PRELOAD_ENVIRONMENT,
type PreloadPerformanceHarness,
} from './main.preload.performance.test-helpers';
import {
XTREAM_PRELOAD_REQUEST_PAYLOAD,
XTREAM_PRELOAD_TARGET_CASES,
} from './main.preload.xtream-performance.spec-data';
const MARKER_KEYS = [
'action',
'boundary',
'categoryType',
'contentType',
'ipcCallId',
'itemCount',
'method',
'operationId',
'outcome',
'playlistId',
'schemaVersion',
'sessionId',
'sourceEpochMs',
] as const;
describe('main preload Xtream performance marker contract', () => {
let harness: PreloadPerformanceHarness;
beforeEach(() => {
harness = createPreloadPerformanceHarness();
});
afterEach(() => {
harness.cleanup();
jest.restoreAllMocks();
});
it.each(XTREAM_PRELOAD_TARGET_CASES)(
'marks $method without changing IPC arguments or result identity',
async ({ expectedInvoke, expectedMetadata, invoke, method }) => {
const result = new Proxy({}, { get: () => undefined });
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: '1',
});
harness.ipcRenderer.invoke.mockResolvedValueOnce(result);
await expect(
Promise.resolve(invoke(harness.getApi()))
).resolves.toBe(result);
expect(harness.ipcRenderer.invoke).toHaveBeenCalledTimes(1);
expect(harness.ipcRenderer.invoke).toHaveBeenCalledWith(
...expectedInvoke
);
const markers = harness.getXtreamMarkers();
expect(markers).toHaveLength(2);
expect(markers).toEqual([
expect.objectContaining({
...expectedMetadata,
boundary: 'start',
ipcCallId: 1,
method,
outcome: null,
schemaVersion: 1,
}),
expect.objectContaining({
...expectedMetadata,
boundary: 'end',
ipcCallId: 1,
method,
outcome: 'success',
schemaVersion: 1,
}),
]);
expectFixedMarkerShape(markers);
}
);
it('preserves exact rejection and synchronous throw identities', async () => {
const rejection = new Error('provider-error-secret');
const synchronousError = new Error('provider-sync-secret');
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: '1',
});
harness.ipcRenderer.invoke.mockRejectedValueOnce(rejection);
await expect(
harness.getApi().dbGetContent('playlist-1', 'live')
).rejects.toBe(rejection);
expect(
harness.getXtreamMarkers().map(({ outcome }) => outcome)
).toEqual([null, 'error']);
harness.ipcRenderer.invoke.mockImplementationOnce(() => {
throw synchronousError;
});
expect(() => harness.getApi().dbCancelOperation('operation-1')).toThrow(
synchronousError
);
expect(
harness.getXtreamMarkers().map(({ outcome }) => outcome)
).toEqual([null, 'error', null, 'error']);
});
it('preserves a synchronous target return identity', async () => {
const result = { synchronous: true };
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: '1',
});
harness.ipcRenderer.invoke.mockReturnValueOnce(result);
const actual = harness
.getApi()
.dbCancelOperation('operation-1') as unknown;
expect(actual).toBe(result);
expect(
harness.getXtreamMarkers().map(({ outcome }) => outcome)
).toEqual([null, 'success']);
});
it('keeps M3U and Xtream protocols on their own channels', async () => {
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: '1',
});
await harness.getApi().refreshPlaylist(createRefreshPayload());
await harness.getApi().xtreamRequest(XTREAM_PRELOAD_REQUEST_PAYLOAD);
expect(harness.getMarkers()).toHaveLength(2);
expect(harness.getXtreamMarkers()).toHaveLength(2);
expect(
harness.getSentPayloads(PRELOAD_PERFORMANCE_MARKER_CHANNEL)
).toEqual(harness.getMarkers());
expect(
harness.getSentPayloads(XTREAM_PRELOAD_PERFORMANCE_MARKER_CHANNEL)
).toEqual(harness.getXtreamMarkers());
expect(
harness
.getMarkers()
.every(({ method }) => method === 'refreshPlaylist')
).toBe(true);
expect(
harness
.getXtreamMarkers()
.every(({ method }) => method === 'xtreamRequest')
).toBe(true);
});
});
function expectFixedMarkerShape(
markers: XtreamPreloadPerformanceMarker[]
): void {
for (const [index, marker] of markers.entries()) {
expect(Object.keys(marker).sort()).toEqual([...MARKER_KEYS].sort());
expect(Number.isFinite(marker.sourceEpochMs)).toBe(true);
if (index > 0) {
expect(marker.sourceEpochMs).toBeGreaterThanOrEqual(
markers[index - 1].sourceEpochMs
);
}
}
const serialized = JSON.stringify(markers);
for (const forbidden of [
'provider-password-secret',
'provider-user-secret',
'provider-url-secret',
'request-secret',
'secret-title',
'secret-stream-title',
'secret-favorite-title',
'secret-recent-title',
'provider-search-secret',
'global-search-secret',
'secret-source',
'provider-error-secret',
]) {
expect(serialized).not.toContain(forbidden);
}
}
@@ -0,0 +1,180 @@
import { DEBUG_TRACE_EVENT_CHANNEL } from '../services/debug-trace';
import {
createPreloadPerformanceHarness,
PRELOAD_ENVIRONMENT,
type PreloadPerformanceHarness,
} from './main.preload.performance.test-helpers';
describe('main preload Xtream performance marker gates', () => {
let harness: PreloadPerformanceHarness;
beforeEach(() => {
harness = createPreloadPerformanceHarness();
});
afterEach(() => {
harness.cleanup();
jest.restoreAllMocks();
});
it.each([
undefined,
'',
'0',
'false',
'true',
' TRUE ',
'yes',
'On',
' 1 ',
])(
'requires the exact performance flag value 1, not %p',
async (flagValue) => {
await harness.load(
flagValue === undefined
? {}
: {
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: flagValue,
}
);
await harness.getApi().dbGetContent('playlist-1', 'live');
expect(harness.getXtreamMarkers()).toEqual([]);
}
);
it('enables the Xtream marker only for the exact value 1', async () => {
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: '1',
});
await harness.getApi().dbGetContent('playlist-1', 'live');
expect(harness.getXtreamMarkers()).toHaveLength(2);
});
it('does no Xtream clock or metadata work while its exact gate is disabled', async () => {
const clock = jest
.spyOn(globalThis.performance, 'now')
.mockImplementation(() => {
throw new Error('clock must stay idle');
});
const payload = new Proxy(
{},
{
get: () => {
throw new Error('metadata must stay opaque');
},
}
);
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: 'true',
});
await expect(
harness.getApi().xtreamRequest(payload as never)
).resolves.toEqual({ success: true });
expect(clock).not.toHaveBeenCalled();
expect(harness.getXtreamMarkers()).toEqual([]);
expect(harness.ipcRenderer.invoke.mock.calls[0][0]).toBe(
'XTREAM_REQUEST'
);
expect(harness.ipcRenderer.invoke.mock.calls[0][1]).toBe(payload);
});
it('does not inspect or mark non-target methods in performance-only mode', async () => {
const settings = new Proxy(
{},
{
get: () => {
throw new Error('non-target data must stay opaque');
},
}
);
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: '1',
});
await harness.getApi().updateSettings(settings);
expect(harness.getXtreamMarkers()).toEqual([]);
expect(harness.ipcRenderer.invoke.mock.calls[0][0]).toBe(
'SETTINGS_UPDATE'
);
expect(harness.ipcRenderer.invoke.mock.calls[0][1]).toBe(settings);
});
it('keeps trace-only Xtream calls on the existing debug channel', async () => {
await harness.load({
[PRELOAD_ENVIRONMENT.TRACE_IPC]: '1',
});
await harness.getApi().dbGetContent('playlist-1', 'live');
expect(
harness
.getSentPayloads<Record<string, unknown>>(
DEBUG_TRACE_EVENT_CHANNEL
)
.map(({ method, phase }) => ({ method, phase }))
).toEqual([
{ method: 'dbGetContent', phase: 'start' },
{ method: 'dbGetContent', phase: 'success' },
]);
expect(harness.getXtreamMarkers()).toEqual([]);
});
it('keeps both debug tracing and Xtream markers when both exact gates are enabled', async () => {
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: '1',
[PRELOAD_ENVIRONMENT.TRACE_IPC]: '1',
});
await harness.getApi().dbGetContent('playlist-1', 'live');
expect(harness.getSentPayloads(DEBUG_TRACE_EVENT_CHANNEL)).toHaveLength(
2
);
expect(harness.getXtreamMarkers()).toHaveLength(2);
});
it('keeps the bridge fail-neutral when marker delivery fails', async () => {
const result = { success: true };
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: '1',
});
harness.ipcRenderer.invoke.mockResolvedValueOnce(result);
harness.ipcRenderer.send.mockImplementation((channel: string) => {
if (channel === 'IPTVNATOR_XTREAM_PERF_CAPTURE_MARKER') {
throw new Error('marker sink failure');
}
});
await expect(
harness.getApi().dbGetContent('playlist-1', 'live')
).resolves.toBe(result);
expect(harness.ipcRenderer.invoke).toHaveBeenCalledTimes(1);
});
it('keeps the bridge fail-neutral when the high-resolution clock fails', async () => {
const result = { success: true };
await harness.load({
[PRELOAD_ENVIRONMENT.PERF_CAPTURE]: '1',
});
harness.ipcRenderer.invoke.mockResolvedValueOnce(result);
jest.spyOn(globalThis.performance, 'now').mockImplementation(() => {
throw new Error('clock failure');
});
await expect(
harness.getApi().dbGetContent('playlist-1', 'live')
).resolves.toBe(result);
expect(
harness
.getXtreamMarkers()
.every(({ sourceEpochMs }) => Number.isFinite(sourceEpochMs))
).toBe(true);
});
});
@@ -0,0 +1,230 @@
import {
XtreamCodeActions,
type XtreamPreloadPerformanceMarker,
} from '@iptvnator/shared/interfaces';
import type { ExposedElectronApi } from './main.preload.performance.test-helpers';
type ExpectedMetadata = Pick<
XtreamPreloadPerformanceMarker,
| 'action'
| 'categoryType'
| 'contentType'
| 'itemCount'
| 'operationId'
| 'playlistId'
| 'sessionId'
>;
const EMPTY_METADATA: ExpectedMetadata = {
action: null,
categoryType: null,
contentType: null,
itemCount: null,
operationId: null,
playlistId: null,
sessionId: null,
};
export const XTREAM_PRELOAD_REQUEST_PAYLOAD = {
params: {
action: XtreamCodeActions.GetLiveStreams,
password: 'provider-password-secret',
username: 'provider-user-secret',
},
requestId: 'request-secret',
sessionId: 'session-1',
suppressErrorLog: true,
url: 'http://provider-url-secret',
};
const categoryItems = [
{
category_id: '1',
category_name: 'secret-title',
parent_id: 0,
},
];
const streamItems = [{ name: 'secret-stream-title', stream_id: 1 }];
const favorites = [{ title: 'secret-favorite-title' }];
const recent = [{ title: 'secret-recent-title' }, {}];
const globalSources = [{ playlistId: 'secret-source' }];
const globalOptions = { limit: 25 };
interface TargetCase {
expectedInvoke: readonly unknown[];
expectedMetadata: ExpectedMetadata;
invoke: (api: ExposedElectronApi) => unknown;
method: string;
}
export const XTREAM_PRELOAD_TARGET_CASES: TargetCase[] = [
{
expectedInvoke: ['XTREAM_REQUEST', XTREAM_PRELOAD_REQUEST_PAYLOAD],
expectedMetadata: {
...EMPTY_METADATA,
action: XtreamCodeActions.GetLiveStreams,
sessionId: 'session-1',
},
invoke: (api) => api.xtreamRequest(XTREAM_PRELOAD_REQUEST_PAYLOAD),
method: 'xtreamRequest',
},
{
expectedInvoke: ['XTREAM_CANCEL_SESSION', 'session-2'],
expectedMetadata: { ...EMPTY_METADATA, sessionId: 'session-2' },
invoke: (api) => api.xtreamCancelSession('session-2'),
method: 'xtreamCancelSession',
},
{
expectedInvoke: ['DB_GET_CATEGORIES', 'playlist-1', 'movies'],
expectedMetadata: {
...EMPTY_METADATA,
categoryType: 'movies',
playlistId: 'playlist-1',
},
invoke: (api) => api.dbGetCategories('playlist-1', 'movies'),
method: 'dbGetCategories',
},
{
expectedInvoke: [
'DB_SAVE_CATEGORIES',
'playlist-2',
categoryItems,
'live',
[91],
],
expectedMetadata: {
...EMPTY_METADATA,
categoryType: 'live',
itemCount: 1,
playlistId: 'playlist-2',
},
invoke: (api) =>
api.dbSaveCategories('playlist-2', categoryItems, 'live', [91]),
method: 'dbSaveCategories',
},
{
expectedInvoke: ['DB_GET_CONTENT', 'playlist-3', 'series'],
expectedMetadata: {
...EMPTY_METADATA,
contentType: 'series',
playlistId: 'playlist-3',
},
invoke: (api) => api.dbGetContent('playlist-3', 'series'),
method: 'dbGetContent',
},
{
expectedInvoke: [
'DB_SAVE_CONTENT',
'playlist-4',
streamItems,
'movie',
'operation-4',
],
expectedMetadata: {
...EMPTY_METADATA,
contentType: 'movie',
itemCount: 1,
operationId: 'operation-4',
playlistId: 'playlist-4',
},
invoke: (api) =>
api.dbSaveContent(
'playlist-4',
streamItems as never,
'movie',
'operation-4'
),
method: 'dbSaveContent',
},
{
expectedInvoke: [
'DB_DELETE_XTREAM_CONTENT',
'playlist-5',
'operation-5',
],
expectedMetadata: {
...EMPTY_METADATA,
operationId: 'operation-5',
playlistId: 'playlist-5',
},
invoke: (api) => api.dbDeleteXtreamContent('playlist-5', 'operation-5'),
method: 'dbDeleteXtreamContent',
},
{
expectedInvoke: ['DB_DELETE_PLAYLIST', 'playlist-6', 'operation-6'],
expectedMetadata: {
...EMPTY_METADATA,
operationId: 'operation-6',
playlistId: 'playlist-6',
},
invoke: (api) => api.dbDeletePlaylist('playlist-6', 'operation-6'),
method: 'dbDeletePlaylist',
},
{
expectedInvoke: [
'DB_RESTORE_XTREAM_USER_DATA',
'playlist-7',
favorites,
recent,
'operation-7',
],
expectedMetadata: {
...EMPTY_METADATA,
itemCount: 3,
operationId: 'operation-7',
playlistId: 'playlist-7',
},
invoke: (api) =>
api.dbRestoreXtreamUserData(
'playlist-7',
favorites as never,
recent as never,
'operation-7'
),
method: 'dbRestoreXtreamUserData',
},
{
expectedInvoke: [
'DB_SEARCH_CONTENT',
'playlist-8',
'provider-search-secret',
['live'],
true,
],
expectedMetadata: { ...EMPTY_METADATA, playlistId: 'playlist-8' },
invoke: (api) =>
api.dbSearchContent(
'playlist-8',
'provider-search-secret',
['live'],
true
),
method: 'dbSearchContent',
},
{
expectedInvoke: [
'DB_GLOBAL_SEARCH',
'global-search-secret',
['live'],
true,
globalSources,
globalOptions,
],
expectedMetadata: EMPTY_METADATA,
invoke: (api) =>
api.dbGlobalSearch(
'global-search-secret',
['live'],
true,
globalSources as never,
globalOptions
),
method: 'dbGlobalSearch',
},
{
expectedInvoke: ['DB_CANCEL_OPERATION', 'operation-9'],
expectedMetadata: { ...EMPTY_METADATA, operationId: 'operation-9' },
invoke: (api) => api.dbCancelOperation('operation-9'),
method: 'dbCancelOperation',
},
];
@@ -0,0 +1,127 @@
import type { XtreamPreloadPerformanceMarker } from '@iptvnator/shared/interfaces';
import { createXtreamPreloadPerformanceCapture } from './xtream-preload-performance-capture';
describe('Xtream preload performance capture runtime', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('does zero target metadata or clock work when disabled', () => {
const clock = jest.fn(() => {
throw new Error('clock must not run');
});
const sendMarker = jest.fn();
const args = new Proxy([{}], {
get: () => {
throw new Error('metadata must not run');
},
});
const capture = createXtreamPreloadPerformanceCapture(
false,
sendMarker,
clock
);
expect(capture.start('xtreamRequest', args)).toBeNull();
expect(capture.start('updateSettings', args)).toBeNull();
capture.success(null);
capture.error(null);
expect(clock).not.toHaveBeenCalled();
expect(sendMarker).not.toHaveBeenCalled();
});
it('keeps calls independent with positive ids and nondecreasing finite timestamps', () => {
const markers: XtreamPreloadPerformanceMarker[] = [];
const clockValues = [10.5, 9.5, Number.NaN, 11.25];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
() => clockValues.shift() as number
);
const first = capture.start('dbGetContent', ['playlist-1', 'live']);
const second = capture.start('dbGetContent', ['playlist-2', 'movie']);
capture.success(second);
capture.error(first);
expect(
markers.map(({ boundary, ipcCallId, outcome }) => ({
boundary,
ipcCallId,
outcome,
}))
).toEqual([
{ boundary: 'start', ipcCallId: 1, outcome: null },
{ boundary: 'start', ipcCallId: 2, outcome: null },
{ boundary: 'end', ipcCallId: 2, outcome: 'success' },
{ boundary: 'end', ipcCallId: 1, outcome: 'error' },
]);
expect(
markers.every(({ sourceEpochMs }) => Number.isFinite(sourceEpochMs))
).toBe(true);
expect(markers.map(({ sourceEpochMs }) => sourceEpochMs)).toEqual([
10.5, 10.5, 10.5, 11.25,
]);
});
it.each([
['non-finite', () => Number.NaN],
[
'throwing',
() => {
throw new Error('clock failure');
},
],
])(
'uses a positive finite epoch fallback for a first %s clock sample',
(_description, clock) => {
jest.spyOn(Date, 'now').mockReturnValue(1_700_000_000_000);
const markers: XtreamPreloadPerformanceMarker[] = [];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
clock
);
capture.start('dbGetContent', ['playlist-1', 'live']);
expect(markers[0].sourceEpochMs).toBe(1_700_000_000_000);
expect(markers[0].sourceEpochMs).toBeGreaterThan(0);
expect(Number.isFinite(markers[0].sourceEpochMs)).toBe(true);
}
);
it('swallows sink and clock failures without inspecting result or error values', () => {
const result = new Proxy(
{},
{
get: () => {
throw new Error('result must stay opaque');
},
}
);
const error = new Proxy(
{},
{
get: () => {
throw new Error('error must stay opaque');
},
}
);
const capture = createXtreamPreloadPerformanceCapture(
true,
() => {
throw new Error('sink failure');
},
() => {
throw new Error('clock failure');
}
);
const call = capture.start('dbGetContent', ['playlist-1', 'live']);
expect(() => capture.success(call, result)).not.toThrow();
expect(() => capture.error(call, error)).not.toThrow();
});
});
@@ -0,0 +1,230 @@
import type { XtreamPreloadPerformanceMarker } from '@iptvnator/shared/interfaces';
import { createXtreamPreloadPerformanceCapture } from './xtream-preload-performance-capture';
describe('Xtream preload performance capture safety', () => {
it.each([
['', null],
[' ', null],
['-leading-dash', null],
['contains space', null],
['contains/slash', null],
['x'.repeat(129), null],
[' playlist.ok:1 ', null],
['playlist.ok:1', 'playlist.ok:1'],
['A', 'A'],
])('normalizes identifier %p to %p', (identifier, expected) => {
const markers: XtreamPreloadPerformanceMarker[] = [];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
() => 1_000
);
capture.start('dbDeletePlaylist', [identifier, identifier]);
expect(markers[0]).toMatchObject({
operationId: expected,
playlistId: expected,
});
});
it.each([
['xtreamRequest', [{ params: { action: 'provider-action-secret' } }]],
['dbGetCategories', ['playlist-1', 'vod']],
['dbGetCategories', ['playlist-1', 'LIVE']],
['dbGetContent', ['playlist-1', 'movies']],
['dbGetContent', ['playlist-1', 'vod']],
] as const)(
'turns invalid action or type metadata into null for %s',
(method, args) => {
const markers: XtreamPreloadPerformanceMarker[] = [];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
() => 1_000
);
capture.start(method, args);
expect(markers[0]).toMatchObject({
action: null,
categoryType: null,
contentType: null,
});
expect(JSON.stringify(markers)).not.toContain(
'provider-action-secret'
);
}
);
it('never enumerates provider payloads, reads array entries, or records forbidden strings', () => {
const forbidden = [
'provider-url-secret',
'provider-user-secret',
'provider-password-secret',
'provider-title-secret',
'provider-search-secret',
'provider-result-secret',
'provider-error-secret',
];
const params = Object.defineProperties(
{ action: 'get_series' },
{
password: {
enumerable: true,
get: () => {
throw new Error(forbidden[2]);
},
},
username: {
enumerable: true,
get: () => {
throw new Error(forbidden[1]);
},
},
}
);
const payload = {
params,
sessionId: 'session-1',
title: forbidden[3],
url: forbidden[0],
};
const entryRead = jest.fn(() => {
throw new Error('array entries must stay opaque');
});
const streams = new Array<unknown>(1);
Object.defineProperty(streams, 0, { get: entryRead });
const markers: XtreamPreloadPerformanceMarker[] = [];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
() => 1_000
);
const request = capture.start('xtreamRequest', [payload]);
capture.success(request);
const save = capture.start('dbSaveContent', [
'playlist-1',
streams,
'live',
'operation-1',
]);
capture.error(save);
capture.start('dbSearchContent', [
'playlist-1',
forbidden[4],
['live'],
]);
capture.start('dbGlobalSearch', [
forbidden[4],
['live'],
false,
[{ title: forbidden[3] }],
]);
expect(entryRead).not.toHaveBeenCalled();
const serialized = JSON.stringify(markers);
for (const value of forbidden) {
expect(serialized).not.toContain(value);
}
});
it('swallows hostile argument, payload, params, and array length traps', () => {
const throwingProxy = new Proxy(
{},
{
get: () => {
throw new Error('hostile getter');
},
}
);
const hostileArray = new Proxy([], {
get: () => {
throw new Error('hostile array length');
},
});
const hostileArgs = new Proxy([throwingProxy], {
get: () => {
throw new Error('hostile args');
},
});
const markers: XtreamPreloadPerformanceMarker[] = [];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
() => 1_000
);
expect(() => capture.start('xtreamRequest', hostileArgs)).not.toThrow();
expect(() =>
capture.start('dbSaveCategories', [
'playlist-1',
hostileArray,
'live',
])
).not.toThrow();
expect(markers).toHaveLength(2);
expect(markers[0]).toMatchObject({
action: null,
sessionId: null,
});
expect(markers[1].itemCount).toBeNull();
});
it('swallows revoked array proxies without changing the call', () => {
const { proxy, revoke } = Proxy.revocable([], {});
const markers: XtreamPreloadPerformanceMarker[] = [];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
() => 1_000
);
revoke();
expect(() =>
capture.start('dbSaveContent', [
'playlist-1',
proxy,
'live',
'operation-1',
])
).not.toThrow();
expect(markers[0].itemCount).toBeNull();
});
it('accepts only bounded array lengths and safely sums restore counts', () => {
const markers: XtreamPreloadPerformanceMarker[] = [];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
() => 1_000
);
capture.start('dbSaveCategories', [
'playlist-1',
new Array(1_000_001),
'live',
]);
capture.start('dbRestoreXtreamUserData', [
'playlist-1',
new Array(600_000),
new Array(400_000),
'operation-1',
]);
capture.start('dbRestoreXtreamUserData', [
'playlist-1',
new Array(600_000),
new Array(400_001),
'operation-1',
]);
expect(markers.map(({ itemCount }) => itemCount)).toEqual([
null,
1_000_000,
null,
]);
});
});
@@ -0,0 +1,219 @@
import {
XTREAM_PRELOAD_PERFORMANCE_MARKER_CHANNEL,
XTREAM_PRELOAD_PERFORMANCE_SCHEMA_VERSION,
type XtreamPreloadPerformanceMarker,
} from '@iptvnator/shared/interfaces';
import { XtreamCodeActions } from '@iptvnator/shared/interfaces';
import { createXtreamPreloadPerformanceCapture } from './xtream-preload-performance-capture';
const MARKER_KEYS = [
'action',
'boundary',
'categoryType',
'contentType',
'ipcCallId',
'itemCount',
'method',
'operationId',
'outcome',
'playlistId',
'schemaVersion',
'sessionId',
'sourceEpochMs',
] as const;
const EMPTY_METADATA = {
action: null,
categoryType: null,
contentType: null,
itemCount: null,
operationId: null,
playlistId: null,
sessionId: null,
} as const;
describe('Xtream preload performance capture contract', () => {
it.each([
{
args: [
{
params: { action: XtreamCodeActions.GetLiveStreams },
sessionId: 'session-1',
},
],
expected: {
...EMPTY_METADATA,
action: XtreamCodeActions.GetLiveStreams,
sessionId: 'session-1',
},
method: 'xtreamRequest',
},
{
args: ['session-2'],
expected: { ...EMPTY_METADATA, sessionId: 'session-2' },
method: 'xtreamCancelSession',
},
{
args: ['playlist-1', 'movies'],
expected: {
...EMPTY_METADATA,
categoryType: 'movies',
playlistId: 'playlist-1',
},
method: 'dbGetCategories',
},
{
args: ['playlist-2', [{}, {}, {}], 'live', [99]],
expected: {
...EMPTY_METADATA,
categoryType: 'live',
itemCount: 3,
playlistId: 'playlist-2',
},
method: 'dbSaveCategories',
},
{
args: ['playlist-3', 'series'],
expected: {
...EMPTY_METADATA,
contentType: 'series',
playlistId: 'playlist-3',
},
method: 'dbGetContent',
},
{
args: ['playlist-4', [{}, {}], 'movie', 'operation-4'],
expected: {
...EMPTY_METADATA,
contentType: 'movie',
itemCount: 2,
operationId: 'operation-4',
playlistId: 'playlist-4',
},
method: 'dbSaveContent',
},
{
args: ['playlist-5', 'operation-5'],
expected: {
...EMPTY_METADATA,
operationId: 'operation-5',
playlistId: 'playlist-5',
},
method: 'dbDeleteXtreamContent',
},
{
args: ['playlist-6', 'operation-6'],
expected: {
...EMPTY_METADATA,
operationId: 'operation-6',
playlistId: 'playlist-6',
},
method: 'dbDeletePlaylist',
},
{
args: ['playlist-7', [{}, {}], [{}], 'operation-7'],
expected: {
...EMPTY_METADATA,
itemCount: 3,
operationId: 'operation-7',
playlistId: 'playlist-7',
},
method: 'dbRestoreXtreamUserData',
},
{
args: ['playlist-8', 'provider search', ['live'], true],
expected: {
...EMPTY_METADATA,
playlistId: 'playlist-8',
},
method: 'dbSearchContent',
},
{
args: ['provider search', ['live'], true, [{ id: 'source-1' }]],
expected: EMPTY_METADATA,
method: 'dbGlobalSearch',
},
{
args: ['operation-9'],
expected: { ...EMPTY_METADATA, operationId: 'operation-9' },
method: 'dbCancelOperation',
},
] as const)(
'extracts only safe positional metadata for $method',
({ args, expected, method }) => {
const sendMarker = jest.fn();
const capture = createXtreamPreloadPerformanceCapture(
true,
sendMarker,
() => 1_000.25
);
const call = capture.start(method, args);
expect(call).not.toBeNull();
expect(sendMarker).toHaveBeenCalledWith(
XTREAM_PRELOAD_PERFORMANCE_MARKER_CHANNEL,
{
...expected,
boundary: 'start',
ipcCallId: 1,
method,
outcome: null,
schemaVersion: XTREAM_PRELOAD_PERFORMANCE_SCHEMA_VERSION,
sourceEpochMs: 1_000.25,
}
);
}
);
it('emits fixed-key start and end markers with matching call metadata', () => {
const markers: XtreamPreloadPerformanceMarker[] = [];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
() => 2_000.5
);
const successCall = capture.start('dbGetContent', [
'playlist-1',
'live',
]);
capture.success(successCall);
const errorCall = capture.start('dbCancelOperation', ['operation-2']);
capture.error(errorCall);
expect(
markers.map(({ boundary, ipcCallId, outcome }) => ({
boundary,
ipcCallId,
outcome,
}))
).toEqual([
{ boundary: 'start', ipcCallId: 1, outcome: null },
{ boundary: 'end', ipcCallId: 1, outcome: 'success' },
{ boundary: 'start', ipcCallId: 2, outcome: null },
{ boundary: 'end', ipcCallId: 2, outcome: 'error' },
]);
for (const marker of markers) {
expect(Object.keys(marker).sort()).toEqual([...MARKER_KEYS].sort());
}
});
it('allows every current Xtream action including the legacy alias', () => {
const markers: XtreamPreloadPerformanceMarker[] = [];
const capture = createXtreamPreloadPerformanceCapture(
true,
(_channel, marker) => markers.push(marker),
() => 3_000
);
const actions = Object.values(XtreamCodeActions);
for (const action of actions) {
capture.start('xtreamRequest', [{ params: { action } }]);
}
expect(markers.map(({ action }) => action)).toEqual(actions);
expect(actions).toContain('get_simple_data_table');
expect(actions).toContain('get_simple_date_table');
});
});
@@ -0,0 +1,191 @@
import {
XTREAM_PRELOAD_PERFORMANCE_MARKER_CHANNEL,
XTREAM_PRELOAD_PERFORMANCE_METHOD,
XTREAM_PRELOAD_PERFORMANCE_SCHEMA_VERSION,
type XtreamPreloadPerformanceBoundary,
type XtreamPreloadPerformanceMarker,
type XtreamPreloadPerformanceMethod,
type XtreamPreloadPerformanceOutcome,
} from '@iptvnator/shared/interfaces';
import {
extractXtreamPreloadPerformanceMetadata,
type XtreamPreloadMarkerMetadata,
} from './xtream-preload-performance-metadata';
export interface XtreamPreloadPerformanceCallContext extends XtreamPreloadMarkerMetadata {
ipcCallId: number;
method: XtreamPreloadPerformanceMethod;
}
export interface XtreamPreloadPerformanceCapture {
error: (
call: XtreamPreloadPerformanceCallContext | null,
ignoredError?: unknown
) => void;
start: (
methodName: string,
args: readonly unknown[]
) => XtreamPreloadPerformanceCallContext | null;
success: (
call: XtreamPreloadPerformanceCallContext | null,
ignoredResult?: unknown
) => void;
}
type MarkerSender = (
channel: typeof XTREAM_PRELOAD_PERFORMANCE_MARKER_CHANNEL,
marker: XtreamPreloadPerformanceMarker
) => void;
type EpochReader = () => number;
export function toXtreamPreloadPerformanceTargetMethod(
methodName: string
): XtreamPreloadPerformanceMethod | null {
switch (methodName) {
case XTREAM_PRELOAD_PERFORMANCE_METHOD.XTREAM_REQUEST:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.XTREAM_CANCEL_SESSION:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_GET_CATEGORIES:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_SAVE_CATEGORIES:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_GET_CONTENT:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_SAVE_CONTENT:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_DELETE_XTREAM_CONTENT:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_DELETE_PLAYLIST:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_RESTORE_XTREAM_USER_DATA:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_SEARCH_CONTENT:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_GLOBAL_SEARCH:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_CANCEL_OPERATION:
return methodName;
default:
return null;
}
}
export function isXtreamPreloadPerformanceCaptureEnabled(): boolean {
return process.env['IPTVNATOR_PERF_CAPTURE'] === '1';
}
function readHighResolutionEpochMs(): number {
try {
const timeOrigin = globalThis.performance?.timeOrigin;
const monotonicNow = globalThis.performance?.now?.();
const highResolutionEpoch =
typeof timeOrigin === 'number' && typeof monotonicNow === 'number'
? timeOrigin + monotonicNow
: Number.NaN;
if (Number.isFinite(highResolutionEpoch)) {
return highResolutionEpoch;
}
} catch {
// Fall through to the wall clock.
}
try {
return Date.now();
} catch {
return Number.NaN;
}
}
function readPositiveWallClockEpochMs(): number {
try {
const wallClockEpochMs = Date.now();
return Number.isFinite(wallClockEpochMs) && wallClockEpochMs > 0
? wallClockEpochMs
: 1;
} catch {
return 1;
}
}
function readMonotonicEpochMs(
readEpochMs: EpochReader,
lastSourceEpochMs: number
): number {
let clockValue = Number.NaN;
try {
clockValue = readEpochMs();
} catch {
// Use the last valid sample or the wall clock below.
}
const fallbackEpochMs =
lastSourceEpochMs > 0
? lastSourceEpochMs
: readPositiveWallClockEpochMs();
const sourceEpochMs =
Number.isFinite(clockValue) && clockValue > 0
? clockValue
: fallbackEpochMs;
return Math.max(sourceEpochMs, lastSourceEpochMs);
}
export function createXtreamPreloadPerformanceCapture(
enabled: boolean,
sendMarker: MarkerSender,
readEpochMs: EpochReader = readHighResolutionEpochMs
): XtreamPreloadPerformanceCapture {
let lastSourceEpochMs = 0;
let nextIpcCallId = 1;
function emit(
call: XtreamPreloadPerformanceCallContext,
boundary: XtreamPreloadPerformanceBoundary,
outcome: XtreamPreloadPerformanceOutcome
): void {
const sourceEpochMs = readMonotonicEpochMs(
readEpochMs,
lastSourceEpochMs
);
lastSourceEpochMs = sourceEpochMs;
try {
sendMarker(XTREAM_PRELOAD_PERFORMANCE_MARKER_CHANNEL, {
schemaVersion: XTREAM_PRELOAD_PERFORMANCE_SCHEMA_VERSION,
sourceEpochMs,
ipcCallId: call.ipcCallId,
method: call.method,
boundary,
outcome,
playlistId: call.playlistId,
operationId: call.operationId,
sessionId: call.sessionId,
action: call.action,
categoryType: call.categoryType,
contentType: call.contentType,
itemCount: call.itemCount,
});
} catch {
// Performance instrumentation must never affect the bridge call.
}
}
return {
error: (call) => {
if (call) {
emit(call, 'end', 'error');
}
},
start: (methodName, args) => {
if (!enabled) {
return null;
}
const method = toXtreamPreloadPerformanceTargetMethod(methodName);
if (!method || nextIpcCallId > Number.MAX_SAFE_INTEGER) {
return null;
}
const call = {
ipcCallId: nextIpcCallId,
method,
...extractXtreamPreloadPerformanceMetadata(method, args),
};
nextIpcCallId += 1;
emit(call, 'start', null);
return call;
},
success: (call) => {
if (call) {
emit(call, 'end', 'success');
}
},
};
}
@@ -0,0 +1,190 @@
import {
XTREAM_PRELOAD_PERFORMANCE_METHOD,
XtreamCodeActions,
type XtreamPreloadPerformanceCategoryType,
type XtreamPreloadPerformanceContentType,
type XtreamPreloadPerformanceMarker,
type XtreamPreloadPerformanceMethod,
} from '@iptvnator/shared/interfaces';
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
const MAX_IDENTIFIER_LENGTH = 128;
const MAX_ITEM_COUNT = 1_000_000;
const ALLOWED_ACTIONS = new Set<string>(Object.values(XtreamCodeActions));
const ALLOWED_CATEGORY_TYPES = new Set(['live', 'movies', 'series']);
const ALLOWED_CONTENT_TYPES = new Set(['live', 'movie', 'series']);
export type XtreamPreloadMarkerMetadata = Pick<
XtreamPreloadPerformanceMarker,
| 'action'
| 'categoryType'
| 'contentType'
| 'itemCount'
| 'operationId'
| 'playlistId'
| 'sessionId'
>;
function readValue(value: unknown, property: PropertyKey): unknown {
if (
(typeof value !== 'object' || value === null) &&
typeof value !== 'function'
) {
return undefined;
}
try {
return Reflect.get(value, property);
} catch {
return undefined;
}
}
function readArgument(args: readonly unknown[], index: number): unknown {
return readValue(args, index);
}
function normalizeIdentifier(value: unknown): string | null {
if (typeof value !== 'string') {
return null;
}
return value.length >= 1 &&
value.length <= MAX_IDENTIFIER_LENGTH &&
IDENTIFIER_PATTERN.test(value)
? value
: null;
}
function normalizeAction(value: unknown): XtreamCodeActions | null {
return typeof value === 'string' && ALLOWED_ACTIONS.has(value)
? (value as XtreamCodeActions)
: null;
}
function normalizeCategoryType(
value: unknown
): XtreamPreloadPerformanceCategoryType | null {
return typeof value === 'string' && ALLOWED_CATEGORY_TYPES.has(value)
? (value as XtreamPreloadPerformanceCategoryType)
: null;
}
function normalizeContentType(
value: unknown
): XtreamPreloadPerformanceContentType | null {
return typeof value === 'string' && ALLOWED_CONTENT_TYPES.has(value)
? (value as XtreamPreloadPerformanceContentType)
: null;
}
function readArrayCount(value: unknown): number | null {
let isArray = false;
try {
isArray = Array.isArray(value);
} catch {
return null;
}
if (!isArray) {
return null;
}
const length = readValue(value, 'length');
return typeof length === 'number' &&
Number.isSafeInteger(length) &&
length >= 0 &&
length <= MAX_ITEM_COUNT
? length
: null;
}
function sumArrayCounts(first: unknown, second: unknown): number | null {
const firstCount = readArrayCount(first);
const secondCount = readArrayCount(second);
if (firstCount === null || secondCount === null) {
return null;
}
const total = firstCount + secondCount;
return Number.isSafeInteger(total) && total <= MAX_ITEM_COUNT
? total
: null;
}
function emptyMetadata(): XtreamPreloadMarkerMetadata {
return {
action: null,
categoryType: null,
contentType: null,
itemCount: null,
operationId: null,
playlistId: null,
sessionId: null,
};
}
export function extractXtreamPreloadPerformanceMetadata(
method: XtreamPreloadPerformanceMethod,
args: readonly unknown[]
): XtreamPreloadMarkerMetadata {
const metadata = emptyMetadata();
const first = readArgument(args, 0);
switch (method) {
case XTREAM_PRELOAD_PERFORMANCE_METHOD.XTREAM_REQUEST: {
const params = readValue(first, 'params');
metadata.sessionId = normalizeIdentifier(
readValue(first, 'sessionId')
);
metadata.action = normalizeAction(readValue(params, 'action'));
return metadata;
}
case XTREAM_PRELOAD_PERFORMANCE_METHOD.XTREAM_CANCEL_SESSION:
metadata.sessionId = normalizeIdentifier(first);
return metadata;
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_GET_CATEGORIES:
metadata.playlistId = normalizeIdentifier(first);
metadata.categoryType = normalizeCategoryType(
readArgument(args, 1)
);
return metadata;
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_SAVE_CATEGORIES:
metadata.playlistId = normalizeIdentifier(first);
metadata.itemCount = readArrayCount(readArgument(args, 1));
metadata.categoryType = normalizeCategoryType(
readArgument(args, 2)
);
return metadata;
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_GET_CONTENT:
metadata.playlistId = normalizeIdentifier(first);
metadata.contentType = normalizeContentType(readArgument(args, 1));
return metadata;
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_SAVE_CONTENT:
metadata.playlistId = normalizeIdentifier(first);
metadata.itemCount = readArrayCount(readArgument(args, 1));
metadata.contentType = normalizeContentType(readArgument(args, 2));
metadata.operationId = normalizeIdentifier(readArgument(args, 3));
return metadata;
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_DELETE_XTREAM_CONTENT:
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_DELETE_PLAYLIST:
metadata.playlistId = normalizeIdentifier(first);
metadata.operationId = normalizeIdentifier(readArgument(args, 1));
return metadata;
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_RESTORE_XTREAM_USER_DATA:
metadata.playlistId = normalizeIdentifier(first);
metadata.itemCount = sumArrayCounts(
readArgument(args, 1),
readArgument(args, 2)
);
metadata.operationId = normalizeIdentifier(readArgument(args, 3));
return metadata;
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_SEARCH_CONTENT:
metadata.playlistId = normalizeIdentifier(first);
return metadata;
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_CANCEL_OPERATION:
metadata.operationId = normalizeIdentifier(first);
return metadata;
case XTREAM_PRELOAD_PERFORMANCE_METHOD.DB_GLOBAL_SEARCH:
return metadata;
}
}