ytdlp-react-native 1.0.0 → 1.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +54 -1
  2. package/README.md +422 -267
  3. package/android/src/main/AndroidManifest.xml +20 -1
  4. package/android/src/main/java/expo/modules/ytdlp/ExpoYtDlpModule.kt +8 -0
  5. package/android/src/main/java/expo/modules/ytdlp/YtDlpDownloadManager.kt +177 -16
  6. package/android/src/main/java/expo/modules/ytdlp/YtDlpEngine.kt +35 -4
  7. package/android/src/main/java/expo/modules/ytdlp/YtDlpForegroundService.kt +241 -0
  8. package/android/src/main/java/expo/modules/ytdlp/YtDlpTask.kt +44 -2
  9. package/build/ExpoYtDlpModule.d.ts +2 -0
  10. package/build/ExpoYtDlpModule.d.ts.map +1 -1
  11. package/build/ExpoYtDlpModule.js.map +1 -1
  12. package/build/YtDlp.d.ts +10 -0
  13. package/build/YtDlp.d.ts.map +1 -1
  14. package/build/YtDlp.js +22 -77
  15. package/build/YtDlp.js.map +1 -1
  16. package/build/constants.d.ts +1 -4
  17. package/build/constants.d.ts.map +1 -1
  18. package/build/constants.js +2 -1
  19. package/build/constants.js.map +1 -1
  20. package/build/downloadTask.d.ts +2 -0
  21. package/build/downloadTask.d.ts.map +1 -1
  22. package/build/downloadTask.js +7 -0
  23. package/build/downloadTask.js.map +1 -1
  24. package/build/errors.js +3 -3
  25. package/build/errors.js.map +1 -1
  26. package/build/index.d.ts +3 -1
  27. package/build/index.d.ts.map +1 -1
  28. package/build/index.js +3 -1
  29. package/build/index.js.map +1 -1
  30. package/build/mappers.js +3 -2
  31. package/build/mappers.js.map +1 -1
  32. package/build/serializers.d.ts +5 -0
  33. package/build/serializers.d.ts.map +1 -0
  34. package/build/serializers.js +96 -0
  35. package/build/serializers.js.map +1 -0
  36. package/build/types.d.ts +39 -1
  37. package/build/types.d.ts.map +1 -1
  38. package/build/types.js.map +1 -1
  39. package/package.json +7 -5
  40. package/src/ExpoYtDlpModule.ts +2 -0
  41. package/src/YtDlp.ts +21 -57
  42. package/src/__tests__/errors.test.ts +117 -0
  43. package/src/__tests__/mappers.test.ts +167 -0
  44. package/src/__tests__/serializers.test.ts +72 -0
  45. package/src/constants.ts +3 -1
  46. package/src/downloadTask.ts +9 -0
  47. package/src/errors.ts +3 -3
  48. package/src/index.ts +3 -1
  49. package/src/mappers.ts +3 -2
  50. package/src/serializers.ts +78 -0
  51. package/src/types.ts +47 -1
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Pure serializers that translate public options into the plain map sent to
3
+ * the native layer. Kept free of native imports so they can be unit-tested
4
+ * (AGENTS.md §49, §41).
5
+ */
6
+ import { YtDlpError } from './errors';
7
+ export function serializeExtractOptions(options) {
8
+ if (!options)
9
+ return {};
10
+ const out = {};
11
+ if (options.cookies)
12
+ out.cookies = serializeCookies(options.cookies);
13
+ if (options.proxy)
14
+ out.proxy = options.proxy;
15
+ if (options.userAgent || options.headers) {
16
+ const headers = { ...(options.headers ?? {}) };
17
+ if (options.userAgent)
18
+ headers['User-Agent'] = options.userAgent;
19
+ out.headers = headers;
20
+ }
21
+ return out;
22
+ }
23
+ export function serializeDownloadOptions(options) {
24
+ const out = { url: options.url };
25
+ if (options.format)
26
+ out.format = options.format;
27
+ if (options.output) {
28
+ const output = {};
29
+ if (options.output.directory)
30
+ output.directory = options.output.directory;
31
+ if (options.output.filename)
32
+ output.filename = options.output.filename;
33
+ if (Object.keys(output).length > 0)
34
+ out.output = output;
35
+ }
36
+ if (options.merge != null)
37
+ out.merge = options.merge;
38
+ if (options.ffmpeg?.location) {
39
+ const location = options.ffmpeg.location.trim();
40
+ if (!location) {
41
+ throw new YtDlpError('PROCESSING_FAILED', 'ffmpeg.location must not be empty.');
42
+ }
43
+ out.ffmpeg = { location };
44
+ }
45
+ if (options.cookies)
46
+ out.cookies = serializeCookies(options.cookies);
47
+ if (options.userAgent || options.headers) {
48
+ const headers = { ...(options.headers ?? {}) };
49
+ if (options.userAgent)
50
+ headers['User-Agent'] = options.userAgent;
51
+ out.headers = headers;
52
+ }
53
+ if (options.referer)
54
+ out.referer = options.referer;
55
+ if (options.proxy)
56
+ out.proxy = options.proxy;
57
+ if (options.playlist) {
58
+ const playlist = {};
59
+ if (options.playlist.enabled != null)
60
+ playlist.enabled = options.playlist.enabled;
61
+ if (options.playlist.start != null)
62
+ playlist.start = options.playlist.start;
63
+ if (options.playlist.end != null)
64
+ playlist.end = options.playlist.end;
65
+ if (Object.keys(playlist).length > 0)
66
+ out.playlist = playlist;
67
+ }
68
+ if (options.network) {
69
+ const network = {};
70
+ if (options.network.timeout != null)
71
+ network.timeout = options.network.timeout;
72
+ if (options.network.retries != null)
73
+ network.retries = options.network.retries;
74
+ if (Object.keys(network).length > 0)
75
+ out.network = network;
76
+ }
77
+ if (options.subtitles) {
78
+ const subtitles = {};
79
+ if (options.subtitles.enabled != null)
80
+ subtitles.enabled = options.subtitles.enabled;
81
+ if (options.subtitles.languages?.length)
82
+ subtitles.languages = options.subtitles.languages;
83
+ if (options.subtitles.autoGenerated != null)
84
+ subtitles.autoGenerated = options.subtitles.autoGenerated;
85
+ if (Object.keys(subtitles).length > 0)
86
+ out.subtitles = subtitles;
87
+ }
88
+ return out;
89
+ }
90
+ export function serializeCookies(cookies) {
91
+ if (cookies.source === 'file' && cookies.path) {
92
+ return { path: cookies.path };
93
+ }
94
+ throw new YtDlpError('INVALID_URL', 'Cookies must reference a cookies file path.');
95
+ }
96
+ //# sourceMappingURL=serializers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializers.js","sourceRoot":"","sources":["../src/serializers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure serializers that translate public options into the plain map sent to\n * the native layer. Kept free of native imports so they can be unit-tested\n * (AGENTS.md §49, §41).\n */\nimport { YtDlpError } from './errors';\nimport type { CookieOptions, DownloadOptions, ExtractOptions } from './types';\n\nexport function serializeExtractOptions(\n options: ExtractOptions | undefined\n): Record<string, unknown> {\n if (!options) return {};\n const out: Record<string, unknown> = {};\n if (options.cookies) out.cookies = serializeCookies(options.cookies);\n if (options.proxy) out.proxy = options.proxy;\n if (options.userAgent || options.headers) {\n const headers: Record<string, string> = { ...(options.headers ?? {}) };\n if (options.userAgent) headers['User-Agent'] = options.userAgent;\n out.headers = headers;\n }\n return out;\n}\n\nexport function serializeDownloadOptions(options: DownloadOptions): Record<string, unknown> {\n const out: Record<string, unknown> = { url: options.url };\n if (options.format) out.format = options.format;\n if (options.output) {\n const output: Record<string, unknown> = {};\n if (options.output.directory) output.directory = options.output.directory;\n if (options.output.filename) output.filename = options.output.filename;\n if (Object.keys(output).length > 0) out.output = output;\n }\n if (options.merge != null) out.merge = options.merge;\n if (options.ffmpeg?.location) {\n const location = options.ffmpeg.location.trim();\n if (!location) {\n throw new YtDlpError('PROCESSING_FAILED', 'ffmpeg.location must not be empty.');\n }\n out.ffmpeg = { location };\n }\n if (options.cookies) out.cookies = serializeCookies(options.cookies);\n if (options.userAgent || options.headers) {\n const headers: Record<string, string> = { ...(options.headers ?? {}) };\n if (options.userAgent) headers['User-Agent'] = options.userAgent;\n out.headers = headers;\n }\n if (options.referer) out.referer = options.referer;\n if (options.proxy) out.proxy = options.proxy;\n if (options.playlist) {\n const playlist: Record<string, unknown> = {};\n if (options.playlist.enabled != null) playlist.enabled = options.playlist.enabled;\n if (options.playlist.start != null) playlist.start = options.playlist.start;\n if (options.playlist.end != null) playlist.end = options.playlist.end;\n if (Object.keys(playlist).length > 0) out.playlist = playlist;\n }\n if (options.network) {\n const network: Record<string, unknown> = {};\n if (options.network.timeout != null) network.timeout = options.network.timeout;\n if (options.network.retries != null) network.retries = options.network.retries;\n if (Object.keys(network).length > 0) out.network = network;\n }\n if (options.subtitles) {\n const subtitles: Record<string, unknown> = {};\n if (options.subtitles.enabled != null) subtitles.enabled = options.subtitles.enabled;\n if (options.subtitles.languages?.length) subtitles.languages = options.subtitles.languages;\n if (options.subtitles.autoGenerated != null)\n subtitles.autoGenerated = options.subtitles.autoGenerated;\n if (Object.keys(subtitles).length > 0) out.subtitles = subtitles;\n }\n return out;\n}\n\nexport function serializeCookies(cookies: CookieOptions): Record<string, unknown> {\n if (cookies.source === 'file' && cookies.path) {\n return { path: cookies.path };\n }\n throw new YtDlpError('INVALID_URL', 'Cookies must reference a cookies file path.');\n}\n"]}
package/build/types.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * leak into this surface (see AGENTS.md §68).
6
6
  */
