fix(pwa): extract KODIPROP DRM on the web-backend /parse import path

The web-backend keeps its own playlist builder for the PWA URL-import
path, so the shared createPlaylistObject() DRM hook never ran there and
encrypted DASH channels imported by URL reached Shaka without keys.
Apply extractDrmFromRaw() in that builder too and cover the path with a
regression test.

Addresses Codex review on PR #1225.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-07-23 23:40:24 +02:00
1 parent 4e16729bf6
commit 2b3d7fa1a9
2 files changed
+59 -4

No files matched your search

@@ -196,6 +196,50 @@ https://stream.example/news.m3u8`);
);
});
it('extracts KODIPROP ClearKey DRM on the /parse URL-import path', async () => {
const httpClient = new StubHttpClient();
const kid = '00112233445566778899aabbccddeeff';
const key = 'ffeeddccbbaa99887766554433221100';
httpClient.queueResponse(`#EXTM3U
#EXTINF:-1 tvg-id="ck" group-title="DASH",Encrypted DASH
#KODIPROP:inputstream.adaptive.license_type=clearkey
#KODIPROP:inputstream.adaptive.license_key=${kid}:${key}
https://stream.example/enc.mpd
#EXTINF:-1 tvg-id="plain",Plain Channel
https://stream.example/live.m3u8`);
await withServer(
createWebBackendApp({
clientOrigins: ['http://localhost:4200'],
guid: () => 'fixed-id',
httpClient,
resolveHostname: resolvePublicHost,
}),
async (baseUrl) => {
const targetId = await registerProviderTarget(
baseUrl,
'https://provider.example/drm.m3u'
);
const response = await fetch(
`${baseUrl}/parse?targetId=${targetId}`,
{ headers: { Origin: 'http://localhost:4200' } }
);
const body = (await response.json()) as {
playlist: { items: Array<Record<string, unknown>> };
};
expect(response.status).toBe(200);
expect(body.playlist.items).toHaveLength(2);
expect(body.playlist.items[0]['drm']).toEqual({
licenseType: 'clearkey',
supported: true,
clearKeys: { [kid]: key },
});
expect(body.playlist.items[1]['drm']).toBeUndefined();
}
);
});
it('allows browser preflight checks for provider target registration', async () => {
await withServer(
createWebBackendApp({
+15 -4
View File
@@ -8,6 +8,7 @@ import axios from 'axios';
import epgParser from 'epg-parser';
import parser from 'iptv-playlist-parser';
import { normalizeXtreamServerUrl } from '@iptvnator/shared/interfaces';
import { extractDrmFromRaw } from '@iptvnator/shared/m3u-utils';
export interface WebBackendHttpGetOptions {
readonly headers?: Record<string, string>;
@@ -514,10 +515,20 @@ function createPlaylistObject(options: {
count: options.playlist.items.length,
playlist: {
...options.playlist,
items: options.playlist.items.map((item) => ({
id: options.guid(),
...item,
})),
items: options.playlist.items.map((item) => {
// Keep this builder aligned with the shared
// createPlaylistObject() in @iptvnator/shared/m3u-utils:
// KODIPROP ClearKey DRM must survive the /parse URL-import
// path too.
const drm = extractDrmFromRaw(
typeof item['raw'] === 'string' ? item['raw'] : undefined
);
return {
id: options.guid(),
...item,
...(drm ? { drm } : {}),
};
}),
},
importDate: timestamp,
lastUsage: timestamp,