@codenkay/video-nsgplayer-core 3.0.1 → 3.0.4

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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Preserve "use client" in React/UI builds, forward analytics, intermittent watermark, Angular Ivy packaging, and packaging metadata.
8
+
9
+ ## 3.0.3
10
+
11
+ - Browser-direct AES keys: after signed-query CORS/network failure, retry once
12
+ with `getKeyAccessToken` Bearer before XHR (needed when AUTH_MODE=browser and
13
+ Appextech enables CORS + Authorization on `enc.key`).
14
+ - Clearer KEY_LOAD_ERROR message distinguishing BFF vs browser-direct CORS.
15
+
16
+ ## 3.0.2
17
+
18
+ - Fix pack/build regression on AES `enc.key`: do **not** statically import
19
+ `XhrLoader` from `hls.js` (second module copy after npm pack broke key loads).
20
+ - Resolve builtin loader via same-module `DefaultConfig` + composition unwrap
21
+ (`Symbol` stash / WeakMap) so nested wrappers never replace real XhrLoader.
22
+ - Key auth order: BFF (≤2) → **signed-query without Bearer** → Bearer only on
23
+ 401/403. Query-only fetch uses no custom headers (CORS simple request).
24
+ - `keyProxyMaxFailures` circuit breaker retained.
25
+
3
26
  ## 3.0.1
4
27
 
5
28
  - 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.3";
