@codenkay/video-nsgplayer-core 0.1.0 → 0.1.3
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 +15 -0
- package/dist/index.cjs +176 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -7
- package/dist/index.d.ts +61 -7
- package/dist/index.js +172 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
- Fix CDN **403 on child HLS playlists/segments** (`stream_N.m3u8`, `.ts`, keys): hls.js now uses an auth-params loader that re-applies signed `token`/`expires` query params on every request, not only the master playlist.
|
|
6
|
+
- Preserve optional `streaming.keyProxyUrlBuilder` through config merge.
|
|
7
|
+
|
|
8
|
+
## 0.1.2
|
|
9
|
+
|
|
10
|
+
- Fix `InternalError: too much recursion` / stack overflow in `assertNoSecretsInConfig` when `createVideoPlayer` options include an `HTMLVideoElement` mount or credential callbacks.
|
|
11
|
+
- Secret scanning is now plain-object only, iterative, depth-capped, and does not deep-walk `mount` / `auth` / `playback`.
|
|
12
|
+
- Harden `redactObject` against circular graphs.
|
|
13
|
+
|
|
14
|
+
## 0.1.1
|
|
15
|
+
|
|
16
|
+
- Live-edge and config scan hardening.
|
|
17
|
+
|
|
3
18
|
## 0.1.0
|
|
4
19
|
|
|
5
20
|
- Initial release
|
package/dist/index.cjs
CHANGED
|
@@ -44,15 +44,19 @@ __export(index_exports, {
|
|
|
44
44
|
PlaybackSourceManager: () => PlaybackSourceManager,
|
|
45
45
|
PlayerErrorCode: () => PlayerErrorCode,
|
|
46
46
|
RefreshManager: () => RefreshManager,
|
|
47
|
+
SECRET_SCAN_MAX_DEPTH: () => SECRET_SCAN_MAX_DEPTH,
|
|
47
48
|
assertNoSecretsInConfig: () => assertNoSecretsInConfig,
|
|
49
|
+
createAuthParamsLoader: () => createAuthParamsLoader,
|
|
48
50
|
createPlayerError: () => createPlayerError,
|
|
49
51
|
createVideoPlayer: () => createVideoPlayer,
|
|
50
52
|
getLiveEdgeTime: () => getLiveEdgeTime,
|
|
51
53
|
isAtLiveEdge: () => isAtLiveEdge,
|
|
54
|
+
isPlainObject: () => isPlainObject,
|
|
52
55
|
mergePlayerConfig: () => mergePlayerConfig,
|
|
53
56
|
redactObject: () => redactObject,
|
|
54
57
|
redactSensitive: () => redactSensitive,
|
|
55
58
|
redactValue: () => redactValue,
|
|
59
|
+
rewriteHlsRequestUrl: () => rewriteHlsRequestUrl,
|
|
56
60
|
seekToLiveEdge: () => seekToLiveEdge,
|
|
57
61
|
themeToCssVars: () => themeToCssVars
|
|
58
62
|
});
|
|
@@ -206,7 +210,7 @@ var AuthManager = class {
|
|
|
206
210
|
|
|
207
211
|
// src/config/defaults.ts
|
|
208
212
|
var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
209
|
-
var PACKAGE_VERSION = "0.1.
|
|
213
|
+
var PACKAGE_VERSION = "0.1.3";
|
|
210
214
|
var DEFAULT_PLAYER_BEHAVIOR = {
|
|
211
215
|
autoplay: true,
|
|
212
216
|
startMuted: true,
|
|
@@ -292,22 +296,83 @@ var FORBIDDEN_SECRET_KEYS = [
|
|
|
292
296
|
"basicauthpassword",
|
|
293
297
|
"password"
|
|
294
298
|
];
|
|
299
|
+
var SKIP_DEEP_SCAN_KEYS = /* @__PURE__ */ new Set([
|
|
300
|
+
"mount",
|
|
301
|
+
"auth",
|
|
302
|
+
"playback",
|
|
303
|
+
"gettoken",
|
|
304
|
+
"getsource",
|
|
305
|
+
"refreshsource",
|
|
306
|
+
"keyproxyurlbuilder"
|
|
307
|
+
]);
|
|
308
|
+
var SECRET_SCAN_MAX_DEPTH = 32;
|
|
309
|
+
function isPlainObject(value) {
|
|
310
|
+
if (value === null || typeof value !== "object") return false;
|
|
311
|
+
if (Array.isArray(value)) return false;
|
|
312
|
+
const proto = Object.getPrototypeOf(value);
|
|
313
|
+
return proto === Object.prototype || proto === null;
|
|
314
|
+
}
|
|
315
|
+
function normalizeConfigKey(key) {
|
|
316
|
+
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
317
|
+
}
|
|
318
|
+
function isForbiddenSecretKey(key) {
|
|
319
|
+
const lower = normalizeConfigKey(key);
|
|
320
|
+
return FORBIDDEN_SECRET_KEYS.some((k) => lower.includes(k));
|
|
321
|
+
}
|
|
322
|
+
function throwForbidden(path, key) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
`INVALID_CONFIGURATION: Forbidden secret key "${path}${key}" must never be passed to the browser SDK. Keep clientSecret on your server/BFF and use auth.getToken().`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
295
327
|
function assertNoSecretsInConfig(raw) {
|
|
296
|
-
if (!raw
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
328
|
+
if (!isPlainObject(raw) && !Array.isArray(raw)) return;
|
|
329
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
330
|
+
const stack = [
|
|
331
|
+
{ value: raw, path: "", depth: 0 }
|
|
332
|
+
];
|
|
333
|
+
while (stack.length > 0) {
|
|
334
|
+
const current = stack.pop();
|
|
335
|
+
if (current.depth > SECRET_SCAN_MAX_DEPTH) continue;
|
|
336
|
+
const { value, path, depth } = current;
|
|
337
|
+
if (Array.isArray(value)) {
|
|
338
|
+
if (seen.has(value)) continue;
|
|
339
|
+
seen.add(value);
|
|
340
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
341
|
+
const item = value[i];
|
|
342
|
+
if (isPlainObject(item) || Array.isArray(item)) {
|
|
343
|
+
stack.push({ value: item, path: `${path}[${i}]`, depth: depth + 1 });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (!isPlainObject(value)) continue;
|
|
349
|
+
if (seen.has(value)) continue;
|
|
350
|
+
seen.add(value);
|
|
351
|
+
for (const [key, child] of Object.entries(value)) {
|
|
352
|
+
const normalized = normalizeConfigKey(key);
|
|
353
|
+
if (isForbiddenSecretKey(key)) {
|
|
354
|
+
throwForbidden(path, key);
|
|
355
|
+
}
|
|
356
|
+
if (normalized === "mount") continue;
|
|
357
|
+
if (SKIP_DEEP_SCAN_KEYS.has(normalized)) {
|
|
358
|
+
if (isPlainObject(child)) {
|
|
359
|
+
for (const nestedKey of Object.keys(child)) {
|
|
360
|
+
if (isForbiddenSecretKey(nestedKey)) {
|
|
361
|
+
throwForbidden(`${path}${key}.`, nestedKey);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
continue;
|
|
304
366
|
}
|
|
305
|
-
if (
|
|
306
|
-
|
|
367
|
+
if (isPlainObject(child) || Array.isArray(child)) {
|
|
368
|
+
stack.push({
|
|
369
|
+
value: child,
|
|
370
|
+
path: path ? `${path}${key}.` : `${key}.`,
|
|
371
|
+
depth: depth + 1
|
|
372
|
+
});
|
|
307
373
|
}
|
|
308
374
|
}
|
|
309
|
-
}
|
|
310
|
-
walk(raw, "");
|
|
375
|
+
}
|
|
311
376
|
}
|
|
312
377
|
function mergePlayerConfig(partial) {
|
|
313
378
|
assertNoSecretsInConfig(partial);
|
|
@@ -321,7 +386,8 @@ function mergePlayerConfig(partial) {
|
|
|
321
386
|
hls: {
|
|
322
387
|
...DEFAULT_PLAYER_CONFIG.streaming.hls,
|
|
323
388
|
...partial?.streaming?.hls
|
|
324
|
-
}
|
|
389
|
+
},
|
|
390
|
+
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder
|
|
325
391
|
},
|
|
326
392
|
logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
|
|
327
393
|
debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
|
|
@@ -395,7 +461,14 @@ var EventEmitter = class {
|
|
|
395
461
|
|
|
396
462
|
// src/security/redaction.ts
|
|
397
463
|
var SENSITIVE_KEYS = /^(authorization|cookie|set-cookie|x-api-key|token|accessToken|access_token|clientSecret|password)$/i;
|
|
398
|
-
|
|
464
|
+
var REDACT_MAX_DEPTH = 32;
|
|
465
|
+
function isPlainObject2(value) {
|
|
466
|
+
if (value === null || typeof value !== "object") return false;
|
|
467
|
+
if (Array.isArray(value)) return false;
|
|
468
|
+
const proto = Object.getPrototypeOf(value);
|
|
469
|
+
return proto === Object.prototype || proto === null;
|
|
470
|
+
}
|
|
471
|
+
function redactValue(key, value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
|
|
399
472
|
if (SENSITIVE_KEYS.test(key)) return "[REDACTED]";
|
|
400
473
|
if (typeof value === "string") {
|
|
401
474
|
if (/^https?:\/\//i.test(value) && /[?&](token|expires)=/i.test(value)) {
|
|
@@ -412,15 +485,25 @@ function redactValue(key, value) {
|
|
|
412
485
|
}
|
|
413
486
|
}
|
|
414
487
|
}
|
|
415
|
-
if (
|
|
416
|
-
|
|
488
|
+
if (Array.isArray(value)) {
|
|
489
|
+
if (depth >= REDACT_MAX_DEPTH || seen.has(value)) return "[REDACTED_CYCLE]";
|
|
490
|
+
seen.add(value);
|
|
491
|
+
return value.map(
|
|
492
|
+
(item, index) => redactValue(String(index), item, seen, depth + 1)
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
if (isPlainObject2(value)) {
|
|
496
|
+
return redactObject(value, seen, depth + 1);
|
|
417
497
|
}
|
|
418
498
|
return value;
|
|
419
499
|
}
|
|
420
|
-
function redactObject(obj) {
|
|
500
|
+
function redactObject(obj, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
|
|
501
|
+
if (depth > REDACT_MAX_DEPTH) return { "[REDACTED]": "max_depth" };
|
|
502
|
+
if (seen.has(obj)) return { "[REDACTED]": "cycle" };
|
|
503
|
+
seen.add(obj);
|
|
421
504
|
const out = {};
|
|
422
505
|
for (const [key, value] of Object.entries(obj)) {
|
|
423
|
-
out[key] = redactValue(key, value);
|
|
506
|
+
out[key] = redactValue(key, value, seen, depth);
|
|
424
507
|
}
|
|
425
508
|
return out;
|
|
426
509
|
}
|
|
@@ -480,11 +563,25 @@ var AuthParamsStore = class {
|
|
|
480
563
|
});
|
|
481
564
|
return out;
|
|
482
565
|
}
|
|
566
|
+
/** True when at least one auth query param is stored. */
|
|
567
|
+
hasParams() {
|
|
568
|
+
return [...this.params.keys()].length > 0;
|
|
569
|
+
}
|
|
570
|
+
toString() {
|
|
571
|
+
return this.params.toString();
|
|
572
|
+
}
|
|
483
573
|
update(next) {
|
|
484
574
|
this.params = next instanceof URLSearchParams ? new URLSearchParams(next) : new URLSearchParams(next);
|
|
485
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Merges current auth params onto a target URL, preserving unrelated query keys.
|
|
578
|
+
* Critical for child HLS playlists/segments that omit signed query params.
|
|
579
|
+
*/
|
|
486
580
|
applyToUrl(rawUrl, baseOrigin) {
|
|
487
|
-
const target = new URL(
|
|
581
|
+
const target = new URL(
|
|
582
|
+
rawUrl,
|
|
583
|
+
baseOrigin ?? (typeof location !== "undefined" ? location.origin : "http://localhost")
|
|
584
|
+
);
|
|
488
585
|
this.params.forEach((value, key) => {
|
|
489
586
|
target.searchParams.set(key, value);
|
|
490
587
|
});
|
|
@@ -719,6 +816,39 @@ function seekToLiveEdge(media, options) {
|
|
|
719
816
|
return { moved: true, edge };
|
|
720
817
|
}
|
|
721
818
|
|
|
819
|
+
// src/streaming/authParamsLoader.ts
|
|
820
|
+
var ENC_KEY_PATH_RE = /\/enc\.key$/i;
|
|
821
|
+
function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
822
|
+
if (!rawUrl) return rawUrl;
|
|
823
|
+
const parsed = new URL(rawUrl, baseOrigin);
|
|
824
|
+
const isKey = ENC_KEY_PATH_RE.test(parsed.pathname);
|
|
825
|
+
if (isKey && options.keyProxyUrlBuilder && options.videoId) {
|
|
826
|
+
const snapshot = store.toRecord();
|
|
827
|
+
return options.keyProxyUrlBuilder({
|
|
828
|
+
videoId: options.videoId,
|
|
829
|
+
keyUrl: parsed.toString(),
|
|
830
|
+
token: snapshot.token,
|
|
831
|
+
expires: snapshot.expires
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if (!store.hasParams()) return parsed.toString();
|
|
835
|
+
return store.applyToUrl(rawUrl, baseOrigin);
|
|
836
|
+
}
|
|
837
|
+
function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
838
|
+
const BaseLoader = HlsClass.DefaultConfig.loader;
|
|
839
|
+
return class AuthParamsLoader extends BaseLoader {
|
|
840
|
+
load(context, config, callbacks) {
|
|
841
|
+
if (context.url) {
|
|
842
|
+
try {
|
|
843
|
+
context.url = rewriteHlsRequestUrl(context.url, store, options);
|
|
844
|
+
} catch {
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
super.load(context, config, callbacks);
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
722
852
|
// src/streaming/HlsEngine.ts
|
|
723
853
|
var HlsEngine = class {
|
|
724
854
|
constructor(options) {
|
|
@@ -771,15 +901,16 @@ var HlsEngine = class {
|
|
|
771
901
|
}
|
|
772
902
|
}
|
|
773
903
|
async startWithHlsJs(Hls, playbackUrl) {
|
|
774
|
-
const { videoEl, authParams, hlsConfig, callbacks } = this.options;
|
|
904
|
+
const { videoEl, authParams, hlsConfig, callbacks, videoId, keyProxyUrlBuilder } = this.options;
|
|
905
|
+
const loader = createAuthParamsLoader(Hls, authParams, {
|
|
906
|
+
videoId,
|
|
907
|
+
keyProxyUrlBuilder
|
|
908
|
+
});
|
|
775
909
|
this.hls = new Hls({
|
|
776
910
|
enableWorker: hlsConfig.enableWorker,
|
|
777
911
|
lowLatencyMode: hlsConfig.lowLatencyMode,
|
|
778
912
|
backBufferLength: hlsConfig.backBufferLength,
|
|
779
|
-
|
|
780
|
-
void xhr;
|
|
781
|
-
void url;
|
|
782
|
-
}
|
|
913
|
+
loader
|
|
783
914
|
});
|
|
784
915
|
this.hls.attachMedia(videoEl);
|
|
785
916
|
this.hls.loadSource(authParams.applyToUrl(playbackUrl));
|
|
@@ -890,7 +1021,18 @@ function generateSessionId() {
|
|
|
890
1021
|
return `ps_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
891
1022
|
}
|
|
892
1023
|
function createVideoPlayer(options) {
|
|
893
|
-
assertNoSecretsInConfig(
|
|
1024
|
+
assertNoSecretsInConfig({
|
|
1025
|
+
videoId: options.videoId,
|
|
1026
|
+
sourceUrl: options.sourceUrl,
|
|
1027
|
+
player: options.player,
|
|
1028
|
+
controls: options.controls,
|
|
1029
|
+
theme: options.theme,
|
|
1030
|
+
watermark: options.watermark,
|
|
1031
|
+
streaming: options.streaming,
|
|
1032
|
+
logging: options.logging,
|
|
1033
|
+
debug: options.debug,
|
|
1034
|
+
accessibility: options.accessibility
|
|
1035
|
+
});
|
|
894
1036
|
let config = mergePlayerConfig({
|
|
895
1037
|
player: options.player,
|
|
896
1038
|
controls: options.controls,
|
|
@@ -976,6 +1118,8 @@ function createVideoPlayer(options) {
|
|
|
976
1118
|
startMuted: config.player.startMuted,
|
|
977
1119
|
startPosition: config.player.startPosition,
|
|
978
1120
|
volume: config.player.volume,
|
|
1121
|
+
videoId: options.videoId,
|
|
1122
|
+
keyProxyUrlBuilder: options.streaming?.keyProxyUrlBuilder,
|
|
979
1123
|
hlsConfig: config.streaming.hls,
|
|
980
1124
|
logger,
|
|
981
1125
|
callbacks: {
|
|
@@ -1236,7 +1380,8 @@ function createVideoPlayer(options) {
|
|
|
1236
1380
|
watermark: { ...config.watermark, ...partial?.watermark },
|
|
1237
1381
|
streaming: {
|
|
1238
1382
|
engine: "hls",
|
|
1239
|
-
hls: { ...config.streaming.hls, ...partial?.streaming?.hls }
|
|
1383
|
+
hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
|
|
1384
|
+
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder
|
|
1240
1385
|
},
|
|
1241
1386
|
logging: { ...config.logging, ...partial?.logging },
|
|
1242
1387
|
debug: { ...config.debug, ...partial?.debug },
|
|
@@ -1273,15 +1418,19 @@ function createVideoPlayer(options) {
|
|
|
1273
1418
|
PlaybackSourceManager,
|
|
1274
1419
|
PlayerErrorCode,
|
|
1275
1420
|
RefreshManager,
|
|
1421
|
+
SECRET_SCAN_MAX_DEPTH,
|
|
1276
1422
|
assertNoSecretsInConfig,
|
|
1423
|
+
createAuthParamsLoader,
|
|
1277
1424
|
createPlayerError,
|
|
1278
1425
|
createVideoPlayer,
|
|
1279
1426
|
getLiveEdgeTime,
|
|
1280
1427
|
isAtLiveEdge,
|
|
1428
|
+
isPlainObject,
|
|
1281
1429
|
mergePlayerConfig,
|
|
1282
1430
|
redactObject,
|
|
1283
1431
|
redactSensitive,
|
|
1284
1432
|
redactValue,
|
|
1433
|
+
rewriteHlsRequestUrl,
|
|
1285
1434
|
seekToLiveEdge,
|
|
1286
1435
|
themeToCssVars
|
|
1287
1436
|
});
|