@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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.2
4
+
5
+ - Fix pack/build regression on AES `enc.key`: do **not** statically import
6
+ `XhrLoader` from `hls.js` (second module copy after npm pack broke key loads).
7
+ - Resolve builtin loader via same-module `DefaultConfig` + composition unwrap
8
+ (`Symbol` stash / WeakMap) so nested wrappers never replace real XhrLoader.
9
+ - Key auth order: BFF (≤2) → **signed-query without Bearer** → Bearer only on
10
+ 401/403. Query-only fetch uses no custom headers (CORS simple request).
11
+ - `keyProxyMaxFailures` circuit breaker retained.
12
+
3
13
  ## 3.0.1
4
14
 
5
15
  - Permanent AES key path: dedicated key loader with **session circuit breaker**
package/dist/index.cjs CHANGED
@@ -213,7 +213,7 @@ var AuthManager = class {
213
213
 
214
214
  // src/config/defaults.ts
215
215
  var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
216
- var PACKAGE_VERSION = "3.0.1";
216
+ var PACKAGE_VERSION = "3.0.2";
217
217
  var DEFAULT_PLAYER_BEHAVIOR = {
218
218
  autoplay: true,
219
219
  startMuted: true,
@@ -833,9 +833,12 @@ function seekToLiveEdge(media, options) {
833
833
  }
834
834
 
835
835
  // src/streaming/authParamsLoader.ts
836
- var import_hls = require("hls.js");
837
836
  var ENC_KEY_PATH_RE = /enc\.key$/i;
838
837
  var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
838
+ var BUILTIN_LOADER_REF = /* @__PURE__ */ Symbol.for(
839
+ "@codenkay/video-nsgplayer-core/builtin-hls-loader"
840
+ );
841
+ var builtinLoaderByHls = /* @__PURE__ */ new WeakMap();
839
842
  var KEY_PROXY_MAX_FAILURES = 2;
840
843
  function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
841
844
  try {
@@ -844,18 +847,34 @@ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined"
844
847
  return /enc\.key/i.test(rawUrl);
845
848
  }
846
849
  }
847
- function resolveBuiltinHlsLoader(HlsClass) {
848
- if (typeof import_hls.XhrLoader === "function") {
849
- return import_hls.XhrLoader;
850
+ function isBuiltinLoaderName(name) {
851
+ return BUILTIN_LOADER_NAMES.has(name);
852
+ }
853
+ function rememberBuiltin(HlsClass, loader) {
854
+ if (isBuiltinLoaderName(typeof loader.name === "string" ? loader.name : "")) {
855
+ builtinLoaderByHls.set(HlsClass, loader);
850
856
  }
857
+ return loader;
858
+ }
859
+ function getStashedBuiltinLoader(loader) {
860
+ return loader[BUILTIN_LOADER_REF];
861
+ }
862
+ function resolveBuiltinHlsLoader(HlsClass) {
863
+ const cached = builtinLoaderByHls.get(HlsClass);
864
+ if (cached) return cached;
851
865
  const start = HlsClass.DefaultConfig.loader;
852
866
  let current = start;
853
867
  const seen = /* @__PURE__ */ new Set();
854
868
  while (current && !seen.has(current)) {
855
869
  seen.add(current);
856
870
  const name = typeof current.name === "string" ? current.name : "";
857
- if (BUILTIN_LOADER_NAMES.has(name)) {
858
- return current;
871
+ if (isBuiltinLoaderName(name)) {
872
+ return rememberBuiltin(HlsClass, current);
873
+ }
874
+ const stashed = getStashedBuiltinLoader(current);
875
+ if (stashed) {
876
+ current = stashed;
877
+ continue;
859
878
  }
860
879
  const parentCtor = Object.getPrototypeOf(current.prototype)?.constructor;
861
880
  if (!parentCtor || parentCtor === Object || parentCtor === Function) {
@@ -863,7 +882,8 @@ function resolveBuiltinHlsLoader(HlsClass) {
863
882
  }
864
883
  current = parentCtor;
865
884
  }
866
- return start;
885
+ const fallback = builtinLoaderByHls.get(HlsClass);
886
+ return fallback ?? start;
867
887
  }
868
888
  function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
869
889
  if (!rawUrl) return rawUrl;
@@ -911,67 +931,82 @@ function isProxyUpstreamBlocked(status, contentType, bodyHead, errorText) {
911
931
  }
912
932
  return false;
913
933
  }
914
- async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signal) {
915
- const stats = createLoaderStats();
916
- stats.loading.first = performance.now();
917
- const headers = {
918
- Accept: "*/*",
919
- ...context.headers
920
- };
921
- if (accessToken) {
922
- headers.Authorization = `Bearer ${accessToken}`;
923
- }
924
- const response = await fetch(context.url, {
925
- method: "GET",
926
- headers,
927
- credentials: "omit",
928
- cache: "no-store",
929
- signal
930
- });
931
- if (!response.ok) {
932
- callbacks.onError(
933
- {
934
- code: response.status,
935
- text: response.statusText || `enc.key HTTP ${response.status}`
936
- },
937
- context,
934
+ async function fetchEncryptionKeyBytes(url, accessToken, signal) {
935
+ const headers = accessToken ? { Authorization: `Bearer ${accessToken}`, Accept: "*/*" } : void 0;
936
+ try {
937
+ const response = await fetch(url, {
938
+ method: "GET",
939
+ ...headers ? { headers } : {},
940
+ credentials: "omit",
941
+ cache: "no-store",
942
+ signal
943
+ });
944
+ if (!response.ok) {
945
+ return {
946
+ ok: false,
947
+ status: response.status,
948
+ text: response.statusText || `enc.key HTTP ${response.status}`,
949
+ cors: false,
950
+ response
951
+ };
952
+ }
953
+ const data = await response.arrayBuffer();
954
+ const head = new TextDecoder().decode(data.slice(0, 32));
955
+ if (/<!DOCTYPE|<html/i.test(head)) {
956
+ return {
957
+ ok: false,
958
+ status: 403,
959
+ text: "enc.key returned HTML (CDN challenge/block), not an AES key",
960
+ cors: false,
961
+ response
962
+ };
963
+ }
964
+ return {
965
+ ok: true,
966
+ data,
938
967
  response,
939
- stats
940
- );
941
- return;
968
+ url: response.url || url
969
+ };
970
+ } catch (error) {
971
+ if (error instanceof DOMException && error.name === "AbortError") {
972
+ return {
973
+ ok: false,
974
+ status: 0,
975
+ text: "enc.key fetch aborted/timeout",
976
+ cors: false,
977
+ response: null
978
+ };
979
+ }
980
+ const cors = error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message);
981
+ return {
982
+ ok: false,
983
+ status: 0,
984
+ text: error instanceof Error ? error.message : "enc.key fetch failed",
985
+ cors,
986
+ response: null
987
+ };
942
988
  }
943
- const data = await response.arrayBuffer();
989
+ }
990
+ function reportKeySuccess(context, callbacks, result) {
991
+ const stats = createLoaderStats();
944
992
  const end = performance.now();
993
+ stats.loading.first = stats.loading.start;
945
994
  stats.loading.end = end;
946
- stats.loaded = data.byteLength;
947
- stats.total = data.byteLength;
948
- const head = new TextDecoder().decode(data.slice(0, 32));
949
- if (/<!DOCTYPE|<html/i.test(head)) {
950
- callbacks.onError(
951
- {
952
- code: 403,
953
- text: "enc.key returned HTML (CDN challenge/block), not an AES key"
954
- },
955
- context,
956
- response,
957
- stats
958
- );
959
- return;
960
- }
995
+ stats.loaded = result.data.byteLength;
996
+ stats.total = result.data.byteLength;
961
997
  callbacks.onSuccess(
962
998
  {
963
- url: response.url || context.url,
964
- data,
965
- code: response.status
999
+ url: result.url,
1000
+ data: result.data,
1001
+ code: result.response.status
966
1002
  },
967
1003
  stats,
968
1004
  context,
969
- response
1005
+ result.response
970
1006
  );
971
1007
  }
972
1008
  async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state) {
973
1009
  const stats = createLoaderStats();
974
- stats.loading.first = performance.now();
975
1010
  const canTryProxy = Boolean(plan.proxyUrl) && !state.circuitOpen && state.proxyFailures < plan.maxProxyFailures;
976
1011
  if (canTryProxy && plan.proxyUrl) {
977
1012
  while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures && !signal.aborted) {
@@ -1042,47 +1077,68 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
1042
1077
  }
1043
1078
  state.circuitOpen = true;
1044
1079
  }
1045
- if (plan.accessToken) {
1046
- try {
1047
- await loadEncryptionKeyWithFetch(
1080
+ const queryResult = await fetchEncryptionKeyBytes(
1081
+ plan.signedKeyUrl,
1082
+ null,
1083
+ signal
1084
+ );
1085
+ if (queryResult.ok) {
1086
+ reportKeySuccess(
1087
+ { ...context, url: plan.signedKeyUrl },
1088
+ callbacks,
1089
+ queryResult
1090
+ );
1091
+ return "ok";
1092
+ }
1093
+ const needsBearer = Boolean(plan.accessToken) && (queryResult.status === 401 || queryResult.status === 403);
1094
+ if (needsBearer && plan.accessToken) {
1095
+ const bearerResult = await fetchEncryptionKeyBytes(
1096
+ plan.signedKeyUrl,
1097
+ plan.accessToken,
1098
+ signal
1099
+ );
1100
+ if (bearerResult.ok) {
1101
+ reportKeySuccess(
1048
1102
  { ...context, url: plan.signedKeyUrl },
1049
1103
  callbacks,
1050
- plan.accessToken,
1051
- signal
1052
- );
1053
- return "ok";
1054
- } catch (error) {
1055
- if (error instanceof DOMException && error.name === "AbortError") {
1056
- callbacks.onError(
1057
- { code: 0, text: "enc.key fetch aborted/timeout" },
1058
- { ...context, url: plan.signedKeyUrl },
1059
- null,
1060
- stats
1061
- );
1062
- return "ok";
1063
- }
1064
- if (error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message)) {
1065
- return "try_xhr";
1066
- }
1067
- callbacks.onError(
1068
- {
1069
- code: 0,
1070
- text: error instanceof Error ? error.message : "enc.key Bearer fetch failed"
1071
- },
1072
- { ...context, url: plan.signedKeyUrl },
1073
- null,
1074
- stats
1104
+ bearerResult
1075
1105
  );
1076
1106
  return "ok";
1077
1107
  }
1108
+ if (bearerResult.cors) {
1109
+ return "try_xhr";
1110
+ }
1111
+ callbacks.onError(
1112
+ {
1113
+ code: bearerResult.status,
1114
+ text: bearerResult.text
1115
+ },
1116
+ { ...context, url: plan.signedKeyUrl },
1117
+ bearerResult.response,
1118
+ stats
1119
+ );
1120
+ return "ok";
1121
+ }
1122
+ if (queryResult.cors || queryResult.status === 0) {
1123
+ return "try_xhr";
1078
1124
  }
1079
- return "try_xhr";
1125
+ callbacks.onError(
1126
+ {
1127
+ code: queryResult.status,
1128
+ text: queryResult.text
1129
+ },
1130
+ { ...context, url: plan.signedKeyUrl },
1131
+ queryResult.response,
1132
+ stats
1133
+ );
1134
+ return "ok";
1080
1135
  }
1081
1136
  function createAuthParamsLoader(HlsClass, store, options = {}) {
1082
1137
  const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
1138
+ rememberBuiltin(HlsClass, BaseLoader);
1083
1139
  const maxProxyFailures = typeof options.keyProxyMaxFailures === "number" && options.keyProxyMaxFailures >= 0 ? options.keyProxyMaxFailures : KEY_PROXY_MAX_FAILURES;
1084
1140
  const keyProxyState = { proxyFailures: 0, circuitOpen: false };
1085
- return class AuthParamsLoader {
1141
+ class AuthParamsLoader {
1086
1142
  constructor(config) {
1087
1143
  this.context = null;
1088
1144
  this.keyAbort = null;
@@ -1166,7 +1222,13 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
1166
1222
  }
1167
1223
  this.inner.load(context, config, callbacks);
1168
1224
  }
1169
- };
1225
+ }
1226
+ Object.defineProperty(AuthParamsLoader, BUILTIN_LOADER_REF, {
1227
+ value: BaseLoader,
1228
+ enumerable: false,
1229
+ configurable: false
1230
+ });
1231
+ return AuthParamsLoader;
1170
1232
  }
1171
1233
 
1172
1234
  // src/streaming/HlsEngine.ts
@@ -1656,10 +1718,11 @@ function createVideoPlayer(options) {
1656
1718
  volume: config.player.volume,
1657
1719
  videoId: options.videoId,
1658
1720
  keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
1659
- // Enterprise AES key auth:
1660
- // 1) keyProxyUrlBuilder (same-origin BFF) when configured — max 2 failures
1661
- // 2) else getKeyAccessToken / AuthManager Bearer via browser fetch
1662
- // 3) else signed query + XHR (CDN hosts)
1721
+ // Enterprise AES key auth (see authParamsLoader):
1722
+ // 1) keyProxyUrlBuilder BFF — max 2 failures then circuit
1723
+ // 2) signed-query fetch (no Bearer / no CORS preflight)
1724
+ // 3) Bearer only on HTTP 401/403 when getKeyAccessToken is set
1725
+ // 4) signed-query XHR fallback if fetch is CORS-blocked
1663
1726
  getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
1664
1727
  keyProxyMaxFailures: config.streaming.keyProxyMaxFailures,
1665
1728
  hlsConfig: config.streaming.hls,