217
217
  var DEFAULT_PLAYER_BEHAVIOR = {
218
218
  autoplay: true,
219
219
  startMuted: true,
@@ -237,7 +237,8 @@ var DEFAULT_CONTROLS = {
237
237
  showSkip: true,
238
238
  showLive: true,
239
239
  seekCueDurationMs: 720,
240
- keyboardShortcuts: true
240
+ keyboardShortcuts: true,
241
+ persistQualityPreference: false
241
242
  };
242
243
  var DEFAULT_THEME = {
243
244
  playIconPreset: "triangle",
@@ -833,9 +834,12 @@ function seekToLiveEdge(media, options) {
833
834
  }
834
835
 
835
836
  // src/streaming/authParamsLoader.ts
836
- var import_hls = require("hls.js");
837
837
  var ENC_KEY_PATH_RE = /enc\.key$/i;
838
838
  var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
839
+ var BUILTIN_LOADER_REF = /* @__PURE__ */ Symbol.for(
840
+ "@codenkay/video-nsgplayer-core/builtin-hls-loader"
841
+ );
842
+ var builtinLoaderByHls = /* @__PURE__ */ new WeakMap();
839
843
  var KEY_PROXY_MAX_FAILURES = 2;
840
844
  function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
841
845
  try {
@@ -844,18 +848,35 @@ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined"
844
848
  return /enc\.key/i.test(rawUrl);
845
849
  }
846
850
  }
847
- function resolveBuiltinHlsLoader(HlsClass) {
848
- if (typeof import_hls.XhrLoader === "function") {
849
- return import_hls.XhrLoader;
851
+ function isBuiltinLoaderName(name) {
852
+ return BUILTIN_LOADER_NAMES.has(name);
853
+ }
854
+ function rememberBuiltin(HlsClass, loader) {
855
+ if (isBuiltinLoaderName(typeof loader.name === "string" ? loader.name : "")) {
856
+ builtinLoaderByHls.set(HlsClass, loader);
850
857
  }
858
+ return loader;
859
+ }
860
+ function getStashedBuiltinLoader(loader) {
861
+ return loader[BUILTIN_LOADER_REF];
862
+ }
863
+ function resolveBuiltinHlsLoader(HlsModule) {
864
+ const HlsClass = HlsModule;
865
+ const cached = builtinLoaderByHls.get(HlsClass);
866
+ if (cached) return cached;
851
867
  const start = HlsClass.DefaultConfig.loader;
852
868
  let current = start;
853
869
  const seen = /* @__PURE__ */ new Set();
854
870
  while (current && !seen.has(current)) {
855
871
  seen.add(current);
856
872
  const name = typeof current.name === "string" ? current.name : "";
857
- if (BUILTIN_LOADER_NAMES.has(name)) {
858
- return current;
873
+ if (isBuiltinLoaderName(name)) {
874
+ return rememberBuiltin(HlsClass, current);
875
+ }
876
+ const stashed = getStashedBuiltinLoader(current);
877
+ if (stashed) {
878
+ current = stashed;
879
+ continue;
859
880
  }
860
881
  const parentCtor = Object.getPrototypeOf(current.prototype)?.constructor;
861
882
  if (!parentCtor || parentCtor === Object || parentCtor === Function) {
@@ -863,7 +884,8 @@ function resolveBuiltinHlsLoader(HlsClass) {
863
884
  }
864
885
  current = parentCtor;
865
886
  }
866
- return start;
887
+ const fallback = builtinLoaderByHls.get(HlsClass);
888
+ return fallback ?? start;
867
889
  }
868
890
  function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
869
891
  if (!rawUrl) return rawUrl;
@@ -911,67 +933,82 @@ function isProxyUpstreamBlocked(status, contentType, bodyHead, errorText) {
911
933
  }
912
934
  return false;
913
935
  }
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,
936
+ async function fetchEncryptionKeyBytes(url, accessToken, signal) {
937
+ const headers = accessToken ? { Authorization: `Bearer ${accessToken}`, Accept: "*/*" } : void 0;
938
+ try {
939
+ const response = await fetch(url, {
940
+ method: "GET",
941
+ ...headers ? { headers } : {},
942
+ credentials: "omit",
943
+ cache: "no-store",
944
+ signal
945
+ });
946
+ if (!response.ok) {
947
+ return {
948
+ ok: false,
949
+ status: response.status,
950
+ text: response.statusText || `enc.key HTTP ${response.status}`,
951
+ cors: false,
952
+ response
953
+ };
954
+ }
955
+ const data = await response.arrayBuffer();
956
+ const head = new TextDecoder().decode(data.slice(0, 32));
957
+ if (/<!DOCTYPE|<html/i.test(head)) {
958
+ return {
959
+ ok: false,
960
+ status: 403,
961
+ text: "enc.key returned HTML (CDN challenge/block), not an AES key",
962
+ cors: false,
963
+ response
964
+ };
965
+ }
966
+ return {
967
+ ok: true,
968
+ data,
938
969
  response,
939
- stats
940
- );
941
- return;
970
+ url: response.url || url
971
+ };
972
+ } catch (error) {
973
+ if (error instanceof DOMException && error.name === "AbortError") {
974
+ return {
975
+ ok: false,
976
+ status: 0,
977
+ text: "enc.key fetch aborted/timeout",
978
+ cors: false,
979
+ response: null
980
+ };
981
+ }
982
+ const cors = error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message);
983
+ return {
984
+ ok: false,
985
+ status: 0,
986
+ text: error instanceof Error ? error.message : "enc.key fetch failed",
987
+ cors,
988
+ response: null
989
+ };
942
990
  }
943
- const data = await response.arrayBuffer();
991
+ }
992
+ function reportKeySuccess(context, callbacks, result) {
993
+ const stats = createLoaderStats();
944
994
  const end = performance.now();
995
+ stats.loading.first = stats.loading.start;
945
996
  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
- }
997
+ stats.loaded = result.data.byteLength;
998
+ stats.total = result.data.byteLength;
961
999
  callbacks.onSuccess(
962
1000
  {
963
- url: response.url || context.url,
964
- data,
965
- code: response.status
1001
+ url: result.url,
1002
+ data: result.data,
1003
+ code: result.response.status
966
1004
  },
967
1005
  stats,
968
1006
  context,
969
- response
1007
+ result.response
970
1008
  );
971
1009
  }
972
1010
  async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state) {
973
1011
  const stats = createLoaderStats();
974
- stats.loading.first = performance.now();
975
1012
  const canTryProxy = Boolean(plan.proxyUrl) && !state.circuitOpen && state.proxyFailures < plan.maxProxyFailures;
976
1013
  if (canTryProxy && plan.proxyUrl) {
977
1014
  while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures && !signal.aborted) {
@@ -1042,47 +1079,71 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
1042
1079
  }
1043
1080
  state.circuitOpen = true;
1044
1081
  }