7
7
  import type { YtDlpError } from './errors';
8
- export type DownloadStatus = 'queued' | 'extracting' | 'downloading' | 'processing' | 'completed' | 'cancelled' | 'failed';
8
+ export type DownloadStatus = 'queued' | 'extracting' | 'downloading' | 'processing' | 'paused' | 'completed' | 'cancelled' | 'failed';
9
9
  export type DownloadPhase = 'extracting' | 'downloading' | 'processing';
10
10
  export interface Thumbnail {
11
11
  url: string;
@@ -95,6 +95,20 @@ export interface NetworkOptions {
95
95
  timeout?: number;
96
96
  retries?: number;
97
97
  }
98
+ /**
99
+ * Optional FFmpeg integration (see issue #2). The package does **not** bundle
100
+ * FFmpeg — yt-dlp needs a real executable to merge separate video+audio
101
+ * streams (`bestvideo+bestaudio`), extract/re-encode audio, or embed metadata
102
+ * and thumbnails. Point `location` at an ffmpeg binary already on the device.
103
+ */
104
+ export interface FfmpegOptions {
105
+ /**
106
+ * Absolute filesystem path to an `ffmpeg` executable, or to a directory
107
+ * that contains one. yt-dlp spawns the binary from here during
108
+ * post-processing. An in-process JNI wrapper (e.g. FFmpegKit) is not enough.
109
+ */
110
+ location: string;
111
+ }
98
112
  export interface PlaylistOptions {
99
113
  enabled?: boolean;
100
114
  start?: number;
@@ -114,7 +128,17 @@ export interface DownloadOptions {
114
128
  /** Raw yt-dlp format expression, e.g. `best`, `bestvideo+bestaudio`. */
115
129
  format?: string;
116
130
  output?: OutputOptions;
131
+ /**
132
+ * Set to `true` to request merging (e.g. with `format: 'bestvideo+bestaudio'`).
133
+ * Merging requires FFmpeg — see `ffmpeg`.
134
+ */
117
135
  merge?: boolean;
136
+ /**
137
+ * Optional FFmpeg integration. When unset, FFmpeg-dependent features are
138
+ * rejected with `PROCESSING_FAILED`. When set, `bestvideo+bestaudio` and
139
+ * other post-processing become available.
140
+ */
141
+ ffmpeg?: FfmpegOptions;
118
142
  subtitles?: SubtitleOptions;
119
143
  cookies?: CookieOptions;
120
144
  /** Additional HTTP headers. Validated; secrets are never logged. */
@@ -168,6 +192,20 @@ export interface Subscription {
168
192
  export interface DownloadTask {
169
193
  id: string;
170
194
  cancel(): Promise<void>;
195
+ /**
196
+ * Pause an in-flight download. yt-dlp aborts on the next progress tick and
197
+ * leaves its `.part` file on disk; the task remains registered with status
198
+ * `paused`. Resolves `false` if the task is unknown, already paused, or
199
+ * already finished.
200
+ */
201
+ pause(): Promise<boolean>;
202
+ /**
203
+ * Resume a paused download with the exact same options. yt-dlp continues
204
+ * from the `.part` file by default (byte-range where the source supports it,
205
+ * otherwise the file restarts). Resolves `false` if the task is unknown or
206
+ * not paused.
207
+ */
208
+ resume(): Promise<boolean>;
171
209
  getStatus(): Promise<DownloadStatus>;
172
210
  getProgress(): Promise<DownloadProgress | null>;
173
211
  addListener(event: 'progress', listener: (progress: DownloadProgress) => void): Subscription;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,MAAM,cAAc,GACxB,QAAQ,GAAG,YAAY,GAAG,aAAa,GAAG,YAAY,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEhG,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,aAAa,GAAG,YAAY,CAAC;AAExE,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,OAAO,GAAG,WAAW,CAAC;AAE3F,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,iBAAiB,CAAC;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;CACxB,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,SAAS,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;IACrC,WAAW,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAChD,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,GAAG,YAAY,CAAC;IAC7F,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,YAAY,CAAC;IACzF,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,GAAG,YAAY,CAAC;IAC1F,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,YAAY,CAAC;CAClF;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GACtB,aAAa,GACb,mBAAmB,GACnB,iBAAiB,GACjB,WAAW,GACX,oBAAoB,GACpB,eAAe,GACf,yBAAyB,GACzB,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,mBAAmB,GACnB,eAAe,GACf,aAAa,GACb,sBAAsB,GACtB,SAAS,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Public types for expo-ytdlp-native.\n *\n * These types are the stable contract of the package. Native details never\n * leak into this surface (see AGENTS.md §68).\n */\nimport type { YtDlpError } from './errors';\n\nexport type DownloadStatus =\n 'queued' | 'extracting' | 'downloading' | 'processing' | 'completed' | 'cancelled' | 'failed';\n\nexport type DownloadPhase = 'extracting' | 'downloading' | 'processing';\n\nexport interface Thumbnail {\n url: string;\n width?: number;\n height?: number;\n resolution?: string;\n id?: string;\n}\n\n/** Normalized media information. Every field is optional except `formats`. */\nexport interface VideoInfo {\n id?: string;\n title?: string;\n description?: string;\n uploader?: string;\n uploaderId?: string;\n uploaderUrl?: string;\n channel?: string;\n channelId?: string;\n channelUrl?: string;\n webpageUrl?: string;\n originalUrl?: string;\n thumbnail?: string;\n thumbnails?: Thumbnail[];\n duration?: number;\n durationString?: string;\n uploadDate?: string;\n timestamp?: number;\n viewCount?: number;\n likeCount?: number;\n commentCount?: number;\n ageLimit?: number;\n isLive?: boolean;\n wasLive?: boolean;\n extractor?: string;\n extractorKey?: string;\n webpageUrlDomain?: string;\n formats: Format[];\n}\n\n/** Normalized format model. Unknown native values become `undefined`. */\nexport interface Format {\n id: string;\n url?: string;\n ext?: string;\n protocol?: string;\n format?: string;\n formatNote?: string;\n width?: number;\n height?: number;\n fps?: number;\n vcodec?: string;\n acodec?: string;\n abr?: number;\n vbr?: number;\n tbr?: number;\n filesize?: number;\n filesizeApprox?: number;\n quality?: number;\n audioOnly: boolean;\n videoOnly: boolean;\n hasVideo: boolean;\n hasAudio: boolean;\n language?: string;\n container?: string;\n}\n\nexport interface OutputOptions {\n /**\n * Directory name, relative to the app-specific external storage directory.\n * Single path segments only; `..` is rejected.\n */\n directory?: string;\n /**\n * yt-dlp output template, e.g. `%(title)s.%(ext)s`.\n * Static parts are sanitized; template directives are preserved.\n */\n filename?: string;\n}\n\nexport interface SubtitleOptions {\n enabled?: boolean;\n languages?: string[];\n autoGenerated?: boolean;\n}\n\nexport interface CookieOptions {\n source: 'file';\n path: string;\n}\n\nexport interface NetworkOptions {\n timeout?: number;\n retries?: number;\n}\n\nexport interface PlaylistOptions {\n enabled?: boolean;\n start?: number;\n end?: number;\n}\n\nexport interface ExtractOptions {\n /** Cookies used during extraction (e.g. for login-gated sources). */\n cookies?: CookieOptions;\n /** Additional HTTP headers sent during extraction. Never logged. */\n headers?: Record<string, string>;\n /** Custom `User-Agent`. */\n userAgent?: string;\n proxy?: string;\n}\n\nexport interface DownloadOptions {\n url: string;\n /** Raw yt-dlp format expression, e.g. `best`, `bestvideo+bestaudio`. */\n format?: string;\n output?: OutputOptions;\n merge?: boolean;\n subtitles?: SubtitleOptions;\n cookies?: CookieOptions;\n /** Additional HTTP headers. Validated; secrets are never logged. */\n headers?: Record<string, string>;\n userAgent?: string;\n referer?: string;\n proxy?: string;\n playlist?: PlaylistOptions;\n network?: NetworkOptions;\n}\n\nexport interface DownloadProgress {\n taskId: string;\n status: DownloadStatus;\n phase: DownloadPhase;\n percent?: number;\n downloadedBytes?: number;\n totalBytes?: number;\n speedBytesPerSecond?: number;\n etaSeconds?: number;\n filename?: string;\n}\n\nexport interface DownloadResult {\n taskId: string;\n path?: string;\n uri?: string;\n filename?: string;\n mimeType?: string;\n size?: number;\n duration?: number;\n}\n\nexport type DownloadEventType = 'progress' | 'state' | 'completed' | 'error' | 'cancelled';\n\nexport interface DownloadEvent {\n taskId: string;\n type: DownloadEventType;\n status: DownloadStatus;\n phase: DownloadPhase;\n progress?: DownloadProgress;\n result?: DownloadResult;\n error?: {\n code: string;\n message: string;\n };\n}\n\nexport type DownloadStateEvent = {\n taskId: string;\n status: DownloadStatus;\n};\n\nexport interface Subscription {\n remove(): void;\n}\n\nexport interface DownloadTask {\n id: string;\n cancel(): Promise<void>;\n getStatus(): Promise<DownloadStatus>;\n getProgress(): Promise<DownloadProgress | null>;\n addListener(event: 'progress', listener: (progress: DownloadProgress) => void): Subscription;\n addListener(event: 'state', listener: (state: DownloadStateEvent) => void): Subscription;\n addListener(event: 'completed', listener: (result: DownloadResult) => void): Subscription;\n addListener(event: 'error', listener: (error: YtDlpError) => void): Subscription;\n}\n\nexport interface YtDlpVersion {\n ytDlp: string;\n library: string;\n}\n\nexport type YtDlpErrorCode =\n | 'INVALID_URL'\n | 'EXTRACTION_FAILED'\n | 'DOWNLOAD_FAILED'\n | 'CANCELLED'\n | 'FORMAT_UNAVAILABLE'\n | 'NETWORK_ERROR'\n | 'AUTHENTICATION_REQUIRED'\n | 'GEO_RESTRICTED'\n | 'PRIVATE_CONTENT'\n | 'AGE_RESTRICTED'\n | 'PROCESSING_FAILED'\n | 'STORAGE_ERROR'\n | 'INIT_FAILED'\n | 'UNSUPPORTED_PLATFORM'\n | 'UNKNOWN';\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Public types for expo-ytdlp-native.\n *\n * These types are the stable contract of the package. Native details never\n * leak into this surface (see AGENTS.md §68).\n */\nimport type { YtDlpError } from './errors';\n\nexport type DownloadStatus =\n | 'queued'\n | 'extracting'\n | 'downloading'\n | 'processing'\n | 'paused'\n | 'completed'\n | 'cancelled'\n | 'failed';\n\nexport type DownloadPhase = 'extracting' | 'downloading' | 'processing';\n\nexport interface Thumbnail {\n url: string;\n width?: number;\n height?: number;\n resolution?: string;\n id?: string;\n}\n\n/** Normalized media information. Every field is optional except `formats`. */\nexport interface VideoInfo {\n id?: string;\n title?: string;\n description?: string;\n uploader?: string;\n uploaderId?: string;\n uploaderUrl?: string;\n channel?: string;\n channelId?: string;\n channelUrl?: string;\n webpageUrl?: string;\n originalUrl?: string;\n thumbnail?: string;\n thumbnails?: Thumbnail[];\n duration?: number;\n durationString?: string;\n uploadDate?: string;\n timestamp?: number;\n viewCount?: number;\n likeCount?: number;\n commentCount?: number;\n ageLimit?: number;\n isLive?: boolean;\n wasLive?: boolean;\n extractor?: string;\n extractorKey?: string;\n webpageUrlDomain?: string;\n formats: Format[];\n}\n\n/** Normalized format model. Unknown native values become `undefined`. */\nexport interface Format {\n id: string;\n url?: string;\n ext?: string;\n protocol?: string;\n format?: string;\n formatNote?: string;\n width?: number;\n height?: number;\n fps?: number;\n vcodec?: string;\n acodec?: string;\n abr?: number;\n vbr?: number;\n tbr?: number;\n filesize?: number;\n filesizeApprox?: number;\n quality?: number;\n audioOnly: boolean;\n videoOnly: boolean;\n hasVideo: boolean;\n hasAudio: boolean;\n language?: string;\n container?: string;\n}\n\nexport interface OutputOptions {\n /**\n * Directory name, relative to the app-specific external storage directory.\n * Single path segments only; `..` is rejected.\n */\n directory?: string;\n /**\n * yt-dlp output template, e.g. `%(title)s.%(ext)s`.\n * Static parts are sanitized; template directives are preserved.\n */\n filename?: string;\n}\n\nexport interface SubtitleOptions {\n enabled?: boolean;\n languages?: string[];\n autoGenerated?: boolean;\n}\n\nexport interface CookieOptions {\n source: 'file';\n path: string;\n}\n\nexport interface NetworkOptions {\n timeout?: number;\n retries?: number;\n}\n\n/**\n * Optional FFmpeg integration (see issue #2). The package does **not** bundle\n * FFmpeg — yt-dlp needs a real executable to merge separate video+audio\n * streams (`bestvideo+bestaudio`), extract/re-encode audio, or embed metadata\n * and thumbnails. Point `location` at an ffmpeg binary already on the device.\n */\nexport interface FfmpegOptions {\n /**\n * Absolute filesystem path to an `ffmpeg` executable, or to a directory\n * that contains one. yt-dlp spawns the binary from here during\n * post-processing. An in-process JNI wrapper (e.g. FFmpegKit) is not enough.\n */\n location: string;\n}\n\nexport interface PlaylistOptions {\n enabled?: boolean;\n start?: number;\n end?: number;\n}\n\nexport interface ExtractOptions {\n /** Cookies used during extraction (e.g. for login-gated sources). */\n cookies?: CookieOptions;\n /** Additional HTTP headers sent during extraction. Never logged. */\n headers?: Record<string, string>;\n /** Custom `User-Agent`. */\n userAgent?: string;\n proxy?: string;\n}\n\nexport interface DownloadOptions {\n url: string;\n /** Raw yt-dlp format expression, e.g. `best`, `bestvideo+bestaudio`. */\n format?: string;\n output?: OutputOptions;\n /**\n * Set to `true` to request merging (e.g. with `format: 'bestvideo+bestaudio'`).\n * Merging requires FFmpeg — see `ffmpeg`.\n */\n merge?: boolean;\n /**\n * Optional FFmpeg integration. When unset, FFmpeg-dependent features are\n * rejected with `PROCESSING_FAILED`. When set, `bestvideo+bestaudio` and\n * other post-processing become available.\n */\n ffmpeg?: FfmpegOptions;\n subtitles?: SubtitleOptions;\n cookies?: CookieOptions;\n /** Additional HTTP headers. Validated; secrets are never logged. */\n headers?: Record<string, string>;\n userAgent?: string;\n referer?: string;\n proxy?: string;\n playlist?: PlaylistOptions;\n network?: NetworkOptions;\n}\n\nexport interface DownloadProgress {\n taskId: string;\n status: DownloadStatus;\n phase: DownloadPhase;\n percent?: number;\n downloadedBytes?: number;\n totalBytes?: number;\n speedBytesPerSecond?: number;\n etaSeconds?: number;\n filename?: string;\n}\n\nexport interface DownloadResult {\n taskId: string;\n path?: string;\n uri?: string;\n filename?: string;\n mimeType?: string;\n size?: number;\n duration?: number;\n}\n\nexport type DownloadEventType = 'progress' | 'state' | 'completed' | 'error' | 'cancelled';\n\nexport interface DownloadEvent {\n taskId: string;\n type: DownloadEventType;\n status: DownloadStatus;\n phase: DownloadPhase;\n progress?: DownloadProgress;\n result?: DownloadResult;\n error?: {\n code: string;\n message: string;\n };\n}\n\nexport type DownloadStateEvent = {\n taskId: string;\n status: DownloadStatus;\n};\n\nexport interface Subscription {\n remove(): void;\n}\n\nexport interface DownloadTask {\n id: string;\n cancel(): Promise<void>;\n /**\n * Pause an in-flight download. yt-dlp aborts on the next progress tick and\n * leaves its `.part` file on disk; the task remains registered with status\n * `paused`. Resolves `false` if the task is unknown, already paused, or\n * already finished.\n */\n pause(): Promise<boolean>;\n /**\n * Resume a paused download with the exact same options. yt-dlp continues\n * from the `.part` file by default (byte-range where the source supports it,\n * otherwise the file restarts). Resolves `false` if the task is unknown or\n * not paused.\n */\n resume(): Promise<boolean>;\n getStatus(): Promise<DownloadStatus>;\n getProgress(): Promise<DownloadProgress | null>;\n addListener(event: 'progress', listener: (progress: DownloadProgress) => void): Subscription;\n addListener(event: 'state', listener: (state: DownloadStateEvent) => void): Subscription;\n addListener(event: 'completed', listener: (result: DownloadResult) => void): Subscription;\n addListener(event: 'error', listener: (error: YtDlpError) => void): Subscription;\n}\n\nexport interface YtDlpVersion {\n ytDlp: string;\n library: string;\n}\n\nexport type YtDlpErrorCode =\n | 'INVALID_URL'\n | 'EXTRACTION_FAILED'\n | 'DOWNLOAD_FAILED'\n | 'CANCELLED'\n | 'FORMAT_UNAVAILABLE'\n | 'NETWORK_ERROR'\n | 'AUTHENTICATION_REQUIRED'\n | 'GEO_RESTRICTED'\n | 'PRIVATE_CONTENT'\n | 'AGE_RESTRICTED'\n | 'PROCESSING_FAILED'\n | 'STORAGE_ERROR'\n | 'INIT_FAILED'\n | 'UNSUPPORTED_PLATFORM'\n | 'UNKNOWN';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ytdlp-react-native",
3
- "version": "1.0.0",
3
+ "version": "1.1.0-beta.0",
4
4
  "description": "A native Android Expo module providing a TypeScript API around yt-dlp.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -17,6 +17,7 @@
17
17
  "build": "node internal/module_scripts/build.js",
18
18
  "clean": "node internal/module_scripts/clean.js",
19
19
  "lint": "eslint src/",
20
+ "typecheck": "tsc --noEmit",
20
21
  "test": "node internal/module_scripts/test.js",
21
22
  "prepare": "node internal/module_scripts/prepare.js",
22
23
  "open:ios": "node internal/module_scripts/open-ios.js",
@@ -42,15 +43,16 @@
42
43
  "devDependencies": {
43
44
  "@babel/core": "^7.26.0",
44
45
  "@types/jest": "^29.2.1",
45
- "@types/react": "~19.1.1",
46
- "babel-preset-expo": "~55.0.8",
46
+ "@types/react": "~19.2.18",
47
+ "babel-preset-expo": "~57.0.10",
47
48
  "eslint": "~9.39.4",
48
49
  "eslint-config-universe": "^15.0.3",
49
50
  "expo": "^57.0.13",
50
51
  "jest": "^29.7.0",
51
- "jest-expo": "~55.0.9",
52
+ "jest-expo": "~57.0.5",
52
53
  "prettier": "^3.0.0",
53
- "react-native": "0.82.1",
54
+ "react": "~19.2.3",
55
+ "react-native": "~0.86.3",
54
56
  "typescript": "^5.9.2"
55
57
  },
56
58
  "jest": {
@@ -34,6 +34,8 @@ export declare class ExpoYtDlpNativeModule extends NativeModule<ExpoYtDlpModuleE
34
34
  extractInfo(url: string, options: Record<string, unknown>): Promise<string>;
35
35
  startDownload(options: Record<string, unknown>): Promise<DownloadTaskInfo>;
36
36
  cancelDownload(taskId: string): Promise<boolean>;
37
+ pauseDownload(taskId: string): Promise<boolean>;
38
+ resumeDownload(taskId: string): Promise<boolean>;
37
39
  getDownloadStatus(taskId: string): Promise<DownloadStatusInfo | null>;
38
40
  }
39
41
 
package/src/YtDlp.ts CHANGED
@@ -3,8 +3,8 @@ import { LIBRARY_VERSION, SUPPORTED_PLATFORM_MESSAGE } from './constants';
3
3
  import { YtDlpDownloadTask } from './downloadTask';
4
4
  import { normalizeError, normalizeExtractionError, YtDlpError } from './errors';
5
5
  import { mapVideoInfo } from './mappers';
6
+ import { serializeDownloadOptions, serializeExtractOptions } from './serializers';
6
7
  import type {
7
- CookieOptions,
8
8
  DownloadOptions,
9
9
  DownloadTask,
10
10
  ExtractOptions,
@@ -88,64 +88,28 @@ export async function cancel(taskId: string): Promise<boolean> {
88
88
  }
89
89
  }
90
90
 
91
- function serializeExtractOptions(options: ExtractOptions | undefined): Record<string, unknown> {
92
- if (!options) return {};
93
- const out: Record<string, unknown> = {};
94
- if (options.cookies) out.cookies = serializeCookies(options.cookies);
95
- if (options.proxy) out.proxy = options.proxy;
96
- if (options.userAgent || options.headers) {
97
- const headers: Record<string, string> = { ...(options.headers ?? {}) };
98
- if (options.userAgent) headers['User-Agent'] = options.userAgent;
99
- out.headers = headers;
100
- }
101
- return out;
102
- }
103
-
104
- function serializeDownloadOptions(options: DownloadOptions): Record<string, unknown> {
105
- const out: Record<string, unknown> = { url: options.url };
106
- if (options.format) out.format = options.format;
107
- if (options.output) {
108
- const output: Record<string, unknown> = {};
109
- if (options.output.directory) output.directory = options.output.directory;
110
- if (options.output.filename) output.filename = options.output.filename;
111
- if (Object.keys(output).length > 0) out.output = output;
112
- }
113
- if (options.merge != null) out.merge = options.merge;
114
- if (options.cookies) out.cookies = serializeCookies(options.cookies);
115
- if (options.userAgent || options.headers) {
116
- const headers: Record<string, string> = { ...(options.headers ?? {}) };
117
- if (options.userAgent) headers['User-Agent'] = options.userAgent;
118
- out.headers = headers;
119
- }
120
- if (options.referer) out.referer = options.referer;
121
- if (options.proxy) out.proxy = options.proxy;
122
- if (options.playlist) {
123
- const playlist: Record<string, unknown> = {};
124
- if (options.playlist.enabled != null) playlist.enabled = options.playlist.enabled;
125
- if (options.playlist.start != null) playlist.start = options.playlist.start;
126
- if (options.playlist.end != null) playlist.end = options.playlist.end;
127
- if (Object.keys(playlist).length > 0) out.playlist = playlist;
128
- }
129
- if (options.network) {
130
- const network: Record<string, unknown> = {};
131
- if (options.network.timeout != null) network.timeout = options.network.timeout;
132
- if (options.network.retries != null) network.retries = options.network.retries;
133
- if (Object.keys(network).length > 0) out.network = network;
134
- }
135
- if (options.subtitles) {
136
- const subtitles: Record<string, unknown> = {};
137
- if (options.subtitles.enabled != null) subtitles.enabled = options.subtitles.enabled;
138
- if (options.subtitles.languages?.length) subtitles.languages = options.subtitles.languages;
139
- if (options.subtitles.autoGenerated != null)
140
- subtitles.autoGenerated = options.subtitles.autoGenerated;
141
- if (Object.keys(subtitles).length > 0) out.subtitles = subtitles;
91
+ /**
92
+ * Pause an in-flight download by task id. Returns `false` if the task is
93
+ * unknown, already paused, or already finished.
94
+ */
95
+ export async function pause(taskId: string): Promise<boolean> {
96
+ try {
97
+ const native = requireNative();
98
+ return await native.pauseDownload(taskId);
99
+ } catch (cause) {
100
+ throw normalizeError(cause);
142
101
  }
143
- return out;
144
102
  }
145
103
 
146
- function serializeCookies(cookies: CookieOptions): Record<string, unknown> {
147
- if (cookies.source === 'file' && cookies.path) {
148
- return { path: cookies.path };
104
+ /**
105
+ * Resume a paused download by task id. Returns `false` if the task is unknown
106
+ * or not paused. yt-dlp continues from the partial `.part` file by default.
107
+ */
108
+ export async function resume(taskId: string): Promise<boolean> {
109
+ try {
110
+ const native = requireNative();
111
+ return await native.resumeDownload(taskId);
112
+ } catch (cause) {
113
+ throw normalizeError(cause);
149
114
  }
150
- throw new YtDlpError('INVALID_URL', 'Cookies must reference a cookies file path.');
151
115
  }
@@ -0,0 +1,117 @@
1
+ import { YtDlpError, fromNativeCode, normalizeError, normalizeExtractionError } from '../errors';
2
+
3
+ describe('YtDlpError', () => {
4
+ it('exposes code, message and optional metadata', () => {
5
+ const error = new YtDlpError('CANCELLED', 'Download cancelled', { taskId: 'abc' });
6
+ expect(error).toBeInstanceOf(Error);
7
+ expect(error.name).toBe('YtDlpError');
8
+ expect(error.code).toBe('CANCELLED');
9
+ expect(error.message).toBe('Download cancelled');
10
+ expect(error.taskId).toBe('abc');
11
+ });
12
+
13
+ it('keeps taskId/cause undefined when not provided', () => {
14
+ const error = new YtDlpError('UNKNOWN', 'boom');
15
+ expect(error.taskId).toBeUndefined();
16
+ expect(error.cause).toBeUndefined();
17
+ });
18
+ });
19
+
20
+ describe('normalizeError', () => {
21
+ it('passes through an existing YtDlpError unchanged', () => {
22
+ const original = new YtDlpError('INVALID_URL', 'nope');
23
+ expect(normalizeError(original)).toBe(original);
24
+ });
25
+
26
+ it('parses native-prefixed messages into a typed error', () => {
27
+ const error = normalizeError(new Error('YTD_NATIVE|GEO_RESTRICTED|Blocked'));
28
+ expect(error).toBeInstanceOf(YtDlpError);
29
+ expect(error.code).toBe('GEO_RESTRICTED');
30
+ expect(error.message).toBe('Blocked');
31
+ });
32
+
33
+ it('accepts native-prefixed string throws', () => {
34
+ const error = normalizeError('YTD_NATIVE|NETWORK_ERROR|timeout');
35
+ expect(error.code).toBe('NETWORK_ERROR');
36
+ expect(error.message).toBe('timeout');
37
+ });
38
+
39
+ it('maps unknown native codes to UNKNOWN', () => {
40
+ const error = normalizeError(new Error('YTD_NATIVE|NOT_A_REAL_CODE|oops'));
41
+ expect(error.code).toBe('UNKNOWN');
42
+ });
43
+
44
+ it('falls back to UNKNOWN for plain errors without leaking stack traces', () => {
45
+ const error = normalizeError(new Error('kaboom'));
46
+ expect(error.code).toBe('UNKNOWN');
47
+ expect(error.message).toBe('kaboom');
48
+ });
49
+
50
+ it('uses the fallback message for non-error, non-string causes', () => {
51
+ const error = normalizeError({ weird: true }, 'Fallback');
52
+ expect(error.code).toBe('UNKNOWN');
53
+ expect(error.message).toBe('Fallback');
54
+ });
55
+ });
56
+
57
+ describe('fromNativeCode', () => {
58
+ it('builds an error from a code and message', () => {
59
+ const error = fromNativeCode('STORAGE_ERROR', 'No space');
60
+ expect(error.code).toBe('STORAGE_ERROR');
61
+ expect(error.message).toBe('No space');
62
+ });
63
+
64
+ it('degrades unknown codes to UNKNOWN', () => {
65
+ const error = fromNativeCode('BOGUS', 'x');
66
+ expect(error.code).toBe('UNKNOWN');
67
+ });
68
+ });
69
+
70
+ describe('normalizeExtractionError', () => {
71
+ it('passes through a non-extraction normalized error untouched', () => {
72
+ const error = normalizeExtractionError(new Error('YTD_NATIVE|CANCELLED|stopped'));
73
+ expect(error.code).toBe('CANCELLED');
74
+ });
75
+
76
+ it('classifies connection failures as NETWORK_ERROR', () => {
77
+ const error = normalizeExtractionError(
78
+ new Error('YTD_NATIVE|EXTRACTION_FAILED|HTTP Error 429')
79
+ );
80
+ expect(error.code).toBe('NETWORK_ERROR');
81
+ });
82
+
83
+ it('classifies sign-in prompts as AUTHENTICATION_REQUIRED', () => {
84
+ const error = normalizeExtractionError(
85
+ new Error('YTD_NATIVE|EXTRACTION_FAILED|Please sign in to confirm you are not a bot')
86
+ );
87
+ expect(error.code).toBe('AUTHENTICATION_REQUIRED');
88
+ });
89
+
90
+ it('classifies private content', () => {
91
+ const error = normalizeExtractionError(
92
+ new Error('YTD_NATIVE|EXTRACTION_FAILED|This video is private')
93
+ );
94
+ expect(error.code).toBe('PRIVATE_CONTENT');
95
+ });
96
+
97
+ it('classifies age-restricted content', () => {
98
+ const error = normalizeExtractionError(
99
+ new Error('YTD_NATIVE|EXTRACTION_FAILED|Sign in to confirm your age')
100
+ );
101
+ expect(error.code).toBe('AGE_RESTRICTED');
102
+ });
103
+
104
+ it('classifies geo-restricted content', () => {
105
+ const error = normalizeExtractionError(
106
+ new Error('YTD_NATIVE|EXTRACTION_FAILED|Not available in your country')
107
+ );
108
+ expect(error.code).toBe('GEO_RESTRICTED');
109
+ });
110
+
111
+ it('keeps the base code when nothing matches', () => {
112
+ const error = normalizeExtractionError(
113
+ new Error('YTD_NATIVE|EXTRACTION_FAILED|Unsupported URL')
114
+ );
115
+ expect(error.code).toBe('EXTRACTION_FAILED');
116
+ });
117
+ });