@codenkay/video-nsgplayer-core 3.0.1 → 3.0.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/dist/index.d.cts CHANGED
@@ -286,7 +286,7 @@ type PlayerEventMap = {
286
286
  };
287
287
 
288
288
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
289
- declare const PACKAGE_VERSION = "3.0.1";
289
+ declare const PACKAGE_VERSION = "3.0.2";
290
290
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
291
291
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
292
292
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -456,7 +456,8 @@ type HlsEngineOptions = {
456
456
  * (playlists, segments, and AES `enc.key` on CDN hosts).
457
457
  *
458
458
  * Optional `streaming.keyProxyUrlBuilder` (same-origin BFF) or
459
- * `streaming.getKeyAccessToken` (browser Bearer) for Video API–origin keys
459
+ * `streaming.getKeyAccessToken` (Bearer only after signed-query 401/403).
460
+ * CDN keys prefer signed query without Authorization (no CORS preflight).
460
461
  * when query-signed CDN access is insufficient.
461
462
  */
462
463
  declare class HlsEngine {
@@ -673,28 +674,17 @@ type AuthParamsLoaderOptions = {
673
674
  };
674
675
  declare function isEncryptionKeyUrl(rawUrl: string, baseOrigin?: string): boolean;
675
676
  /**
676
- * Always resolve a builtin hls.js loader (XhrLoader).
677
- *
678
- * Never trust `Hls.DefaultConfig.loader` alone — if it was reassigned to a
679
- * custom wrapper, `extends` / nested `inner.load()` chains blow Firefox's
680
- * stack (`InternalError: too much recursion`).
677
+ * Resolve the builtin loader from the **same** `hls.js` module instance the
678
+ * engine loaded. Never statically import `XhrLoader` — that can pull a second
679
+ * hls.js copy into the app bundle after npm pack/publish and break key loads.
681
680
  */
682
681
  declare function resolveBuiltinHlsLoader(HlsClass: typeof Hls): HlsLoaderConstructor;
683
682
  /**
684
683
  * Rewrites an HLS request URL so signed auth query params are present.
685
- *
686
- * AES `enc.key` handling (enterprise priority order):
687
- * 1. `keyProxyUrlBuilder` — same-origin BFF (production; server IP must pass CDN/WAF)
688
- * 2. Otherwise keep CDN/API URL and apply signed `token`/`expires` (and related) query
689
- * 3. Optional `getKeyAccessToken` is applied at load-time (Bearer), not in this rewrite
690
684
  */
691
685
  declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
692
686
  /**
693
- * hls.js loader that:
694
- * - Re-applies signed auth query params on every media request
695
- * - Loads AES `enc.key` via controlled BFF (≤2) → Bearer → query XHR
696
- * with a **session circuit breaker** so a dead Cloudflare BFF is not
697
- * hammered by hls.js retries (which previously caused 9–10× 502s).
687
+ * hls.js loader: auth query rewrite + enterprise AES key loading.
698
688
  */
699
689
  declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoaderConstructor;
700
690
 
package/dist/index.d.ts CHANGED
@@ -286,7 +286,7 @@ type PlayerEventMap = {
286
286
  };
287
287
 
288
288
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
289
- declare const PACKAGE_VERSION = "3.0.1";
289
+ declare const PACKAGE_VERSION = "3.0.2";
290
290
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
291
291
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
292
292
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -456,7 +456,8 @@ type HlsEngineOptions = {
456
456
  * (playlists, segments, and AES `enc.key` on CDN hosts).
457
457
  *
458
458
  * Optional `streaming.keyProxyUrlBuilder` (same-origin BFF) or
459
- * `streaming.getKeyAccessToken` (browser Bearer) for Video API–origin keys
459
+ * `streaming.getKeyAccessToken` (Bearer only after signed-query 401/403).
460
+ * CDN keys prefer signed query without Authorization (no CORS preflight).
460
461
  * when query-signed CDN access is insufficient.
461
462
  */
462
463
  declare class HlsEngine {
@@ -673,28 +674,17 @@ type AuthParamsLoaderOptions = {
673
674
  };
674
675
  declare function isEncryptionKeyUrl(rawUrl: string, baseOrigin?: string): boolean;
675
676
  /**
676
- * Always resolve a builtin hls.js loader (XhrLoader).
677
- *
678
- * Never trust `Hls.DefaultConfig.loader` alone — if it was reassigned to a
679
- * custom wrapper, `extends` / nested `inner.load()` chains blow Firefox's
680
- * stack (`InternalError: too much recursion`).
677
+ * Resolve the builtin loader from the **same** `hls.js` module instance the
678
+ * engine loaded. Never statically import `XhrLoader` — that can pull a second
679
+ * hls.js copy into the app bundle after npm pack/publish and break key loads.
681
680
  */
682
681
  declare function resolveBuiltinHlsLoader(HlsClass: typeof Hls): HlsLoaderConstructor;
683
682
  /**
684
683
  * Rewrites an HLS request URL so signed auth query params are present.
685
- *
686
- * AES `enc.key` handling (enterprise priority order):
687
- * 1. `keyProxyUrlBuilder` — same-origin BFF (production; server IP must pass CDN/WAF)
688
- * 2. Otherwise keep CDN/API URL and apply signed `token`/`expires` (and related) query
689
- * 3. Optional `getKeyAccessToken` is applied at load-time (Bearer), not in this rewrite
690
684
  */
691
685
  declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
692
686
  /**
693
- * hls.js loader that:
694
- * - Re-applies signed auth query params on every media request
695
- * - Loads AES `enc.key` via controlled BFF (≤2) → Bearer → query XHR
696
- * with a **session circuit breaker** so a dead Cloudflare BFF is not
697
- * hammered by hls.js retries (which previously caused 9–10× 502s).
687
+ * hls.js loader: auth query rewrite + enterprise AES key loading.
698
688
  */
699
689
  declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoaderConstructor;
700
690
 
package/dist/index.js CHANGED
@@ -146,7 +146,7 @@ var AuthManager = class {
146
146
 
147
147
  // src/config/defaults.ts
148
148
  var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
149
- var PACKAGE_VERSION = "3.0.1";
149
+ var PACKAGE_VERSION = "3.0.2";
150
150
  var DEFAULT_PLAYER_BEHAVIOR = {
151
151
  autoplay: true,
152
152
  startMuted: true,
@@ -766,9 +766,12 @@ function seekToLiveEdge(media, options) {
766
766
  }
767
767
 
768
768
  // src/streaming/authParamsLoader.ts
769
- import { XhrLoader as BuiltinXhrLoader } from "hls.js";
770
769
  var ENC_KEY_PATH_RE = /enc\.key$/i;
771
770
  var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
771
+ var BUILTIN_LOADER_REF = /* @__PURE__ */ Symbol.for(
772
+ "@codenkay/video-nsgplayer-core/builtin-hls-loader"
773
+ );
774
+ var builtinLoaderByHls = /* @__PURE__ */ new WeakMap();
772
775
  var KEY_PROXY_MAX_FAILURES = 2;
773
776
  function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
774
777
  try {
@@ -777,18 +780,34 @@ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined"
777
780
  return /enc\.key/i.test(rawUrl);
778
781
  }
779
782
  }
780
- function resolveBuiltinHlsLoader(HlsClass) {
781
- if (typeof BuiltinXhrLoader === "function") {
782
- return BuiltinXhrLoader;
783
+ function isBuiltinLoaderName(name) {
784
+ return BUILTIN_LOADER_NAMES.has(name);
785
+ }
786
+ function rememberBuiltin(HlsClass, loader) {
787
+ if (isBuiltinLoaderName(typeof loader.name === "string" ? loader.name : "")) {
788
+ builtinLoaderByHls.set(HlsClass, loader);
783
789
  }
790
+ return loader;
791
+ }
792
+ function getStashedBuiltinLoader(loader) {
793
+ return loader[BUILTIN_LOADER_REF];
794
+ }
795
+ function resolveBuiltinHlsLoader(HlsClass) {
796
+ const cached = builtinLoaderByHls.get(HlsClass);
797
+ if (cached) return cached;
784
798
  const start = HlsClass.DefaultConfig.loader;
785
799
  let current = start;
786
800
  const seen = /* @__PURE__ */ new Set();
787
801
  while (current && !seen.has(current)) {
788
802
  seen.add(current);
789
803
  const name = typeof current.name === "string" ? current.name : "";
790
- if (BUILTIN_LOADER_NAMES.has(name)) {
791
- return current;
804
+ if (isBuiltinLoaderName(name)) {
805
+ return rememberBuiltin(HlsClass, current);
806
+ }
807
+ const stashed = getStashedBuiltinLoader(current);
808
+ if (stashed) {
809
+ current = stashed;
810
+ continue;
792
811
  }
793
812
  const parentCtor = Object.getPrototypeOf(current.prototype)?.constructor;
794
813
  if (!parentCtor || parentCtor === Object || parentCtor === Function) {
@@ -796,7 +815,8 @@ function resolveBuiltinHlsLoader(HlsClass) {
796
815
  }
797
816
  current = parentCtor;
798
817
  }
799
- return start;
818
+ const fallback = builtinLoaderByHls.get(HlsClass);
819
+ return fallback ?? start;
800
820
  }
801
821
  function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
802
822
  if (!rawUrl) return rawUrl;
@@ -844,67 +864,82 @@ function isProxyUpstreamBlocked(status, contentType, bodyHead, errorText) {
844
864
  }
845
865
  return false;
846
866
  }
847
- async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signal) {
848
- const stats = createLoaderStats();
849
- stats.loading.first = performance.now();
850
- const headers = {
851
- Accept: "*/*",
852
- ...context.headers
853
- };
854
- if (accessToken) {
855
- headers.Authorization = `Bearer ${accessToken}`;
856
- }
857
- const response = await fetch(context.url, {
858
- method: "GET",
859
- headers,
860
- credentials: "omit",
861
- cache: "no-store",
862
- signal
863
- });
864
- if (!response.ok) {
865
- callbacks.onError(
866
- {
867
- code: response.status,
868
- text: response.statusText || `enc.key HTTP ${response.status}`
869
- },
870
- context,
867
+ async function fetchEncryptionKeyBytes(url, accessToken, signal) {
868
+ const headers = accessToken ? { Authorization: `Bearer ${accessToken}`, Accept: "*/*" } : void 0;
869
+ try {
870
+ const response = await fetch(url, {
871
+ method: "GET",
872
+ ...headers ? { headers } : {},
873
+ credentials: "omit",
874
+ cache: "no-store",
875
+ signal
876
+ });
877
+ if (!response.ok) {
878
+ return {
879
+ ok: false,
880
+ status: response.status,
881
+ text: response.statusText || `enc.key HTTP ${response.status}`,
882
+ cors: false,
883
+ response
884
+ };
885
+ }
886
+ const data = await response.arrayBuffer();
887
+ const head = new TextDecoder().decode(data.slice(0, 32));
888
+ if (/<!DOCTYPE|<html/i.test(head)) {
889
+ return {
890
+ ok: false,
891
+ status: 403,
892
+ text: "enc.key returned HTML (CDN challenge/block), not an AES key",
893
+ cors: false,
894
+ response
895
+ };
896
+ }
897
+ return {
898
+ ok: true,
899
+ data,
871
900
  response,
872
- stats
873
- );
874
- return;
901
+ url: response.url || url
902
+ };
903
+ } catch (error) {
904
+ if (error instanceof DOMException && error.name === "AbortError") {
905
+ return {
906
+ ok: false,
907
+ status: 0,
908
+ text: "enc.key fetch aborted/timeout",
909
+ cors: false,
910
+ response: null
911
+ };
912
+ }
913
+ const cors = error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message);
914
+ return {
915
+ ok: false,
916
+ status: 0,
917
+ text: error instanceof Error ? error.message : "enc.key fetch failed",
918
+ cors,
919
+ response: null
920
+ };
875
921
  }
876
- const data = await response.arrayBuffer();
922
+ }
923
+ function reportKeySuccess(context, callbacks, result) {
924
+ const stats = createLoaderStats();
877
925
  const end = performance.now();
926
+ stats.loading.first = stats.loading.start;
878
927
  stats.loading.end = end;
879
- stats.loaded = data.byteLength;
880
- stats.total = data.byteLength;
881
- const head = new TextDecoder().decode(data.slice(0, 32));
882
- if (/<!DOCTYPE|<html/i.test(head)) {
883
- callbacks.onError(
884
- {
885
- code: 403,
886
- text: "enc.key returned HTML (CDN challenge/block), not an AES key"
887
- },
888
- context,
889
- response,
890
- stats
891
- );
892
- return;
893
- }
928
+ stats.loaded = result.data.byteLength;
929
+ stats.total = result.data.byteLength;
894
930
  callbacks.onSuccess(
895
931
  {
896
- url: response.url || context.url,
897
- data,
898
- code: response.status
932
+ url: result.url,
933
+ data: result.data,
934
+ code: result.response.status
899
935
  },
900
936
  stats,
901
937
  context,
902
- response
938
+ result.response
903
939
  );
904
940
  }
905
941
  async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state) {
906
942
  const stats = createLoaderStats();
907
- stats.loading.first = performance.now();
908
943
  const canTryProxy = Boolean(plan.proxyUrl) && !state.circuitOpen && state.proxyFailures < plan.maxProxyFailures;
909
944
  if (canTryProxy && plan.proxyUrl) {
910
945
  while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures && !signal.aborted) {
@@ -975,47 +1010,68 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
975
1010
  }
976
1011
  state.circuitOpen = true;
977
1012
  }
978
- if (plan.accessToken) {
979
- try {
980
- await loadEncryptionKeyWithFetch(
1013
+ const queryResult = await fetchEncryptionKeyBytes(
1014
+ plan.signedKeyUrl,
1015
+ null,
1016
+ signal
1017
+ );
1018
+ if (queryResult.ok) {
1019
+ reportKeySuccess(
1020
+ { ...context, url: plan.signedKeyUrl },
1021
+ callbacks,
1022
+ queryResult
1023
+ );
1024
+ return "ok";
1025
+ }
1026
+ const needsBearer = Boolean(plan.accessToken) && (queryResult.status === 401 || queryResult.status === 403);
1027
+ if (needsBearer && plan.accessToken) {
1028
+ const bearerResult = await fetchEncryptionKeyBytes(
1029
+ plan.signedKeyUrl,
1030
+ plan.accessToken,
1031
+ signal
1032
+ );
1033
+ if (bearerResult.ok) {
1034
+ reportKeySuccess(
981
1035
  { ...context, url: plan.signedKeyUrl },
982
1036
  callbacks,
983
- plan.accessToken,
984
- signal
985
- );
986
- return "ok";
987
- } catch (error) {
988
- if (error instanceof DOMException && error.name === "AbortError") {
989
- callbacks.onError(
990
- { code: 0, text: "enc.key fetch aborted/timeout" },
991
- { ...context, url: plan.signedKeyUrl },
992
- null,
993
- stats
994
- );
995
- return "ok";
996
- }
997
- if (error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message)) {
998
- return "try_xhr";
999
- }
1000
- callbacks.onError(
1001
- {
1002
- code: 0,
1003
- text: error instanceof Error ? error.message : "enc.key Bearer fetch failed"
1004
- },
1005
- { ...context, url: plan.signedKeyUrl },
1006
- null,
1007
- stats
1037
+ bearerResult
1008
1038
  );
1009
1039
  return "ok";
1010
1040
  }
1041
+ if (bearerResult.cors) {
1042
+ return "try_xhr";
1043
+ }
1044
+ callbacks.onError(
1045
+ {
1046
+ code: bearerResult.status,
1047
+ text: bearerResult.text
1048
+ },
1049
+ { ...context, url: plan.signedKeyUrl },
1050
+ bearerResult.response,
1051
+ stats
1052
+ );
1053
+ return "ok";
1054
+ }
1055
+ if (queryResult.cors || queryResult.status === 0) {
1056
+ return "try_xhr";
1011
1057
  }
1012
- return "try_xhr";
1058
+ callbacks.onError(
1059
+ {
1060
+ code: queryResult.status,
1061
+ text: queryResult.text
1062
+ },
1063
+ { ...context, url: plan.signedKeyUrl },
1064
+ queryResult.response,
1065
+ stats
1066
+ );
1067
+ return "ok";
1013
1068
  }
1014
1069
  function createAuthParamsLoader(HlsClass, store, options = {}) {
1015
1070
  const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
1071
+ rememberBuiltin(HlsClass, BaseLoader);
1016
1072
  const maxProxyFailures = typeof options.keyProxyMaxFailures === "number" && options.keyProxyMaxFailures >= 0 ? options.keyProxyMaxFailures : KEY_PROXY_MAX_FAILURES;
1017
1073
  const keyProxyState = { proxyFailures: 0, circuitOpen: false };
1018
- return class AuthParamsLoader {
1074
+ class AuthParamsLoader {
1019
1075
  constructor(config) {
1020
1076
  this.context = null;
1021
1077
  this.keyAbort = null;
@@ -1099,7 +1155,13 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
1099
1155
  }
1100
1156
  this.inner.load(context, config, callbacks);
1101
1157
  }
1102
- };
1158
+ }
1159
+ Object.defineProperty(AuthParamsLoader, BUILTIN_LOADER_REF, {
1160
+ value: BaseLoader,
1161
+ enumerable: false,
1162
+ configurable: false
1163
+ });
1164
+ return AuthParamsLoader;
1103
1165
  }
1104
1166
 
1105
1167
  // src/streaming/HlsEngine.ts
@@ -1589,10 +1651,11 @@ function createVideoPlayer(options) {
1589
1651
  volume: config.player.volume,
1590
1652
  videoId: options.videoId,
1591
1653
  keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
1592
- // Enterprise AES key auth:
1593
- // 1) keyProxyUrlBuilder (same-origin BFF) when configured — max 2 failures
1594
- // 2) else getKeyAccessToken / AuthManager Bearer via browser fetch
1595
- // 3) else signed query + XHR (CDN hosts)
1654
+ // Enterprise AES key auth (see authParamsLoader):
1655
+ // 1) keyProxyUrlBuilder BFF — max 2 failures then circuit
1656
+ // 2) signed-query fetch (no Bearer / no CORS preflight)
1657
+ // 3) Bearer only on HTTP 401/403 when getKeyAccessToken is set
1658
+ // 4) signed-query XHR fallback if fetch is CORS-blocked
1596
1659
  getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
1597
1660
  keyProxyMaxFailures: config.streaming.keyProxyMaxFailures,
1598
1661
  hlsConfig: config.streaming.hls,