1045
- if (plan.accessToken) {
1046
- try {
1047
- await loadEncryptionKeyWithFetch(
1082
+ const queryResult = await fetchEncryptionKeyBytes(
1083
+ plan.signedKeyUrl,
1084
+ null,
1085
+ signal
1086
+ );
1087
+ if (queryResult.ok) {
1088
+ reportKeySuccess(
1089
+ { ...context, url: plan.signedKeyUrl },
1090
+ callbacks,
1091
+ queryResult
1092
+ );
1093
+ return "ok";
1094
+ }
1095
+ const needsBearer = Boolean(plan.accessToken) && (queryResult.status === 401 || queryResult.status === 403 || // Browser-direct: opaque CORS/network on unsigned GET — try Bearer once
1096
+ // Appextech allows ACAO + Authorization (AUTH_MODE=browser, no BFF).
1097
+ queryResult.cors || queryResult.status === 0);
1098
+ if (needsBearer && plan.accessToken) {
1099
+ const bearerResult = await fetchEncryptionKeyBytes(
1100
+ plan.signedKeyUrl,
1101
+ plan.accessToken,
1102
+ signal
1103
+ );
1104
+ if (bearerResult.ok) {
1105
+ reportKeySuccess(
1048
1106
  { ...context, url: plan.signedKeyUrl },
1049
1107
  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
1108
+ bearerResult
1075
1109
  );
1076
1110
  return "ok";
1077
1111
  }
1112
+ if (bearerResult.cors || bearerResult.status === 0) {
1113
+ return "try_xhr";
1114
+ }
1115
+ callbacks.onError(
1116
+ {
1117
+ code: bearerResult.status,
1118
+ text: bearerResult.text
1119
+ },
1120
+ { ...context, url: plan.signedKeyUrl },
1121
+ bearerResult.response,
1122
+ stats
1123
+ );
1124
+ return "ok";
1125
+ }
1126
+ if (queryResult.cors || queryResult.status === 0) {
1127
+ return "try_xhr";
1078
1128
  }
1079
- return "try_xhr";
1129
+ callbacks.onError(
1130
+ {
1131
+ code: queryResult.status,
1132
+ text: queryResult.text
1133
+ },
1134
+ { ...context, url: plan.signedKeyUrl },
1135
+ queryResult.response,
1136
+ stats
1137
+ );
1138
+ return "ok";
1080
1139
  }
1081
- function createAuthParamsLoader(HlsClass, store, options = {}) {
1140
+ function createAuthParamsLoader(HlsModule, store, options = {}) {
1141
+ const HlsClass = HlsModule;
1082
1142
  const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
1143
+ rememberBuiltin(HlsClass, BaseLoader);
1083
1144
  const maxProxyFailures = typeof options.keyProxyMaxFailures === "number" && options.keyProxyMaxFailures >= 0 ? options.keyProxyMaxFailures : KEY_PROXY_MAX_FAILURES;
1084
1145
  const keyProxyState = { proxyFailures: 0, circuitOpen: false };
1085
- return class AuthParamsLoader {
1146
+ class AuthParamsLoader {
1086
1147
  constructor(config) {
1087
1148
  this.context = null;
1088
1149
  this.keyAbort = null;
@@ -1166,7 +1227,13 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
1166
1227
  }
1167
1228
  this.inner.load(context, config, callbacks);
1168
1229
  }
1169
- };
1230
+ }
1231
+ Object.defineProperty(AuthParamsLoader, BUILTIN_LOADER_REF, {
1232
+ value: BaseLoader,
1233
+ enumerable: false,
1234
+ configurable: false
1235
+ });
1236
+ return AuthParamsLoader;
1170
1237
  }
1171
1238
 
1172
1239
  // src/streaming/HlsEngine.ts
@@ -1281,7 +1348,7 @@ var HlsEngine = class {
1281
1348
  if (!data.fatal) return;
1282
1349
  } else if (!data.fatal) {
1283
1350
  callbacks.onFatalError?.(
1284
- status ? `Encryption key load failed (HTTP ${status}).` : "Encryption key load failed (network/CORS). Ensure signed query params are applied, or use keyProxyUrlBuilder / getKeyAccessToken when the key host requires API Bearer."
1351
+ status ? `Encryption key load failed (HTTP ${status}).` : "Encryption key load failed (network/CORS). For browser-direct mode the key host must allow CORS (and Authorization if Bearer is required). Otherwise use keyProxyUrlBuilder (same-origin BFF) when the server IP can reach Appextech."
1285
1352
  );
1286
1353
  return;
1287
1354
  }
@@ -1656,10 +1723,11 @@ function createVideoPlayer(options) {
1656
1723
  volume: config.player.volume,
1657
1724
  videoId: options.videoId,
1658
1725
  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)
1726
+ // Enterprise AES key auth (see authParamsLoader):
1727
+ // 1) keyProxyUrlBuilder BFF — max 2 failures then circuit
1728
+ // 2) signed-query fetch (no Bearer / no CORS preflight)
1729
+ // 3) Bearer only on HTTP 401/403 when getKeyAccessToken is set
1730
+ // 4) signed-query XHR fallback if fetch is CORS-blocked
1663
1731
  getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
1664
1732
  keyProxyMaxFailures: config.streaming.keyProxyMaxFailures,
1665
1733
  hlsConfig: config.streaming.hls,