@francofantomius/jellyfin 0.4.0 → 0.4.2

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.
package/README.md CHANGED
@@ -6,7 +6,7 @@ A modern, lightweight, modular, and type-safe Jellyfin SDK for Node.js and the B
6
6
  [![Documentation](https://img.shields.io/badge/docs-Material%20Design%203-6f42c1.svg)](https://francofantomius.github.io/jellyfin-sdk/)
7
7
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
8
 
9
- **Current Version: `v0.4.0`** (THE PROJECT IS BEING ACTIVELY DEVELOPED. WAIT FOR VERSION 0.5.0 TO USE IT)
9
+ **Current Version: `v0.4.2`**
10
10
 
11
11
 
12
12
  ---
package/dist/index.cjs CHANGED
@@ -27,7 +27,10 @@ var JellyfinAuthError = class extends JellyfinApiError {
27
27
  // src/http.ts
28
28
  function cleanUrl(url) {
29
29
  if (!url) return "";
30
- let cleaned = url.trim().replace(/\/+$/, "");
30
+ let cleaned = url.trim();
31
+ while (cleaned.endsWith("/")) {
32
+ cleaned = cleaned.slice(0, -1);
33
+ }
31
34
  if (!/^https?:\/\//i.test(cleaned)) {
32
35
  cleaned = "https://" + cleaned;
33
36
  }
@@ -1188,6 +1191,7 @@ var MediaModule = class {
1188
1191
  const qualityResolved = resolveAudioQuality(options.quality);
1189
1192
  const maxStreamingBitrate = options.maxStreamingBitrate ?? qualityResolved.maxStreamingBitrate;
1190
1193
  const {
1194
+ mediaSourceId,
1191
1195
  static: isStatic = false,
1192
1196
  startTimeTicks = 0,
1193
1197
  container = "opus,mp3|mp3,aac,m4a,m4b,flac,wav,ogg",
@@ -1198,12 +1202,14 @@ var MediaModule = class {
1198
1202
  const endpoint = isStatic ? `/Audio/${itemId}/stream` : `/Audio/${itemId}/universal`;
1199
1203
  const url = new URL(`${serverUrl}${endpoint}`);
1200
1204
  if (token && useQueryToken) {
1205
+ url.searchParams.append("ApiKey", token);
1201
1206
  url.searchParams.append("api_key", token);
1202
1207
  url.searchParams.append("X-Emby-Token", token);
1203
1208
  }
1204
1209
  if (userId) url.searchParams.append("UserId", userId);
1205
1210
  const targetDeviceId = options.deviceId !== void 0 ? options.deviceId : clientInfo.deviceId;
1206
1211
  if (targetDeviceId) url.searchParams.append("DeviceId", targetDeviceId);
1212
+ if (mediaSourceId) url.searchParams.append("MediaSourceId", mediaSourceId);
1207
1213
  if (isStatic) {
1208
1214
  url.searchParams.append("static", "true");
1209
1215
  } else {
@@ -1234,6 +1240,7 @@ var MediaModule = class {
1234
1240
  const qualityResolved = resolveAudioQuality(options.quality);
1235
1241
  const maxStreamingBitrate = options.maxStreamingBitrate ?? qualityResolved.maxStreamingBitrate;
1236
1242
  const {
1243
+ mediaSourceId,
1237
1244
  startTimeTicks = 0,
1238
1245
  audioCodec = "aac,mp3",
1239
1246
  segmentLength = 3,
@@ -1241,13 +1248,16 @@ var MediaModule = class {
1241
1248
  } = options;
1242
1249
  const url = new URL(`${serverUrl}/Audio/${itemId}/master.m3u8`);
1243
1250
  if (token && useQueryToken) {
1251
+ url.searchParams.append("ApiKey", token);
1244
1252
  url.searchParams.append("api_key", token);
1245
1253
  url.searchParams.append("X-Emby-Token", token);
1246
1254
  }
1247
1255
  if (userId) url.searchParams.append("UserId", userId);
1248
1256
  const targetDeviceId = options.deviceId !== void 0 ? options.deviceId : clientInfo.deviceId;
1249
1257
  if (targetDeviceId) url.searchParams.append("DeviceId", targetDeviceId);
1250
- url.searchParams.append("MediaSourceId", itemId);
1258
+ if (mediaSourceId) {
1259
+ url.searchParams.append("MediaSourceId", mediaSourceId);
1260
+ }
1251
1261
  url.searchParams.append("AudioCodec", audioCodec);
1252
1262
  url.searchParams.append("TranscodingContainer", "ts");
1253
1263
  url.searchParams.append("TranscodingProtocol", "hls");
@@ -1293,6 +1303,7 @@ var MediaModule = class {
1293
1303
  const endpoint = container && !container.includes(",") ? `/Videos/${itemId}/stream.${container}` : `/Videos/${itemId}/stream`;
1294
1304
  const url = new URL(`${serverUrl}${endpoint}`);
1295
1305
  if (token && useQueryToken) {
1306
+ url.searchParams.append("ApiKey", token);
1296
1307
  url.searchParams.append("api_key", token);
1297
1308
  url.searchParams.append("X-Emby-Token", token);
1298
1309
  }
@@ -1358,6 +1369,7 @@ var MediaModule = class {
1358
1369
  } = options;
1359
1370
  const url = new URL(`${serverUrl}/Videos/${itemId}/master.m3u8`);
1360
1371
  if (token && useQueryToken) {
1372
+ url.searchParams.append("ApiKey", token);
1361
1373
  url.searchParams.append("api_key", token);
1362
1374
  url.searchParams.append("X-Emby-Token", token);
1363
1375
  }
@@ -1400,6 +1412,7 @@ var MediaModule = class {
1400
1412
  const sourceId = mediaSourceId || itemId;
1401
1413
  const url = new URL(`${serverUrl}/Videos/${itemId}/${sourceId}/Subtitles/${subtitleStreamIndex}/Stream.${format}`);
1402
1414
  if (token && useQueryToken) {
1415
+ url.searchParams.append("ApiKey", token);
1403
1416
  url.searchParams.append("api_key", token);
1404
1417
  }
1405
1418
  if (startTimeTicks > 0) {
@@ -1473,6 +1486,7 @@ var MediaModule = class {
1473
1486
  if (!serverUrl || !itemId) return "";
1474
1487
  const url = new URL(`${serverUrl}/Items/${itemId}/Download`);
1475
1488
  if (token && useQueryToken) {
1489
+ url.searchParams.append("ApiKey", token);
1476
1490
  url.searchParams.append("api_key", token);
1477
1491
  }
1478
1492
  if (filename) {
@@ -1519,13 +1533,17 @@ var MediaModule = class {
1519
1533
  /**
1520
1534
  * Returns authentication headers for media streaming requests (e.g., configuring hls.js xhrSetup).
1521
1535
  * Crucial for Jellyfin 12+ servers where query-based auth (?api_key=) is disabled.
1536
+ * Pass { legacy: false } to only send Authorization and avoid conflicting headers or reverse-proxy CORS rejections.
1522
1537
  */
1523
- getStreamHeaders() {
1538
+ getStreamHeaders(options = {}) {
1524
1539
  const authHeader = this.http.getAuthHeader();
1525
- return {
1526
- Authorization: authHeader,
1527
- "X-Emby-Authorization": authHeader
1540
+ const headers = {
1541
+ Authorization: authHeader
1528
1542
  };
1543
+ if (options.legacy ?? true) {
1544
+ headers["X-Emby-Authorization"] = authHeader;
1545
+ }
1546
+ return headers;
1529
1547
  }
1530
1548
  };
1531
1549
 
@@ -1662,7 +1680,7 @@ var OfflineStorageManager = class {
1662
1680
  request.onerror = () => resolve(null);
1663
1681
  });
1664
1682
  } catch (err) {
1665
- console.warn(`[Jellyfin Offline] getDownloadedRecord error for ${id}:`, err);
1683
+ console.warn("[Jellyfin Offline] getDownloadedRecord error for %s:", id, err);
1666
1684
  return null;
1667
1685
  }
1668
1686
  }
@@ -2417,7 +2435,7 @@ var WebSocketModule = class {
2417
2435
  try {
2418
2436
  listener(...args);
2419
2437
  } catch (e) {
2420
- console.error(`[Jellyfin SDK] WebSocket listener error on '${event}':`, e);
2438
+ console.error("[Jellyfin SDK] WebSocket listener error on '%s':", event, e);
2421
2439
  }
2422
2440
  });
2423
2441
  }
@@ -2857,6 +2875,7 @@ var TrickplayModule = class {
2857
2875
  const { mediaSourceId, useQueryToken = true } = options;
2858
2876
  const url = new URL(`${serverUrl}/Videos/${itemId}/Trickplay/${width}/tiles.m3u8`);
2859
2877
  if (token && useQueryToken) {
2878
+ url.searchParams.append("ApiKey", token);
2860
2879
  url.searchParams.append("api_key", token);
2861
2880
  url.searchParams.append("X-Emby-Token", token);
2862
2881
  }
@@ -2875,6 +2894,7 @@ var TrickplayModule = class {
2875
2894
  const { mediaSourceId, useQueryToken = true } = options;
2876
2895
  const url = new URL(`${serverUrl}/Videos/${itemId}/Trickplay/${width}/${index}.jpg`);
2877
2896
  if (token && useQueryToken) {
2897
+ url.searchParams.append("ApiKey", token);
2878
2898
  url.searchParams.append("api_key", token);
2879
2899
  url.searchParams.append("X-Emby-Token", token);
2880
2900
  }
@@ -3065,6 +3085,7 @@ var ChaptersModule = class {
3065
3085
  } = options;
3066
3086
  const url = new URL(`${serverUrl}/Items/${itemId}/Images/Chapter/${chapterIndex}`);
3067
3087
  if (token && useQueryToken) {
3088
+ url.searchParams.append("ApiKey", token);
3068
3089
  url.searchParams.append("api_key", token);
3069
3090
  url.searchParams.append("X-Emby-Token", token);
3070
3091
  }
@@ -3749,7 +3770,7 @@ var JellyfinClient = class {
3749
3770
  this.cache = new MemoryCacheAdapter();
3750
3771
  this.clientInfo = {
3751
3772
  name: options.clientInfo?.name || "@francofantomius/jellyfin",
3752
- version: options.clientInfo?.version || "0.4.0",
3773
+ version: options.clientInfo?.version || "0.4.2",
3753
3774
  device: options.clientInfo?.device || (typeof window !== "undefined" ? "Web Browser" : "Node.js"),
3754
3775
  deviceId: options.clientInfo?.deviceId || generateDeviceId()
3755
3776
  };
@@ -3871,7 +3892,7 @@ var JellyfinClient = class {
3871
3892
  try {
3872
3893
  fn(...args);
3873
3894
  } catch (e) {
3874
- console.error(`[Jellyfin SDK] Event handler error on '${event}':`, e);
3895
+ console.error("[Jellyfin SDK] Event handler error on '%s':", event, e);
3875
3896
  }
3876
3897
  });
3877
3898
  }